AAPT2: Introduce notion of 'product' to ResourceTable

This allows us to preserve the various product definitions during the compile
phase, and allows us to select the product in the link phase.

This allows compiled files to remain product-independent, so that they do not need
to be recompiled when switching targets.

Bug:25958912
Change-Id: Iaa7eed25c834b67a39cdc9be43613e8b5ab6cdd7
diff --git a/tools/aapt2/Android.mk b/tools/aapt2/Android.mk
index 88b6270..cb82ac3 100644
--- a/tools/aapt2/Android.mk
+++ b/tools/aapt2/Android.mk
@@ -38,6 +38,7 @@
 	io/ZipArchive.cpp \
 	link/AutoVersioner.cpp \
 	link/ManifestFixer.cpp \
+	link/ProductFilter.cpp \
 	link/PrivateAttributeMover.cpp \
 	link/ReferenceLinker.cpp \
 	link/TableMerger.cpp \
@@ -83,6 +84,7 @@
 	link/AutoVersioner_test.cpp \
 	link/ManifestFixer_test.cpp \
 	link/PrivateAttributeMover_test.cpp \
+	link/ProductFilter_test.cpp \
 	link/ReferenceLinker_test.cpp \
 	link/TableMerger_test.cpp \
 	link/XmlReferenceLinker_test.cpp \
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index b4e75f9..4bea129 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -144,8 +144,8 @@
 
                 PrintVisitor visitor;
                 for (const auto& value : entry->values) {
-                    std::cout << "      (" << value.config << ") ";
-                    value.value->accept(&visitor);
+                    std::cout << "      (" << value->config << ") ";
+                    value->value->accept(&visitor);
                     std::cout << std::endl;
                 }
             }
@@ -176,7 +176,7 @@
         if (result) {
             ResourceEntry* entry = result.value().entry;
             for (const auto& value : entry->values) {
-                if (Style* style = valueCast<Style>(value.value.get())) {
+                if (Style* style = valueCast<Style>(value->value.get())) {
                     if (style->parent && style->parent.value().name) {
                         parents.insert(style->parent.value().name.value());
                         stylesToVisit.push(style->parent.value().name.value());
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index b37d366..b100e84 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -19,7 +19,6 @@
 #include "ResourceUtils.h"
 #include "ResourceValues.h"
 #include "ValueVisitor.h"
-#include "util/Comparators.h"
 #include "util/ImmutableMap.h"
 #include "util/Util.h"
 #include "xml/XmlPullParser.h"
@@ -71,6 +70,7 @@
 struct ParsedResource {
     ResourceName name;
     ConfigDescription config;
+    std::string product;
     Source source;
     ResourceId id;
     Maybe<SymbolState> symbolState;
@@ -79,35 +79,6 @@
     std::list<ParsedResource> childResources;
 };
 
-bool ResourceParser::shouldStripResource(const ResourceNameRef& name,
-                                         const Maybe<std::u16string>& product) const {
-    if (product) {
-        for (const std::u16string& productToMatch : mOptions.products) {
-            if (product.value() == productToMatch) {
-                // We specified a product, and it is in the list, so don't strip.
-                return false;
-            }
-        }
-    }
-
-    // Nothing matched, try 'default'. Default only matches if we didn't already use another
-    // product variant.
-    if (!product || product.value() == u"default") {
-        if (Maybe<ResourceTable::SearchResult> result = mTable->findResource(name)) {
-            const ResourceEntry* entry = result.value().entry;
-            auto iter = std::lower_bound(entry->values.begin(), entry->values.end(), mConfig,
-                                         cmp::lessThanConfig);
-            if (iter != entry->values.end() && iter->config == mConfig && !iter->value->isWeak()) {
-                // We have a value for this config already, and it is not weak,
-                // so filter out this default.
-                return true;
-            }
-        }
-        return false;
-    }
-    return true;
-}
-
 // Recursively adds resources to the ResourceTable.
 static bool addResourcesToTable(ResourceTable* table, IDiagnostics* diag, ParsedResource* res) {
     if (res->symbolState) {
@@ -125,7 +96,8 @@
         res->value->setComment(std::move(res->comment));
         res->value->setSource(std::move(res->source));
 
-        if (!table->addResource(res->name, res->id, res->config, std::move(res->value), diag)) {
+        if (!table->addResource(res->name, res->id, res->config, res->product,
+                                std::move(res->value), diag)) {
             return false;
         }
     }
@@ -295,9 +267,8 @@
         parsedResource.comment = std::move(comment);
 
         // Extract the product name if it exists.
-        Maybe<std::u16string> product;
         if (Maybe<StringPiece16> maybeProduct = xml::findNonEmptyAttribute(parser, u"product")) {
-            product = maybeProduct.value().toString();
+            parsedResource.product = util::utf16ToUtf8(maybeProduct.value());
         }
 
         // Parse the resource regardless of product.
@@ -306,12 +277,7 @@
             continue;
         }
 
-        // We successfully parsed the resource. Check if we should include it or strip it.
-        if (shouldStripResource(parsedResource.name, product)) {
-            // Record that we stripped out this resource name.
-            // We will check that at least one variant of this resource was included.
-            strippedResources.insert(parsedResource.name);
-        } else if (!addResourcesToTable(mTable, mDiag, &parsedResource)) {
+        if (!addResourcesToTable(mTable, mDiag, &parsedResource)) {
             error = true;
         }
     }
@@ -524,7 +490,7 @@
         // name.package can be empty here, as it will assume the package name of the table.
         std::unique_ptr<Id> id = util::make_unique<Id>();
         id->setSource(mSource.withLine(beginXmlLine));
-        mTable->addResource(name, {}, std::move(id), mDiag);
+        mTable->addResource(name, {}, {}, std::move(id), mDiag);
     };
 
     // Process the raw value.
diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h
index 51cbbe1..ee5b337 100644
--- a/tools/aapt2/ResourceParser.h
+++ b/tools/aapt2/ResourceParser.h
@@ -34,13 +34,6 @@
 
 struct ResourceParserOptions {
     /**
-     * Optional product names by which to filter resources.
-     * This is like a preprocessor definition in that we strip out resources
-     * that don't match before we compile them.
-     */
-    std::vector<std::u16string> products;
-
-    /**
      * Whether the default setting for this parser is to allow translation.
      */
     bool translatable = true;
@@ -106,9 +99,6 @@
     bool parseArrayImpl(xml::XmlPullParser* parser, ParsedResource* outResource, uint32_t typeMask);
     bool parsePlural(xml::XmlPullParser* parser, ParsedResource* outResource);
 
-    bool shouldStripResource(const ResourceNameRef& name,
-                             const Maybe<std::u16string>& product) const;
-
     IDiagnostics* mDiag;
     ResourceTable* mTable;
     Source mSource;
diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp
index cf0fcd1..3450de9 100644
--- a/tools/aapt2/ResourceParser_test.cpp
+++ b/tools/aapt2/ResourceParser_test.cpp
@@ -48,24 +48,13 @@
     }
 
     ::testing::AssertionResult testParse(const StringPiece& str) {
-        return testParse(str, ConfigDescription{}, {});
+        return testParse(str, ConfigDescription{});
     }
 
     ::testing::AssertionResult testParse(const StringPiece& str, const ConfigDescription& config) {
-        return testParse(str, config, {});
-    }
-
-    ::testing::AssertionResult testParse(const StringPiece& str,
-                                         std::initializer_list<std::u16string> products) {
-        return testParse(str, {}, std::move(products));
-    }
-
-    ::testing::AssertionResult testParse(const StringPiece& str, const ConfigDescription& config,
-                                         std::initializer_list<std::u16string> products) {
         std::stringstream input(kXmlPreamble);
         input << "<resources>\n" << str << "\n</resources>" << std::endl;
         ResourceParserOptions parserOptions;
-        parserOptions.products = products;
         ResourceParser parser(mContext->getDiagnostics(), &mTable, Source{ "test" }, config,
                               parserOptions);
         xml::XmlPullParser xmlParser(input);
@@ -546,7 +535,7 @@
     ASSERT_NE(nullptr, id);
 }
 
-TEST_F(ResourceParserTest, FilterProductsThatDontMatch) {
+TEST_F(ResourceParserTest, KeepAllProducts) {
     std::string input = R"EOF(
         <string name="foo" product="phone">hi</string>
         <string name="foo" product="no-sdcard">ho</string>
@@ -555,33 +544,26 @@
         <string name="bit" product="phablet">hoot</string>
         <string name="bot" product="default">yes</string>
     )EOF";
-    ASSERT_TRUE(testParse(input, { std::u16string(u"no-sdcard"), std::u16string(u"phablet") }));
+    ASSERT_TRUE(testParse(input));
 
-    String* fooStr = test::getValue<String>(&mTable, u"@string/foo");
-    ASSERT_NE(nullptr, fooStr);
-    EXPECT_EQ(StringPiece16(u"ho"), *fooStr->value);
-
-    EXPECT_NE(nullptr, test::getValue<String>(&mTable, u"@string/bar"));
-    EXPECT_NE(nullptr, test::getValue<String>(&mTable, u"@string/baz"));
-    EXPECT_NE(nullptr, test::getValue<String>(&mTable, u"@string/bit"));
-    EXPECT_NE(nullptr, test::getValue<String>(&mTable, u"@string/bot"));
-}
-
-TEST_F(ResourceParserTest, FilterProductsThatBothMatchInOrder) {
-    std::string input = R"EOF(
-        <string name="foo" product="phone">phone</string>
-        <string name="foo" product="default">default</string>
-    )EOF";
-    ASSERT_TRUE(testParse(input, { std::u16string(u"phone") }));
-
-    String* foo = test::getValue<String>(&mTable, u"@string/foo");
-    ASSERT_NE(nullptr, foo);
-    EXPECT_EQ(std::u16string(u"phone"), *foo->value);
-}
-
-TEST_F(ResourceParserTest, FailWhenProductFilterStripsOutAllVersionsOfResource) {
-    std::string input = "<string name=\"foo\" product=\"tablet\">hello</string>\n";
-    ASSERT_FALSE(testParse(input, { std::u16string(u"phone") }));
+    EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, u"@string/foo",
+                                                                 ConfigDescription::defaultConfig(),
+                                                                 "phone"));
+    EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, u"@string/foo",
+                                                                 ConfigDescription::defaultConfig(),
+                                                                 "no-sdcard"));
+    EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, u"@string/bar",
+                                                                 ConfigDescription::defaultConfig(),
+                                                                 ""));
+    EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, u"@string/baz",
+                                                                 ConfigDescription::defaultConfig(),
+                                                                 ""));
+    EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, u"@string/bit",
+                                                                 ConfigDescription::defaultConfig(),
+                                                                 "phablet"));
+    EXPECT_NE(nullptr, test::getValueForConfigAndProduct<String>(&mTable, u"@string/bot",
+                                                                 ConfigDescription::defaultConfig(),
+                                                                 "default"));
 }
 
 TEST_F(ResourceParserTest, AutoIncrementIdsInPublicGroup) {
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index 8a3d047..3e73be4 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -19,8 +19,6 @@
 #include "ResourceTable.h"
 #include "ResourceValues.h"
 #include "ValueVisitor.h"
-
-#include "util/Comparators.h"
 #include "util/Util.h"
 
 #include <algorithm>
@@ -124,6 +122,73 @@
     return entries.emplace(iter, new ResourceEntry(name))->get();
 }
 
+ResourceConfigValue* ResourceEntry::findValue(const ConfigDescription& config) {
+    return findValue(config, StringPiece());
+}
+
+struct ConfigKey {
+    const ConfigDescription* config;
+    const StringPiece& product;
+};
+
+bool ltConfigKeyRef(const std::unique_ptr<ResourceConfigValue>& lhs, const ConfigKey& rhs) {
+    int cmp = lhs->config.compare(*rhs.config);
+    if (cmp == 0) {
+        cmp = StringPiece(lhs->product).compare(rhs.product);
+    }
+    return cmp < 0;
+}
+
+ResourceConfigValue* ResourceEntry::findValue(const ConfigDescription& config,
+                                              const StringPiece& product) {
+    auto iter = std::lower_bound(values.begin(), values.end(),
+                                 ConfigKey{ &config, product }, ltConfigKeyRef);
+    if (iter != values.end()) {
+        ResourceConfigValue* value = iter->get();
+        if (value->config == config && StringPiece(value->product) == product) {
+            return value;
+        }
+    }
+    return nullptr;
+}
+
+ResourceConfigValue* ResourceEntry::findOrCreateValue(const ConfigDescription& config,
+                                                      const StringPiece& product) {
+    auto iter = std::lower_bound(values.begin(), values.end(),
+                                 ConfigKey{ &config, product }, ltConfigKeyRef);
+    if (iter != values.end()) {
+        ResourceConfigValue* value = iter->get();
+        if (value->config == config && StringPiece(value->product) == product) {
+            return value;
+        }
+    }
+    ResourceConfigValue* newValue = values.insert(
+            iter, util::make_unique<ResourceConfigValue>(config, product))->get();
+    return newValue;
+}
+
+std::vector<ResourceConfigValue*> ResourceEntry::findAllValues(const ConfigDescription& config) {
+    std::vector<ResourceConfigValue*> results;
+
+    auto iter = values.begin();
+    for (; iter != values.end(); ++iter) {
+        ResourceConfigValue* value = iter->get();
+        if (value->config == config) {
+            results.push_back(value);
+            ++iter;
+            break;
+        }
+    }
+
+    for (; iter != values.end(); ++iter) {
+        ResourceConfigValue* value = iter->get();
+        if (value->config == config) {
+            results.push_back(value);
+        }
+    }
+    return results;
+}
+
 /**
  * The default handler for collisions. A return value of -1 means keep the
  * existing value, 0 means fail, and +1 means take the incoming value.
@@ -188,56 +253,69 @@
 static constexpr const char16_t* kValidNameChars = u"._-";
 static constexpr const char16_t* kValidNameMangledChars = u"._-$";
 
-bool ResourceTable::addResource(const ResourceNameRef& name, const ConfigDescription& config,
-                                std::unique_ptr<Value> value, IDiagnostics* diag) {
-    return addResourceImpl(name, {}, config, std::move(value), kValidNameChars,
-                           resolveValueCollision, diag);
-}
-
-bool ResourceTable::addResource(const ResourceNameRef& name, const ResourceId resId,
-                                const ConfigDescription& config, std::unique_ptr<Value> value,
+bool ResourceTable::addResource(const ResourceNameRef& name,
+                                const ConfigDescription& config,
+                                const StringPiece& product,
+                                std::unique_ptr<Value> value,
                                 IDiagnostics* diag) {
-    return addResourceImpl(name, resId, config, std::move(value), kValidNameChars,
+    return addResourceImpl(name, {}, config, product, std::move(value), kValidNameChars,
                            resolveValueCollision, diag);
 }
 
-bool ResourceTable::addFileReference(const ResourceNameRef& name, const ConfigDescription& config,
-                                     const Source& source, const StringPiece16& path,
+bool ResourceTable::addResource(const ResourceNameRef& name,
+                                const ResourceId resId,
+                                const ConfigDescription& config,
+                                const StringPiece& product,
+                                std::unique_ptr<Value> value,
+                                IDiagnostics* diag) {
+    return addResourceImpl(name, resId, config, product, std::move(value), kValidNameChars,
+                           resolveValueCollision, diag);
+}
+
+bool ResourceTable::addFileReference(const ResourceNameRef& name,
+                                     const ConfigDescription& config,
+                                     const Source& source,
+                                     const StringPiece16& path,
                                      IDiagnostics* diag) {
     return addFileReference(name, config, source, path, resolveValueCollision, diag);
 }
 
-bool ResourceTable::addFileReference(const ResourceNameRef& name, const ConfigDescription& config,
-                                     const Source& source, const StringPiece16& path,
+bool ResourceTable::addFileReference(const ResourceNameRef& name,
+                                     const ConfigDescription& config,
+                                     const Source& source,
+                                     const StringPiece16& path,
                                      std::function<int(Value*,Value*)> conflictResolver,
                                      IDiagnostics* diag) {
     std::unique_ptr<FileReference> fileRef = util::make_unique<FileReference>(
             stringPool.makeRef(path));
     fileRef->setSource(source);
-    return addResourceImpl(name, ResourceId{}, config, std::move(fileRef), kValidNameChars,
-                           conflictResolver, diag);
+    return addResourceImpl(name, ResourceId{}, config, StringPiece{}, std::move(fileRef),
+                           kValidNameChars, conflictResolver, diag);
 }
 
 bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name,
                                             const ConfigDescription& config,
+                                            const StringPiece& product,
                                             std::unique_ptr<Value> value,
                                             IDiagnostics* diag) {
-    return addResourceImpl(name, ResourceId{}, config, std::move(value), kValidNameMangledChars,
-                           resolveValueCollision, diag);
+    return addResourceImpl(name, ResourceId{}, config, product, std::move(value),
+                           kValidNameMangledChars, resolveValueCollision, diag);
 }
 
 bool ResourceTable::addResourceAllowMangled(const ResourceNameRef& name,
                                             const ResourceId id,
                                             const ConfigDescription& config,
+                                            const StringPiece& product,
                                             std::unique_ptr<Value> value,
                                             IDiagnostics* diag) {
-    return addResourceImpl(name, id, config, std::move(value), kValidNameMangledChars,
+    return addResourceImpl(name, id, config, product, std::move(value), kValidNameMangledChars,
                            resolveValueCollision, diag);
 }
 
 bool ResourceTable::addResourceImpl(const ResourceNameRef& name,
                                     const ResourceId resId,
                                     const ConfigDescription& config,
+                                    const StringPiece& product,
                                     std::unique_ptr<Value> value,
                                     const char16_t* validChars,
                                     std::function<int(Value*,Value*)> conflictResolver,
@@ -298,21 +376,21 @@
         return false;
     }
 
-    const auto endIter = entry->values.end();
-    auto iter = std::lower_bound(entry->values.begin(), endIter, config, cmp::lessThanConfig);
-    if (iter == endIter || iter->config != config) {
-        // This resource did not exist before, add it.
-        entry->values.insert(iter, ResourceConfigValue{ config, std::move(value) });
+    ResourceConfigValue* configValue = entry->findOrCreateValue(config, product);
+    if (!configValue->value) {
+        // Resource does not exist, add it now.
+        configValue->value = std::move(value);
+
     } else {
-        int collisionResult = conflictResolver(iter->value.get(), value.get());
+        int collisionResult = conflictResolver(configValue->value.get(), value.get());
         if (collisionResult > 0) {
             // Take the incoming value.
-            iter->value = std::move(value);
+            configValue->value = std::move(value);
         } else if (collisionResult == 0) {
             diag->error(DiagMessage(value->getSource())
                         << "duplicate value for resource '" << name << "' "
                         << "with config '" << config << "'");
-            diag->error(DiagMessage(iter->value->getSource())
+            diag->error(DiagMessage(configValue->value->getSource())
                         << "resource previously defined here");
             return false;
         }
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index 6b7b07e..8ffff1f 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -24,9 +24,12 @@
 #include "Source.h"
 #include "StringPool.h"
 
+#include <android-base/macros.h>
+#include <map>
 #include <memory>
 #include <string>
 #include <tuple>
+#include <unordered_map>
 #include <vector>
 
 namespace aapt {
@@ -46,19 +49,36 @@
     std::u16string comment;
 };
 
-/**
- * Represents a value defined for a given configuration.
- */
-struct ResourceConfigValue {
-    ConfigDescription config;
+class ResourceConfigValue {
+public:
+    /**
+     * The configuration for which this value is defined.
+     */
+    const ConfigDescription config;
+
+    /**
+     * The product for which this value is defined.
+     */
+    const std::string product;
+
+    /**
+     * The actual Value.
+     */
     std::unique_ptr<Value> value;
+
+    ResourceConfigValue(const ConfigDescription& config, const StringPiece& product) :
+            config(config), product(product.toString()) { }
+
+private:
+    DISALLOW_COPY_AND_ASSIGN(ResourceConfigValue);
 };
 
 /**
  * Represents a resource entry, which may have
  * varying values for each defined configuration.
  */
-struct ResourceEntry {
+class ResourceEntry {
+public:
     /**
      * The name of the resource. Immutable, as
      * this determines the order of this resource
@@ -72,24 +92,33 @@
     Maybe<uint16_t> id;
 
     /**
-     * Whether this resource is public (and must maintain the same
-     * entry ID across builds).
+     * Whether this resource is public (and must maintain the same entry ID across builds).
      */
     Symbol symbolStatus;
 
     /**
      * The resource's values for each configuration.
      */
-    std::vector<ResourceConfigValue> values;
+    std::vector<std::unique_ptr<ResourceConfigValue>> values;
 
     ResourceEntry(const StringPiece16& name) : name(name.toString()) { }
+
+    ResourceConfigValue* findValue(const ConfigDescription& config);
+    ResourceConfigValue* findValue(const ConfigDescription& config, const StringPiece& product);
+    ResourceConfigValue* findOrCreateValue(const ConfigDescription& config,
+                                           const StringPiece& product);
+    std::vector<ResourceConfigValue*> findAllValues(const ConfigDescription& config);
+
+private:
+    DISALLOW_COPY_AND_ASSIGN(ResourceEntry);
 };
 
 /**
  * Represents a resource type, which holds entries defined
  * for this type.
  */
-struct ResourceTableType {
+class ResourceTableType {
+public:
     /**
      * The logical type of resource (string, drawable, layout, etc.).
      */
@@ -114,8 +143,10 @@
     explicit ResourceTableType(const ResourceType type) : type(type) { }
 
     ResourceEntry* findEntry(const StringPiece16& name);
-
     ResourceEntry* findOrCreateEntry(const StringPiece16& name);
+
+private:
+    DISALLOW_COPY_AND_ASSIGN(ResourceTableType);
 };
 
 enum class PackageType {
@@ -125,16 +156,20 @@
     Dynamic
 };
 
-struct ResourceTablePackage {
+class ResourceTablePackage {
+public:
     PackageType type = PackageType::App;
     Maybe<uint8_t> id;
     std::u16string name;
 
     std::vector<std::unique_ptr<ResourceTableType>> types;
 
+    ResourceTablePackage() = default;
     ResourceTableType* findType(ResourceType type);
-
     ResourceTableType* findOrCreateType(const ResourceType type);
+
+private:
+    DISALLOW_COPY_AND_ASSIGN(ResourceTablePackage);
 };
 
 /**
@@ -144,8 +179,6 @@
 class ResourceTable {
 public:
     ResourceTable() = default;
-    ResourceTable(const ResourceTable&) = delete;
-    ResourceTable& operator=(const ResourceTable&) = delete;
 
     /**
      * When a collision of resources occurs, this method decides which value to keep.
@@ -155,38 +188,59 @@
      */
     static int resolveValueCollision(Value* existing, Value* incoming);
 
-    bool addResource(const ResourceNameRef& name, const ConfigDescription& config,
-                     std::unique_ptr<Value> value, IDiagnostics* diag);
-
-    bool addResource(const ResourceNameRef& name, const ResourceId resId,
-                     const ConfigDescription& config, std::unique_ptr<Value> value,
+    bool addResource(const ResourceNameRef& name,
+                     const ConfigDescription& config,
+                     const StringPiece& product,
+                     std::unique_ptr<Value> value,
                      IDiagnostics* diag);
 
-    bool addFileReference(const ResourceNameRef& name, const ConfigDescription& config,
-                          const Source& source, const StringPiece16& path,
+    bool addResource(const ResourceNameRef& name,
+                     const ResourceId resId,
+                     const ConfigDescription& config,
+                     const StringPiece& product,
+                     std::unique_ptr<Value> value,
+                     IDiagnostics* diag);
+
+    bool addFileReference(const ResourceNameRef& name,
+                          const ConfigDescription& config,
+                          const Source& source,
+                          const StringPiece16& path,
                           IDiagnostics* diag);
 
-    bool addFileReference(const ResourceNameRef& name, const ConfigDescription& config,
-                          const Source& source, const StringPiece16& path,
-                          std::function<int(Value*,Value*)> conflictResolver, IDiagnostics* diag);
+    bool addFileReference(const ResourceNameRef& name,
+                          const ConfigDescription& config,
+                          const Source& source,
+                          const StringPiece16& path,
+                          std::function<int(Value*,Value*)> conflictResolver,
+                          IDiagnostics* diag);
 
     /**
      * Same as addResource, but doesn't verify the validity of the name. This is used
      * when loading resources from an existing binary resource table that may have mangled
      * names.
      */
-    bool addResourceAllowMangled(const ResourceNameRef& name, const ConfigDescription& config,
-                                 std::unique_ptr<Value> value, IDiagnostics* diag);
-
-    bool addResourceAllowMangled(const ResourceNameRef& name, const ResourceId id,
-                                 const ConfigDescription& config, std::unique_ptr<Value> value,
+    bool addResourceAllowMangled(const ResourceNameRef& name,
+                                 const ConfigDescription& config,
+                                 const StringPiece& product,
+                                 std::unique_ptr<Value> value,
                                  IDiagnostics* diag);
 
-    bool setSymbolState(const ResourceNameRef& name, const ResourceId resId,
-                        const Symbol& symbol, IDiagnostics* diag);
+    bool addResourceAllowMangled(const ResourceNameRef& name,
+                                 const ResourceId id,
+                                 const ConfigDescription& config,
+                                 const StringPiece& product,
+                                 std::unique_ptr<Value> value,
+                                 IDiagnostics* diag);
 
-    bool setSymbolStateAllowMangled(const ResourceNameRef& name, const ResourceId resId,
-                                    const Symbol& symbol, IDiagnostics* diag);
+    bool setSymbolState(const ResourceNameRef& name,
+                        const ResourceId resId,
+                        const Symbol& symbol,
+                        IDiagnostics* diag);
+
+    bool setSymbolStateAllowMangled(const ResourceNameRef& name,
+                                    const ResourceId resId,
+                                    const Symbol& symbol,
+                                    IDiagnostics* diag);
 
     struct SearchResult {
         ResourceTablePackage* package;
@@ -229,13 +283,19 @@
     bool addResourceImpl(const ResourceNameRef& name,
                          ResourceId resId,
                          const ConfigDescription& config,
+                         const StringPiece& product,
                          std::unique_ptr<Value> value,
                          const char16_t* validChars,
                          std::function<int(Value*,Value*)> conflictResolver,
                          IDiagnostics* diag);
 
-    bool setSymbolStateImpl(const ResourceNameRef& name, ResourceId resId,
-                            const Symbol& symbol, const char16_t* validChars, IDiagnostics* diag);
+    bool setSymbolStateImpl(const ResourceNameRef& name,
+                            ResourceId resId,
+                            const Symbol& symbol,
+                            const char16_t* validChars,
+                            IDiagnostics* diag);
+
+    DISALLOW_COPY_AND_ASSIGN(ResourceTable);
 };
 
 } // namespace aapt
diff --git a/tools/aapt2/ResourceTable_test.cpp b/tools/aapt2/ResourceTable_test.cpp
index 42508fe..180bd11 100644
--- a/tools/aapt2/ResourceTable_test.cpp
+++ b/tools/aapt2/ResourceTable_test.cpp
@@ -43,13 +43,13 @@
 
     EXPECT_FALSE(table.addResource(
             ResourceNameRef(u"android", ResourceType::kId, u"hey,there"),
-            ConfigDescription{},
+            ConfigDescription{}, "",
             test::ValueBuilder<Id>().setSource("test.xml", 21u).build(),
             &mDiagnostics));
 
     EXPECT_FALSE(table.addResource(
             ResourceNameRef(u"android", ResourceType::kId, u"hey:there"),
-            ConfigDescription{},
+            ConfigDescription{}, "",
             test::ValueBuilder<Id>().setSource("test.xml", 21u).build(),
             &mDiagnostics));
 }
@@ -59,6 +59,7 @@
 
     EXPECT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/id"),
                                   ConfigDescription{},
+                                  "",
                                   test::ValueBuilder<Id>()
                                           .setSource("test/path/file.xml", 23u).build(),
                                   &mDiagnostics));
@@ -76,24 +77,28 @@
     EXPECT_TRUE(table.addResource(
             test::parseNameOrDie(u"@android:attr/layout_width"),
             config,
+            "",
             test::ValueBuilder<Id>().setSource("test/path/file.xml", 10u).build(),
             &mDiagnostics));
 
     EXPECT_TRUE(table.addResource(
             test::parseNameOrDie(u"@android:attr/id"),
             config,
+            "",
             test::ValueBuilder<Id>().setSource("test/path/file.xml", 12u).build(),
             &mDiagnostics));
 
     EXPECT_TRUE(table.addResource(
             test::parseNameOrDie(u"@android:string/ok"),
             config,
+            "",
             test::ValueBuilder<Id>().setSource("test/path/file.xml", 14u).build(),
             &mDiagnostics));
 
     EXPECT_TRUE(table.addResource(
             test::parseNameOrDie(u"@android:string/ok"),
             languageConfig,
+            "",
             test::ValueBuilder<BinaryPrimitive>(android::Res_value{})
                     .setSource("test/path/file.xml", 20u)
                     .build(),
@@ -110,18 +115,49 @@
     ResourceTable table;
 
     ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/foo"), ConfigDescription{},
-                                  util::make_unique<Attribute>(true), &mDiagnostics));
+                                  "", util::make_unique<Attribute>(true), &mDiagnostics));
 
     Attribute* attr = test::getValue<Attribute>(&table, u"@android:attr/foo");
     ASSERT_NE(nullptr, attr);
     EXPECT_TRUE(attr->isWeak());
 
     ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:attr/foo"), ConfigDescription{},
-                                  util::make_unique<Attribute>(false), &mDiagnostics));
+                                  "", util::make_unique<Attribute>(false), &mDiagnostics));
 
     attr = test::getValue<Attribute>(&table, u"@android:attr/foo");
     ASSERT_NE(nullptr, attr);
     EXPECT_FALSE(attr->isWeak());
 }
 
+TEST_F(ResourceTableTest, ProductVaryingValues) {
+    ResourceTable table;
+
+    EXPECT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/foo"),
+                                  test::parseConfigOrDie("land"),
+                                  "tablet",
+                                  util::make_unique<Id>(),
+                                  &mDiagnostics));
+    EXPECT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/foo"),
+                                  test::parseConfigOrDie("land"),
+                                  "phone",
+                                  util::make_unique<Id>(),
+                                  &mDiagnostics));
+
+    EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, u"@android:string/foo",
+                                                             test::parseConfigOrDie("land"),
+                                                             "tablet"));
+    EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, u"@android:string/foo",
+                                                             test::parseConfigOrDie("land"),
+                                                             "phone"));
+
+    Maybe<ResourceTable::SearchResult> sr = table.findResource(
+            test::parseNameOrDie(u"@android:string/foo"));
+    AAPT_ASSERT_TRUE(sr);
+    std::vector<ResourceConfigValue*> values = sr.value().entry->findAllValues(
+            test::parseConfigOrDie("land"));
+    ASSERT_EQ(2u, values.size());
+    EXPECT_EQ(std::string("phone"), values[0]->product);
+    EXPECT_EQ(std::string("tablet"), values[1]->product);
+}
+
 } // namespace aapt
diff --git a/tools/aapt2/ValueVisitor.h b/tools/aapt2/ValueVisitor.h
index 5493039..ea2aa55 100644
--- a/tools/aapt2/ValueVisitor.h
+++ b/tools/aapt2/ValueVisitor.h
@@ -146,7 +146,7 @@
     for (auto& type : pkg->types) {
         for (auto& entry : type->entries) {
             for (auto& configValue : entry->values) {
-                configValue.value->accept(visitor);
+                configValue->value->accept(visitor);
             }
         }
     }
diff --git a/tools/aapt2/compile/Compile.cpp b/tools/aapt2/compile/Compile.cpp
index 1eefb82..0dd8e18 100644
--- a/tools/aapt2/compile/Compile.cpp
+++ b/tools/aapt2/compile/Compile.cpp
@@ -107,7 +107,6 @@
 struct CompileOptions {
     std::string outputPath;
     Maybe<std::string> resDir;
-    std::vector<std::u16string> products;
     bool pseudolocalize = false;
     bool legacyMode = false;
     bool verbose = false;
@@ -198,7 +197,6 @@
         xml::XmlPullParser xmlParser(fin);
 
         ResourceParserOptions parserOptions;
-        parserOptions.products = options.products;
         parserOptions.errorOnPositionalArguments = !options.legacyMode;
 
         // If the filename includes donottranslate, then the default translatable is false.
@@ -457,11 +455,8 @@
 int compile(const std::vector<StringPiece>& args) {
     CompileOptions options;
 
-    Maybe<std::string> productList;
     Flags flags = Flags()
             .requiredFlag("-o", "Output path", &options.outputPath)
-            .optionalFlag("--product", "Comma separated list of product types to compile",
-                          &productList)
             .optionalFlag("--dir", "Directory to scan for resources", &options.resDir)
             .optionalSwitch("--pseudo-localize", "Generate resources for pseudo-locales "
                             "(en-XA and ar-XB)", &options.pseudolocalize)
@@ -472,12 +467,6 @@
         return 1;
     }
 
-    if (productList) {
-        for (StringPiece part : util::tokenize<char>(productList.value(), ',')) {
-            options.products.push_back(util::utf8ToUtf16(part));
-        }
-    }
-
     CompileContext context;
     std::unique_ptr<IArchiveWriter> archiveWriter;
 
diff --git a/tools/aapt2/compile/PseudolocaleGenerator.cpp b/tools/aapt2/compile/PseudolocaleGenerator.cpp
index 2963d13..be26b52 100644
--- a/tools/aapt2/compile/PseudolocaleGenerator.cpp
+++ b/tools/aapt2/compile/PseudolocaleGenerator.cpp
@@ -19,7 +19,6 @@
 #include "ValueVisitor.h"
 #include "compile/PseudolocaleGenerator.h"
 #include "compile/Pseudolocalizer.h"
-#include "util/Comparators.h"
 
 namespace aapt {
 
@@ -208,10 +207,12 @@
     return modified;
 }
 
-void pseudolocalizeIfNeeded(std::vector<ResourceConfigValue>* configValues,
-                            Pseudolocalizer::Method method, StringPool* pool, Value* value) {
+void pseudolocalizeIfNeeded(const Pseudolocalizer::Method method,
+                            ResourceConfigValue* originalValue,
+                            StringPool* pool,
+                            ResourceEntry* entry) {
     Visitor visitor(pool, method);
-    value->accept(&visitor);
+    originalValue->value->accept(&visitor);
 
     std::unique_ptr<Value> localizedValue;
     if (visitor.mValue) {
@@ -220,16 +221,18 @@
         localizedValue = std::move(visitor.mItem);
     }
 
-    if (localizedValue) {
-        ConfigDescription pseudolocalizedConfig = modifyConfigForPseudoLocale(ConfigDescription{},
-                                                                              method);
-        auto iter = std::lower_bound(configValues->begin(), configValues->end(),
-                                     pseudolocalizedConfig, cmp::lessThanConfig);
-        if (iter == configValues->end() || iter->config != pseudolocalizedConfig) {
-            // The pseudolocalized config doesn't exist, add it.
-            configValues->insert(iter, ResourceConfigValue{ pseudolocalizedConfig,
-                                                            std::move(localizedValue) });
-        }
+    if (!localizedValue) {
+        return;
+    }
+
+    ConfigDescription configWithAccent = modifyConfigForPseudoLocale(
+            originalValue->config, method);
+
+    ResourceConfigValue* newConfigValue = entry->findOrCreateValue(
+            configWithAccent, originalValue->product);
+    if (!newConfigValue->value) {
+        // Only use auto-generated pseudo-localization if none is defined.
+        newConfigValue->value = std::move(localizedValue);
     }
 }
 
@@ -239,18 +242,13 @@
     for (auto& package : table->packages) {
         for (auto& type : package->types) {
             for (auto& entry : type->entries) {
-                auto iter = std::lower_bound(entry->values.begin(), entry->values.end(),
-                                             ConfigDescription{}, cmp::lessThanConfig);
-                if (iter != entry->values.end() && iter->config == ConfigDescription{}) {
-                    // Only pseudolocalize the default configuration.
-
-                    // The iterator will be invalidated, so grab a pointer to the value.
-                    Value* originalValue = iter->value.get();
-
-                    pseudolocalizeIfNeeded(&entry->values, Pseudolocalizer::Method::kAccent,
-                                           &table->stringPool, originalValue);
-                    pseudolocalizeIfNeeded(&entry->values, Pseudolocalizer::Method::kBidi,
-                                           &table->stringPool, originalValue);
+                std::vector<ResourceConfigValue*> values = entry->findAllValues(
+                        ConfigDescription::defaultConfig());
+                for (ResourceConfigValue* value : values) {
+                    pseudolocalizeIfNeeded(Pseudolocalizer::Method::kAccent, value,
+                                           &table->stringPool, entry.get());
+                    pseudolocalizeIfNeeded(Pseudolocalizer::Method::kBidi, value,
+                                           &table->stringPool, entry.get());
                 }
             }
         }
diff --git a/tools/aapt2/flatten/TableFlattener.cpp b/tools/aapt2/flatten/TableFlattener.cpp
index 71ab3db..da81046 100644
--- a/tools/aapt2/flatten/TableFlattener.cpp
+++ b/tools/aapt2/flatten/TableFlattener.cpp
@@ -402,10 +402,10 @@
 
             const size_t configCount = entry->values.size();
             for (size_t i = 0; i < configCount; i++) {
-                const ConfigDescription& config = entry->values[i].config;
+                const ConfigDescription& config = entry->values[i]->config;
                 for (size_t j = i + 1; j < configCount; j++) {
                     configMasks[entry->id.value()] |= util::hostToDevice32(
-                            config.diff(entry->values[j].config));
+                            config.diff(entry->values[j]->config));
                 }
             }
         }
@@ -445,8 +445,8 @@
 
                 // Group values by configuration.
                 for (auto& configValue : entry->values) {
-                    configToEntryListMap[configValue.config].push_back(FlatEntry{
-                            entry, configValue.value.get(), keyIndex });
+                    configToEntryListMap[configValue->config].push_back(FlatEntry{
+                            entry, configValue->value.get(), keyIndex });
                 }
             }
 
diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp
index 7280f3a..6e340a2 100644
--- a/tools/aapt2/java/JavaClassGenerator.cpp
+++ b/tools/aapt2/java/JavaClassGenerator.cpp
@@ -23,7 +23,6 @@
 #include "java/AnnotationProcessor.h"
 #include "java/ClassDefinitionWriter.h"
 #include "java/JavaClassGenerator.h"
-#include "util/Comparators.h"
 #include "util/StringPiece.h"
 
 #include <algorithm>
@@ -258,12 +257,12 @@
         }
 
         for (const auto& configValue : entry->values) {
-            processor.appendComment(configValue.value->getComment());
+            processor.appendComment(configValue->value->getComment());
         }
 
         // If this is an Attribute, append the format Javadoc.
         if (!entry->values.empty()) {
-            if (Attribute* attr = valueCast<Attribute>(entry->values.front().value.get())) {
+            if (Attribute* attr = valueCast<Attribute>(entry->values.front()->value.get())) {
                 // We list out the available values for the given attribute.
                 addAttributeFormatDoc(&processor, attr);
             }
@@ -272,7 +271,7 @@
         if (type->type == ResourceType::kStyleable) {
             assert(!entry->values.empty());
             const Styleable* styleable = static_cast<const Styleable*>(
-                    entry->values.front().value.get());
+                    entry->values.front()->value.get());
             writeStyleableEntryForClass(outClassDef, &processor, packageNameToGenerate,
                                         unmangledName, styleable);
         } else {
@@ -311,11 +310,10 @@
 
             if (type->type == ResourceType::kAttr) {
                 // Also include private attributes in this same class.
-                auto iter = std::lower_bound(package->types.begin(), package->types.end(),
-                                             ResourceType::kAttrPrivate, cmp::lessThanType);
-                if (iter != package->types.end() && (*iter)->type == ResourceType::kAttrPrivate) {
+                ResourceTableType* privType = package->findType(ResourceType::kAttrPrivate);
+                if (privType) {
                     result = writeEntriesForClass(&classDef, packageNameToGenerate,
-                                                  package.get(), iter->get());
+                                                  package.get(), privType);
                     if (!result) {
                         return false;
                     }
diff --git a/tools/aapt2/link/AutoVersioner.cpp b/tools/aapt2/link/AutoVersioner.cpp
index c7e603e..459c330 100644
--- a/tools/aapt2/link/AutoVersioner.cpp
+++ b/tools/aapt2/link/AutoVersioner.cpp
@@ -18,9 +18,7 @@
 #include "ResourceTable.h"
 #include "SdkConstants.h"
 #include "ValueVisitor.h"
-
 #include "link/Linkers.h"
-#include "util/Comparators.h"
 
 #include <algorithm>
 #include <cassert>
@@ -31,7 +29,12 @@
                                      const int sdkVersionToGenerate) {
     assert(sdkVersionToGenerate > config.sdkVersion);
     const auto endIter = entry->values.end();
-    auto iter = std::lower_bound(entry->values.begin(), endIter, config, cmp::lessThanConfig);
+    auto iter = entry->values.begin();
+    for (; iter != endIter; ++iter) {
+        if ((*iter)->config == config) {
+            break;
+        }
+    }
 
     // The source config came from this list, so it should be here.
     assert(iter != entry->values.end());
@@ -45,10 +48,10 @@
     // 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) {
+        tempConfig.sdkVersion = (*iter)->config.sdkVersion;
+        if (tempConfig == (*iter)->config) {
             // The two configs are the same, check the sdk version.
-            return sdkVersionToGenerate < iter->config.sdkVersion;
+            return sdkVersionToGenerate < (*iter)->config.sdkVersion;
         }
     }
 
@@ -65,14 +68,14 @@
 
             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) {
+                    ResourceConfigValue* configValue = entry->values[i].get();
+                    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())) {
+                    if (Style* style = valueCast<Style>(configValue->value.get())) {
                         Maybe<size_t> minSdkStripped;
                         std::vector<Style::Entry> stripped;
 
@@ -82,7 +85,7 @@
 
                             // 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)) {
+                            if (sdkLevel > std::max<size_t>(configValue->config.sdkVersion, 1)) {
                                 // Record that we are about to strip this.
                                 stripped.emplace_back(std::move(*iter));
 
@@ -105,10 +108,10 @@
                             // there is no other defined resource for the version we want to
                             // generate.
                             if (shouldGenerateVersionedResource(entry.get(),
-                                                                configValue.config,
+                                                                configValue->config,
                                                                 minSdkStripped.value())) {
                                 // Let's create a new Style for this versioned resource.
-                                ConfigDescription newConfig(configValue.config);
+                                ConfigDescription newConfig(configValue->config);
                                 newConfig.sdkVersion = minSdkStripped.value();
 
                                 std::unique_ptr<Style> newStyle(style->clone(&table->stringPool));
@@ -121,14 +124,8 @@
                                                          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,
-                                                             cmp::lessThanConfig);
-
-                                entry->values.insert(
-                                        iter,
-                                        ResourceConfigValue{ newConfig, std::move(newStyle) });
+                                entry->findOrCreateValue(newConfig, {})->value =
+                                        std::move(newStyle);
                             }
                         }
                     }
diff --git a/tools/aapt2/link/AutoVersioner_test.cpp b/tools/aapt2/link/AutoVersioner_test.cpp
index 29bcc93..9b3a87c 100644
--- a/tools/aapt2/link/AutoVersioner_test.cpp
+++ b/tools/aapt2/link/AutoVersioner_test.cpp
@@ -15,9 +15,7 @@
  */
 
 #include "ConfigDescription.h"
-
 #include "link/Linkers.h"
-
 #include "test/Builders.h"
 #include "test/Context.h"
 
@@ -31,9 +29,9 @@
     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 });
+    entry.values.push_back(util::make_unique<ResourceConfigValue>(defaultConfig, ""));
+    entry.values.push_back(util::make_unique<ResourceConfigValue>(landConfig, ""));
+    entry.values.push_back(util::make_unique<ResourceConfigValue>(sw600dpLandConfig, ""));
 
     EXPECT_TRUE(shouldGenerateVersionedResource(&entry, defaultConfig, 17));
     EXPECT_TRUE(shouldGenerateVersionedResource(&entry, landConfig, 17));
@@ -45,9 +43,9 @@
     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 });
+    entry.values.push_back(util::make_unique<ResourceConfigValue>(defaultConfig, ""));
+    entry.values.push_back(util::make_unique<ResourceConfigValue>(sw600dpV13Config, ""));
+    entry.values.push_back(util::make_unique<ResourceConfigValue>(v21Config, ""));
 
     EXPECT_TRUE(shouldGenerateVersionedResource(&entry, defaultConfig, 17));
     EXPECT_FALSE(shouldGenerateVersionedResource(&entry, defaultConfig, 22));
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index 8e32179..3437ac0 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -31,6 +31,7 @@
 #include "java/ManifestClassGenerator.h"
 #include "java/ProguardRules.h"
 #include "link/Linkers.h"
+#include "link/ProductFilter.h"
 #include "link/ReferenceLinker.h"
 #include "link/ManifestFixer.h"
 #include "link/TableMerger.h"
@@ -70,6 +71,7 @@
     Maybe<std::u16string> privateSymbols;
     ManifestFixerOptions manifestFixerOptions;
     IConfigFilter* configFilter = nullptr;
+    std::unordered_set<std::string> products;
 };
 
 struct LinkContext : public IAaptContext {
@@ -292,16 +294,16 @@
                         for (const auto& configValue : entry->values) {
                             // Special case the occurrence of an ID that is being generated for the
                             // 'android' package. This is due to legacy reasons.
-                            if (valueCast<Id>(configValue.value.get()) &&
+                            if (valueCast<Id>(configValue->value.get()) &&
                                     package->name == u"android") {
                                 mContext->getDiagnostics()->warn(
-                                        DiagMessage(configValue.value->getSource())
+                                        DiagMessage(configValue->value->getSource())
                                         << "generated id '" << resName
                                         << "' for external package '" << package->name
                                         << "'");
                             } else {
                                 mContext->getDiagnostics()->error(
-                                        DiagMessage(configValue.value->getSource())
+                                        DiagMessage(configValue->value->getSource())
                                         << "defined resource '" << resName
                                         << "' for external package '" << package->name
                                         << "'");
@@ -512,7 +514,10 @@
 
             std::unique_ptr<Id> id = util::make_unique<Id>();
             id->setSource(fileDesc->source.withLine(exportedSymbol.line));
-            bool result = mFinalTable.addResourceAllowMangled(resName, {}, std::move(id),
+            bool result = mFinalTable.addResourceAllowMangled(resName,
+                                                              ConfigDescription::defaultConfig(),
+                                                              std::string(),
+                                                              std::move(id),
                                                               mContext->getDiagnostics());
             if (!result) {
                 return false;
@@ -681,6 +686,12 @@
                 mContext->getDiagnostics()->error(DiagMessage() << "failed linking references");
                 return 1;
             }
+
+            ProductFilter productFilter(mOptions.products);
+            if (!productFilter.consume(mContext, &mFinalTable)) {
+                mContext->getDiagnostics()->error(DiagMessage() << "failed stripping products");
+                return 1;
+            }
         }
 
         proguard::KeepSet proguardKeepSet;
@@ -931,6 +942,7 @@
     Maybe<std::string> customJavaPackage;
     std::vector<std::string> extraJavaPackages;
     Maybe<std::string> configs;
+    Maybe<std::string> productList;
     bool legacyXFlag = false;
     bool requireLocalization = false;
     Flags flags = Flags()
@@ -954,6 +966,8 @@
                             &requireLocalization)
             .optionalFlag("-c", "Comma separated list of configurations to include. The default\n"
                                 "is all configurations", &configs)
+            .optionalFlag("--product", "Comma separated list of product names to keep",
+                          &productList)
             .optionalSwitch("--output-to-dir", "Outputs the APK contents to a directory specified "
                             "by -o",
                             &options.outputToDirectory)
@@ -1039,6 +1053,14 @@
         }
     }
 
+    if (productList) {
+        for (StringPiece product : util::tokenize<char>(productList.value(), ',')) {
+            if (product != "" && product != "default") {
+                options.products.insert(product.toString());
+            }
+        }
+    }
+
     AxisConfigFilter filter;
     if (configs) {
         for (const StringPiece& configStr : util::tokenize<char>(configs.value(), ',')) {
diff --git a/tools/aapt2/link/Linkers.h b/tools/aapt2/link/Linkers.h
index 4d3a483..ec532ab 100644
--- a/tools/aapt2/link/Linkers.h
+++ b/tools/aapt2/link/Linkers.h
@@ -26,7 +26,7 @@
 namespace aapt {
 
 class ResourceTable;
-struct ResourceEntry;
+class ResourceEntry;
 struct ConfigDescription;
 
 /**
diff --git a/tools/aapt2/link/ProductFilter.cpp b/tools/aapt2/link/ProductFilter.cpp
new file mode 100644
index 0000000..8784e89
--- /dev/null
+++ b/tools/aapt2/link/ProductFilter.cpp
@@ -0,0 +1,118 @@
+/*
+ * 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 "link/ProductFilter.h"
+
+namespace aapt {
+
+ProductFilter::ResourceConfigValueIter
+ProductFilter::selectProductToKeep(const ResourceNameRef& name,
+                                   const ResourceConfigValueIter begin,
+                                   const ResourceConfigValueIter end,
+                                   IDiagnostics* diag) {
+    ResourceConfigValueIter defaultProductIter = end;
+    ResourceConfigValueIter selectedProductIter = end;
+
+    for (ResourceConfigValueIter iter = begin; iter != end; ++iter) {
+        ResourceConfigValue* configValue = iter->get();
+        if (mProducts.find(configValue->product) != mProducts.end()) {
+            if (selectedProductIter != end) {
+                // We have two possible values for this product!
+                diag->error(DiagMessage(configValue->value->getSource())
+                            << "selection of product '" << configValue->product
+                            << "' for resource " << name << " is ambiguous");
+
+                ResourceConfigValue* previouslySelectedConfigValue = selectedProductIter->get();
+                diag->note(DiagMessage(previouslySelectedConfigValue->value->getSource())
+                           << "product '" << previouslySelectedConfigValue->product
+                           << "' is also a candidate");
+                return end;
+            }
+
+            // Select this product.
+            selectedProductIter = iter;
+        }
+
+        if (configValue->product.empty() || configValue->product == "default") {
+            if (defaultProductIter != end) {
+                // We have two possible default values.
+                diag->error(DiagMessage(configValue->value->getSource())
+                            << "multiple default products defined for resource " << name);
+
+                ResourceConfigValue* previouslyDefaultConfigValue = defaultProductIter->get();
+                diag->note(DiagMessage(previouslyDefaultConfigValue->value->getSource())
+                           << "default product also defined here");
+                return end;
+            }
+
+            // Mark the default.
+            defaultProductIter = iter;
+        }
+    }
+
+    if (defaultProductIter == end) {
+        diag->error(DiagMessage() << "no default product defined for resource " << name);
+        return end;
+    }
+
+    if (selectedProductIter == end) {
+        selectedProductIter = defaultProductIter;
+    }
+    return selectedProductIter;
+}
+
+bool ProductFilter::consume(IAaptContext* context, ResourceTable* table) {
+    bool error = false;
+    for (auto& pkg : table->packages) {
+        for (auto& type : pkg->types) {
+            for (auto& entry : type->entries) {
+                std::vector<std::unique_ptr<ResourceConfigValue>> newValues;
+
+                ResourceConfigValueIter iter = entry->values.begin();
+                ResourceConfigValueIter startRangeIter = iter;
+                while (iter != entry->values.end()) {
+                    ++iter;
+                    if (iter == entry->values.end() ||
+                            (*iter)->config != (*startRangeIter)->config) {
+
+                        // End of the array, or we saw a different config,
+                        // so this must be the end of a range of products.
+                        // Select the product to keep from the set of products defined.
+                        ResourceNameRef name(pkg->name, type->type, entry->name);
+                        auto valueToKeep = selectProductToKeep(name, startRangeIter, iter,
+                                                               context->getDiagnostics());
+                        if (valueToKeep == iter) {
+                            // An error occurred, we could not pick a product.
+                            error = true;
+                        } else {
+                            // We selected a product to keep. Move it to the new array.
+                            newValues.push_back(std::move(*valueToKeep));
+                        }
+
+                        // Start the next range of products.
+                        startRangeIter = iter;
+                    }
+                }
+
+                // Now move the new values in to place.
+                entry->values = std::move(newValues);
+            }
+        }
+    }
+    return !error;
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/ProductFilter.h b/tools/aapt2/link/ProductFilter.h
new file mode 100644
index 0000000..d2edd38
--- /dev/null
+++ b/tools/aapt2/link/ProductFilter.h
@@ -0,0 +1,49 @@
+/*
+ * 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_LINK_PRODUCTFILTER_H
+#define AAPT_LINK_PRODUCTFILTER_H
+
+#include "ResourceTable.h"
+#include "process/IResourceTableConsumer.h"
+
+#include <android-base/macros.h>
+#include <unordered_set>
+
+namespace aapt {
+
+class ProductFilter {
+public:
+    using ResourceConfigValueIter = std::vector<std::unique_ptr<ResourceConfigValue>>::iterator;
+
+    ProductFilter(std::unordered_set<std::string> products) : mProducts(products) { }
+
+    ResourceConfigValueIter selectProductToKeep(const ResourceNameRef& name,
+                                                const ResourceConfigValueIter begin,
+                                                const ResourceConfigValueIter end,
+                                                IDiagnostics* diag);
+
+    bool consume(IAaptContext* context, ResourceTable* table);
+
+private:
+    std::unordered_set<std::string> mProducts;
+
+    DISALLOW_COPY_AND_ASSIGN(ProductFilter);
+};
+
+} // namespace aapt
+
+#endif /* AAPT_LINK_PRODUCTFILTER_H */
diff --git a/tools/aapt2/link/ProductFilter_test.cpp b/tools/aapt2/link/ProductFilter_test.cpp
new file mode 100644
index 0000000..f4f756a
--- /dev/null
+++ b/tools/aapt2/link/ProductFilter_test.cpp
@@ -0,0 +1,136 @@
+/*
+ * 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 "link/ProductFilter.h"
+#include "test/Builders.h"
+#include "test/Context.h"
+
+#include <gtest/gtest.h>
+
+namespace aapt {
+
+TEST(ProductFilterTest, SelectTwoProducts) {
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+
+    const ConfigDescription land = test::parseConfigOrDie("land");
+    const ConfigDescription port = test::parseConfigOrDie("port");
+
+    ResourceTable table;
+    ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"),
+                                  land, "",
+                                  test::ValueBuilder<Id>()
+                                          .setSource(Source("land/default.xml")).build(),
+                                  context->getDiagnostics()));
+    ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"),
+                                  land, "tablet",
+                                  test::ValueBuilder<Id>()
+                                          .setSource(Source("land/tablet.xml")).build(),
+                                  context->getDiagnostics()));
+
+    ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"),
+                                  port, "",
+                                  test::ValueBuilder<Id>()
+                                          .setSource(Source("port/default.xml")).build(),
+                                  context->getDiagnostics()));
+    ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"),
+                                  port, "tablet",
+                                  test::ValueBuilder<Id>()
+                                          .setSource(Source("port/tablet.xml")).build(),
+                                  context->getDiagnostics()));
+
+    ProductFilter filter({ "tablet" });
+    ASSERT_TRUE(filter.consume(context.get(), &table));
+
+    EXPECT_EQ(nullptr, test::getValueForConfigAndProduct<Id>(&table, u"@android:string/one",
+                                                             land, ""));
+    EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, u"@android:string/one",
+                                                             land, "tablet"));
+    EXPECT_EQ(nullptr, test::getValueForConfigAndProduct<Id>(&table, u"@android:string/one",
+                                                             port, ""));
+    EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, u"@android:string/one",
+                                                             port, "tablet"));
+}
+
+TEST(ProductFilterTest, SelectDefaultProduct) {
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+
+    ResourceTable table;
+    ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"),
+                                  ConfigDescription::defaultConfig(), "",
+                                  test::ValueBuilder<Id>()
+                                          .setSource(Source("default.xml")).build(),
+                                  context->getDiagnostics()));
+    ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"),
+                                  ConfigDescription::defaultConfig(), "tablet",
+                                  test::ValueBuilder<Id>()
+                                          .setSource(Source("tablet.xml")).build(),
+                                  context->getDiagnostics()));
+
+    ProductFilter filter({});
+    ASSERT_TRUE(filter.consume(context.get(), &table));
+
+    EXPECT_NE(nullptr, test::getValueForConfigAndProduct<Id>(&table, u"@android:string/one",
+                                                             ConfigDescription::defaultConfig(),
+                                                             ""));
+    EXPECT_EQ(nullptr, test::getValueForConfigAndProduct<Id>(&table, u"@android:string/one",
+                                                             ConfigDescription::defaultConfig(),
+                                                             "tablet"));
+}
+
+TEST(ProductFilterTest, FailOnAmbiguousProduct) {
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+
+    ResourceTable table;
+    ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"),
+                                  ConfigDescription::defaultConfig(), "",
+                                  test::ValueBuilder<Id>()
+                                          .setSource(Source("default.xml")).build(),
+                                  context->getDiagnostics()));
+    ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"),
+                                  ConfigDescription::defaultConfig(), "tablet",
+                                  test::ValueBuilder<Id>()
+                                          .setSource(Source("tablet.xml")).build(),
+                                  context->getDiagnostics()));
+    ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"),
+                                  ConfigDescription::defaultConfig(), "no-sdcard",
+                                  test::ValueBuilder<Id>()
+                                          .setSource(Source("no-sdcard.xml")).build(),
+                                  context->getDiagnostics()));
+
+    ProductFilter filter({ "tablet", "no-sdcard" });
+    ASSERT_FALSE(filter.consume(context.get(), &table));
+}
+
+TEST(ProductFilterTest, FailOnMultipleDefaults) {
+    std::unique_ptr<IAaptContext> context = test::ContextBuilder().build();
+
+    ResourceTable table;
+    ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"),
+                                  ConfigDescription::defaultConfig(), "",
+                                  test::ValueBuilder<Id>()
+                                          .setSource(Source(".xml")).build(),
+                                  context->getDiagnostics()));
+    ASSERT_TRUE(table.addResource(test::parseNameOrDie(u"@android:string/one"),
+                                  ConfigDescription::defaultConfig(), "default",
+                                  test::ValueBuilder<Id>()
+                                          .setSource(Source("default.xml")).build(),
+                                  context->getDiagnostics()));
+
+    ProductFilter filter({});
+    ASSERT_FALSE(filter.consume(context.get(), &table));
+}
+
+} // namespace aapt
diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp
index 2743539..ef3fe4f 100644
--- a/tools/aapt2/link/ReferenceLinker.cpp
+++ b/tools/aapt2/link/ReferenceLinker.cpp
@@ -316,7 +316,7 @@
                                                &table->stringPool, &declStack, &callSite);
 
                 for (auto& configValue : entry->values) {
-                    configValue.value->accept(&visitor);
+                    configValue->value->accept(&visitor);
                 }
 
                 if (visitor.hasError()) {
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index e01a004..2ecd5b0 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -18,9 +18,7 @@
 #include "ResourceUtils.h"
 #include "ResourceValues.h"
 #include "ValueVisitor.h"
-
 #include "link/TableMerger.h"
-#include "util/Comparators.h"
 #include "util/Util.h"
 
 #include <cassert>
@@ -197,28 +195,28 @@
 
             ResourceNameRef resName(mMasterPackage->name, dstType->type, dstEntry->name);
 
-            for (ResourceConfigValue& srcValue : srcEntry->values) {
-                auto iter = std::lower_bound(dstEntry->values.begin(), dstEntry->values.end(),
-                                             srcValue.config, cmp::lessThanConfig);
+            for (auto& srcValue : srcEntry->values) {
+                ResourceConfigValue* dstValue = dstEntry->findValue(srcValue->config,
+                                                                    srcValue->product);
 
                 const bool stripConfig = mOptions.filter ?
-                        !mOptions.filter->match(srcValue.config) : false;
+                        !mOptions.filter->match(srcValue->config) : false;
 
-                if (iter != dstEntry->values.end() && iter->config == srcValue.config) {
+                if (dstValue) {
                     const int collisionResult = ResourceTable::resolveValueCollision(
-                            iter->value.get(), srcValue.value.get());
+                            dstValue->value.get(), srcValue->value.get());
                     if (collisionResult == 0 && !overlay) {
                         // Error!
                         ResourceNameRef resourceName(srcPackage->name,
                                                      srcType->type,
                                                      srcEntry->name);
 
-                        mContext->getDiagnostics()->error(DiagMessage(srcValue.value->getSource())
+                        mContext->getDiagnostics()->error(DiagMessage(srcValue->value->getSource())
                                                           << "resource '" << resourceName
                                                           << "' has a conflicting value for "
                                                           << "configuration ("
-                                                          << srcValue.config << ")");
-                        mContext->getDiagnostics()->note(DiagMessage(iter->value->getSource())
+                                                          << srcValue->config << ")");
+                        mContext->getDiagnostics()->note(DiagMessage(dstValue->value->getSource())
                                                          << "originally defined here");
                         error = true;
                         continue;
@@ -227,16 +225,18 @@
                         continue;
                     }
 
-                } 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())) {
+                if (!dstValue) {
+                    // Force create the entry if we didn't have it.
+                    dstValue = dstEntry->findOrCreateValue(srcValue->config, srcValue->product);
+                }
+
+                if (FileReference* f = valueCast<FileReference>(srcValue->value.get())) {
                     std::unique_ptr<FileReference> newFileRef;
                     if (manglePackage) {
                         newFileRef = cloneAndMangleFile(srcPackage->name, *f);
@@ -246,15 +246,15 @@
                     }
 
                     if (callback) {
-                        if (!callback(resName, iter->config, newFileRef.get(), f)) {
+                        if (!callback(resName, srcValue->config, newFileRef.get(), f)) {
                             error = true;
                             continue;
                         }
                     }
-                    iter->value = std::move(newFileRef);
+                    dstValue->value = std::move(newFileRef);
 
                 } else {
-                    iter->value = std::unique_ptr<Value>(srcValue.value->clone(
+                    dstValue->value = std::unique_ptr<Value>(srcValue->value->clone(
                             &mMasterTable->stringPool));
                 }
             }
@@ -290,7 +290,8 @@
     ResourceTablePackage* pkg = table.createPackage(fileDesc.name.package, 0x0);
     pkg->findOrCreateType(fileDesc.name.type)
             ->findOrCreateEntry(fileDesc.name.entry)
-            ->values.push_back(ResourceConfigValue{ fileDesc.config, std::move(fileRef) });
+            ->findOrCreateValue(fileDesc.config, {})
+            ->value = std::move(fileRef);
 
     auto callback = [&](const ResourceNameRef& name, const ConfigDescription& config,
                        FileReference* newFile, FileReference* oldFile) -> bool {
diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp
index 6ad2f9c..b6030a2 100644
--- a/tools/aapt2/process/SymbolTable.cpp
+++ b/tools/aapt2/process/SymbolTable.cpp
@@ -17,9 +17,7 @@
 #include "ConfigDescription.h"
 #include "Resource.h"
 #include "ValueVisitor.h"
-
 #include "process/SymbolTable.h"
-#include "util/Comparators.h"
 #include "util/Util.h"
 
 #include <androidfw/AssetManager.h>
@@ -55,12 +53,10 @@
 
     if (name.type == ResourceType::kAttr || name.type == ResourceType::kAttrPrivate) {
         const ConfigDescription kDefaultConfig;
-        auto iter = std::lower_bound(sr.entry->values.begin(), sr.entry->values.end(),
-                                     kDefaultConfig, cmp::lessThanConfig);
-
-        if (iter != sr.entry->values.end() && iter->config == kDefaultConfig) {
+        ResourceConfigValue* configValue = sr.entry->findValue(kDefaultConfig);
+        if (configValue) {
             // This resource has an Attribute.
-            if (Attribute* attr = valueCast<Attribute>(iter->value.get())) {
+            if (Attribute* attr = valueCast<Attribute>(configValue->value.get())) {
                 symbol->attribute = util::make_unique<Attribute>(*attr);
             } else {
                 return {};
diff --git a/tools/aapt2/proto/TableProtoDeserializer.cpp b/tools/aapt2/proto/TableProtoDeserializer.cpp
index 1310aa6..9856a00 100644
--- a/tools/aapt2/proto/TableProtoDeserializer.cpp
+++ b/tools/aapt2/proto/TableProtoDeserializer.cpp
@@ -19,7 +19,6 @@
 #include "ValueVisitor.h"
 #include "proto/ProtoHelpers.h"
 #include "proto/ProtoSerialize.h"
-#include "util/Comparators.h"
 
 #include <androidfw/ResourceTypes.h>
 
@@ -134,21 +133,19 @@
                         return {};
                     }
 
-                    auto iter = std::lower_bound(entry->values.begin(), entry->values.end(),
-                                                 config, cmp::lessThanConfig);
-                    if (iter != entry->values.end() && iter->config == config) {
+                    ResourceConfigValue* configValue = entry->findOrCreateValue(config,
+                                                                                pbConfig.product());
+                    if (configValue->value) {
                         // Duplicate config.
                         mDiag->error(DiagMessage(mSource) << "duplicate configuration");
                         return {};
                     }
 
-                    std::unique_ptr<Value> value = deserializeValueFromPb(pbConfigValue.value(),
-                                                                          config,
-                                                                          &table->stringPool);
-                    if (!value) {
+                    configValue->value = deserializeValueFromPb(pbConfigValue.value(),
+                                                                config, &table->stringPool);
+                    if (!configValue->value) {
                         return {};
                     }
-                    entry->values.insert(iter, ResourceConfigValue{ config, std::move(value) });
                 }
             }
         }
diff --git a/tools/aapt2/proto/TableProtoSerializer.cpp b/tools/aapt2/proto/TableProtoSerializer.cpp
index 4a2176d..bba2da4 100644
--- a/tools/aapt2/proto/TableProtoSerializer.cpp
+++ b/tools/aapt2/proto/TableProtoSerializer.cpp
@@ -242,21 +242,24 @@
 
                 for (auto& configValue : entry->values) {
                     pb::ConfigValue* pbConfigValue = pbEntry->add_config_values();
-                    serializeConfig(configValue.config, pbConfigValue->mutable_config());
-
-                    pb::Value* pbValue = pbConfigValue->mutable_value();
-                    serializeSourceToPb(configValue.value->getSource(), &sourcePool,
-                                        pbValue->mutable_source());
-                    if (!configValue.value->getComment().empty()) {
-                        pbValue->set_comment(util::utf16ToUtf8(configValue.value->getComment()));
+                    serializeConfig(configValue->config, pbConfigValue->mutable_config());
+                    if (!configValue->product.empty()) {
+                        pbConfigValue->mutable_config()->set_product(configValue->product);
                     }
 
-                    if (configValue.value->isWeak()) {
+                    pb::Value* pbValue = pbConfigValue->mutable_value();
+                    serializeSourceToPb(configValue->value->getSource(), &sourcePool,
+                                        pbValue->mutable_source());
+                    if (!configValue->value->getComment().empty()) {
+                        pbValue->set_comment(util::utf16ToUtf8(configValue->value->getComment()));
+                    }
+
+                    if (configValue->value->isWeak()) {
                         pbValue->set_weak(true);
                     }
 
                     PbSerializerVisitor visitor(&sourcePool, &symbolPool, pbValue);
-                    configValue.value->accept(&visitor);
+                    configValue->value->accept(&visitor);
                 }
             }
         }
diff --git a/tools/aapt2/proto/TableProtoSerializer_test.cpp b/tools/aapt2/proto/TableProtoSerializer_test.cpp
index 1061b8f..70a33f7 100644
--- a/tools/aapt2/proto/TableProtoSerializer_test.cpp
+++ b/tools/aapt2/proto/TableProtoSerializer_test.cpp
@@ -49,9 +49,19 @@
     std::unique_ptr<Plural> plural = util::make_unique<Plural>();
     plural->values[Plural::One] = util::make_unique<String>(table->stringPool.makeRef(u"one"));
     ASSERT_TRUE(table->addResource(test::parseNameOrDie(u"@com.app.a:plurals/hey"),
-                                   ConfigDescription{}, std::move(plural),
+                                   ConfigDescription{}, std::string(), std::move(plural),
                                    context->getDiagnostics()));
 
+    // Make a resource with different products.
+    ASSERT_TRUE(table->addResource(test::parseNameOrDie(u"@com.app.a:integer/one"),
+                                   test::parseConfigOrDie("land"), std::string(),
+                                   test::buildPrimitive(android::Res_value::TYPE_INT_DEC, 123u),
+                                   context->getDiagnostics()));
+    ASSERT_TRUE(table->addResource(test::parseNameOrDie(u"@com.app.a:integer/one"),
+                                       test::parseConfigOrDie("land"), std::string("tablet"),
+                                       test::buildPrimitive(android::Res_value::TYPE_INT_DEC, 321u),
+                                       context->getDiagnostics()));
+
     std::unique_ptr<pb::ResourceTable> pbTable = serializeTableToPb(table.get());
     ASSERT_NE(nullptr, pbTable);
 
@@ -69,6 +79,17 @@
     AAPT_ASSERT_TRUE(result);
     EXPECT_EQ(SymbolState::kPublic, result.value().type->symbolStatus.state);
     EXPECT_EQ(SymbolState::kPublic, result.value().entry->symbolStatus.state);
+
+    // Find the product-dependent values
+    BinaryPrimitive* prim = test::getValueForConfigAndProduct<BinaryPrimitive>(
+            newTable.get(), u"@com.app.a:integer/one", test::parseConfigOrDie("land"), "");
+    ASSERT_NE(nullptr, prim);
+    EXPECT_EQ(123u, prim->value.data);
+
+    prim = test::getValueForConfigAndProduct<BinaryPrimitive>(
+            newTable.get(), u"@com.app.a:integer/one", test::parseConfigOrDie("land"), "tablet");
+    ASSERT_NE(nullptr, prim);
+    EXPECT_EQ(321u, prim->value.data);
 }
 
 TEST(TableProtoSerializer, SerializeFileHeader) {
diff --git a/tools/aapt2/test/Builders.h b/tools/aapt2/test/Builders.h
index 579a46e..834caf8 100644
--- a/tools/aapt2/test/Builders.h
+++ b/tools/aapt2/test/Builders.h
@@ -104,8 +104,8 @@
                                    const ConfigDescription& config,
                                    std::unique_ptr<Value> value) {
         ResourceName resName = parseNameOrDie(name);
-        bool result = mTable->addResourceAllowMangled(resName, id, config, std::move(value),
-                                                      &mDiagnostics);
+        bool result = mTable->addResourceAllowMangled(resName, id, config, std::string(),
+                                                      std::move(value), &mDiagnostics);
         assert(result);
         return *this;
     }
@@ -132,6 +132,14 @@
     return reference;
 }
 
+inline std::unique_ptr<BinaryPrimitive> buildPrimitive(uint8_t type, uint32_t data) {
+    android::Res_value value = {};
+    value.size = sizeof(value);
+    value.dataType = type;
+    value.data = data;
+    return util::make_unique<BinaryPrimitive>(value);
+}
+
 template <typename T>
 class ValueBuilder {
 private:
diff --git a/tools/aapt2/test/Common.h b/tools/aapt2/test/Common.h
index 51e2dd4..348c32a 100644
--- a/tools/aapt2/test/Common.h
+++ b/tools/aapt2/test/Common.h
@@ -52,6 +52,11 @@
     void note(const DiagMessage& message) override {}
 };
 
+inline IDiagnostics* getDiagnostics() {
+    static DummyDiagnosticsImpl diag;
+    return &diag;
+}
+
 inline ResourceName parseNameOrDie(const StringPiece16& str) {
     ResourceNameRef ref;
     bool result = ResourceUtils::tryParseReference(str, &ref);
@@ -66,23 +71,25 @@
     return config;
 }
 
-template <typename T> T* getValueForConfig(ResourceTable* table, const StringPiece16& resName,
-                                           const ConfigDescription& config) {
+template <typename T> T* getValueForConfigAndProduct(ResourceTable* table,
+                                                     const StringPiece16& resName,
+                                                     const ConfigDescription& config,
+                                                     const StringPiece& product) {
     Maybe<ResourceTable::SearchResult> result = table->findResource(parseNameOrDie(resName));
     if (result) {
-        ResourceEntry* entry = result.value().entry;
-        auto iter = std::lower_bound(entry->values.begin(), entry->values.end(), config,
-                                     [](const ResourceConfigValue& a, const ConfigDescription& b)
-                                             -> bool {
-                                         return a.config < b;
-                                     });
-        if (iter != entry->values.end() && iter->config == config) {
-            return valueCast<T>(iter->value.get());
+        ResourceConfigValue* configValue = result.value().entry->findValue(config, product);
+        if (configValue) {
+            return valueCast<T>(configValue->value.get());
         }
     }
     return nullptr;
 }
 
+template <typename T> T* getValueForConfig(ResourceTable* table, const StringPiece16& resName,
+                                           const ConfigDescription& config) {
+    return getValueForConfigAndProduct<T>(table, resName, config, {});
+}
+
 template <typename T> T* getValue(ResourceTable* table, const StringPiece16& resName) {
     return getValueForConfig<T>(table, resName, {});
 }
diff --git a/tools/aapt2/unflatten/BinaryResourceParser.cpp b/tools/aapt2/unflatten/BinaryResourceParser.cpp
index 3417703..33b505e 100644
--- a/tools/aapt2/unflatten/BinaryResourceParser.cpp
+++ b/tools/aapt2/unflatten/BinaryResourceParser.cpp
@@ -352,7 +352,7 @@
             return false;
         }
 
-        if (!mTable->addResourceAllowMangled(name, config, std::move(resourceValue),
+        if (!mTable->addResourceAllowMangled(name, config, {}, std::move(resourceValue),
                                              mContext->getDiagnostics())) {
             return false;
         }
diff --git a/tools/aapt2/util/Comparators.h b/tools/aapt2/util/Comparators.h
deleted file mode 100644
index 0ee0bf3..0000000
--- a/tools/aapt2/util/Comparators.h
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-#ifndef AAPT_UTIL_COMPARATORS_H
-#define AAPT_UTIL_COMPARATORS_H
-
-#include "ConfigDescription.h"
-#include "ResourceTable.h"
-
-namespace aapt {
-namespace cmp {
-
-inline bool lessThanConfig(const ResourceConfigValue& a, const ConfigDescription& b) {
-    return a.config < b;
-}
-
-inline bool lessThanType(const std::unique_ptr<ResourceTableType>& a, ResourceType b) {
-    return a->type < b;
-}
-
-} // namespace cmp
-} // namespace aapt
-
-#endif /* AAPT_UTIL_COMPARATORS_H */