Implement filter-product
filter-product option is added to aapt2 compile, which filters values
with a given product and removes all other values (including default).
The motivation of this change is to generate product-specific RRO
automatically, making a main resource APK (e.g. framework-res.apk)
identical in all targets.
Bug: 294799593
Test: build
Change-Id: I42eb3e134c7aa120f6bbe2d26d311bd46b586595
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp
index 977b276..fff8f78 100644
--- a/tools/aapt2/Android.bp
+++ b/tools/aapt2/Android.bp
@@ -122,7 +122,6 @@
"link/AutoVersioner.cpp",
"link/ManifestFixer.cpp",
"link/NoDefaultResourceRemover.cpp",
- "link/ProductFilter.cpp",
"link/PrivateAttributeMover.cpp",
"link/ReferenceLinker.cpp",
"link/ResourceExcluder.cpp",
@@ -135,6 +134,7 @@
"optimize/ResourceFilter.cpp",
"optimize/Obfuscator.cpp",
"optimize/VersionCollapser.cpp",
+ "process/ProductFilter.cpp",
"process/SymbolTable.cpp",
"split/TableSplitter.cpp",
"text/Printer.cpp",
diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp
index b5c290e..728ba8a 100644
--- a/tools/aapt2/cmd/Compile.cpp
+++ b/tools/aapt2/cmd/Compile.cpp
@@ -45,6 +45,7 @@
#include "io/StringStream.h"
#include "io/Util.h"
#include "io/ZipArchive.h"
+#include "process/ProductFilter.h"
#include "trace/TraceBuffer.h"
#include "util/Files.h"
#include "util/Util.h"
@@ -179,6 +180,15 @@
if (!res_parser.Parse(&xml_parser)) {
return false;
}
+
+ if (options.product_.has_value()) {
+ if (!ProductFilter({*options.product_}, /* remove_default_config_values = */ true)
+ .Consume(context, &table)) {
+ context->GetDiagnostics()->Error(android::DiagMessage(path_data.source)
+ << "failed to filter product");
+ return false;
+ }
+ }
}
if (options.pseudolocalize && translatable_file) {
diff --git a/tools/aapt2/cmd/Compile.h b/tools/aapt2/cmd/Compile.h
index 22890fc..61c5b60 100644
--- a/tools/aapt2/cmd/Compile.h
+++ b/tools/aapt2/cmd/Compile.h
@@ -44,6 +44,7 @@
// See comments on aapt::ResourceParserOptions.
bool preserve_visibility_of_styleables = false;
bool verbose = false;
+ std::optional<std::string> product_;
};
/** Parses flags and compiles resources to be used in linking. */
@@ -87,6 +88,10 @@
"Sets the ratio of resources to generate grammatical gender strings for. The "
"ratio has to be a float number between 0 and 1.",
&options_.pseudo_localize_gender_ratio);
+ AddOptionalFlag("--filter-product",
+ "Leave only resources specific to the given product. All "
+ "other resources (including defaults) are removed.",
+ &options_.product_);
}
int Action(const std::vector<std::string>& args) override;
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index eb4e38c..159c6fd 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -66,6 +66,7 @@
#include "optimize/ResourceDeduper.h"
#include "optimize/VersionCollapser.h"
#include "process/IResourceTableConsumer.h"
+#include "process/ProductFilter.h"
#include "process/SymbolTable.h"
#include "split/TableSplitter.h"
#include "trace/TraceBuffer.h"
@@ -2128,7 +2129,7 @@
<< "can't select products when building static library");
}
} else {
- ProductFilter product_filter(options_.products);
+ ProductFilter product_filter(options_.products, /* remove_default_config_values = */ false);
if (!product_filter.Consume(context_, &final_table_)) {
context_->GetDiagnostics()->Error(android::DiagMessage() << "failed stripping products");
return 1;
diff --git a/tools/aapt2/link/Linkers.h b/tools/aapt2/link/Linkers.h
index 44cd276..18165f7 100644
--- a/tools/aapt2/link/Linkers.h
+++ b/tools/aapt2/link/Linkers.h
@@ -20,12 +20,12 @@
#include <set>
#include <unordered_set>
-#include "android-base/macros.h"
-#include "androidfw/ConfigDescription.h"
-#include "androidfw/StringPiece.h"
-
#include "Resource.h"
#include "SdkConstants.h"
+#include "android-base/macros.h"
+#include "android-base/result.h"
+#include "androidfw/ConfigDescription.h"
+#include "androidfw/StringPiece.h"
#include "process/IResourceTableConsumer.h"
#include "xml/XmlDom.h"
@@ -92,28 +92,6 @@
DISALLOW_COPY_AND_ASSIGN(PrivateAttributeMover);
};
-class ResourceConfigValue;
-
-class ProductFilter : public IResourceTableConsumer {
- public:
- using ResourceConfigValueIter = std::vector<std::unique_ptr<ResourceConfigValue>>::iterator;
-
- explicit ProductFilter(std::unordered_set<std::string> products) : products_(products) {
- }
-
- ResourceConfigValueIter SelectProductToKeep(const ResourceNameRef& name,
- const ResourceConfigValueIter begin,
- const ResourceConfigValueIter end,
- android::IDiagnostics* diag);
-
- bool Consume(IAaptContext* context, ResourceTable* table) override;
-
- private:
- DISALLOW_COPY_AND_ASSIGN(ProductFilter);
-
- std::unordered_set<std::string> products_;
-};
-
// Removes namespace nodes and URI information from the XmlResource.
//
// Once an XmlResource is processed by this consumer, it is no longer able to have its attributes
diff --git a/tools/aapt2/link/ProductFilter_test.cpp b/tools/aapt2/link/ProductFilter_test.cpp
deleted file mode 100644
index 2cb9afa..0000000
--- a/tools/aapt2/link/ProductFilter_test.cpp
+++ /dev/null
@@ -1,151 +0,0 @@
-/*
- * 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/Linkers.h"
-
-#include "test/Test.h"
-
-using ::android::ConfigDescription;
-
-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(
- NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
- .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("land/default.xml")).Build(),
- land)
- .Build(),
- context->GetDiagnostics()));
-
- ASSERT_TRUE(table.AddResource(
- NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
- .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("land/tablet.xml")).Build(),
- land, "tablet")
- .Build(),
- context->GetDiagnostics()));
-
- ASSERT_TRUE(table.AddResource(
- NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
- .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("port/default.xml")).Build(),
- port)
- .Build(),
- context->GetDiagnostics()));
-
- ASSERT_TRUE(table.AddResource(
- NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
- .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("port/tablet.xml")).Build(),
- port, "tablet")
- .Build(),
- context->GetDiagnostics()));
-
- ProductFilter filter({"tablet"});
- ASSERT_TRUE(filter.Consume(context.get(), &table));
-
- EXPECT_EQ(nullptr, test::GetValueForConfigAndProduct<Id>(
- &table, "android:string/one", land, ""));
- EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<Id>(
- &table, "android:string/one", land, "tablet"));
- EXPECT_EQ(nullptr, test::GetValueForConfigAndProduct<Id>(
- &table, "android:string/one", port, ""));
- EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<Id>(
- &table, "android:string/one", port, "tablet"));
-}
-
-TEST(ProductFilterTest, SelectDefaultProduct) {
- std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
-
- ResourceTable table;
- ASSERT_TRUE(table.AddResource(
- NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
- .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("default.xml")).Build())
- .Build(),
- context->GetDiagnostics()));
-
- ASSERT_TRUE(table.AddResource(
- NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
- .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("tablet.xml")).Build(), {},
- "tablet")
- .Build(),
- context->GetDiagnostics()));
- ;
-
- ProductFilter filter(std::unordered_set<std::string>{});
- ASSERT_TRUE(filter.Consume(context.get(), &table));
-
- EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<Id>(
- &table, "android:string/one",
- ConfigDescription::DefaultConfig(), ""));
- EXPECT_EQ(nullptr, test::GetValueForConfigAndProduct<Id>(
- &table, "android:string/one",
- ConfigDescription::DefaultConfig(), "tablet"));
-}
-
-TEST(ProductFilterTest, FailOnAmbiguousProduct) {
- std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
-
- ResourceTable table;
- ASSERT_TRUE(table.AddResource(
- NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
- .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("default.xml")).Build())
- .Build(),
- context->GetDiagnostics()));
-
- ASSERT_TRUE(table.AddResource(
- NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
- .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("tablet.xml")).Build(), {},
- "tablet")
- .Build(),
- context->GetDiagnostics()));
-
- ASSERT_TRUE(table.AddResource(
- NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
- .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("no-sdcard.xml")).Build(),
- {}, "no-sdcard")
- .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(
- NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
- .SetValue(test::ValueBuilder<Id>().SetSource(android::Source(".xml")).Build())
- .Build(),
- context->GetDiagnostics()));
-
- ASSERT_TRUE(table.AddResource(
- NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
- .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("default.xml")).Build(), {},
- "default")
- .Build(),
- context->GetDiagnostics()));
-
- ProductFilter filter(std::unordered_set<std::string>{});
- ASSERT_FALSE(filter.Consume(context.get(), &table));
-}
-
-} // namespace aapt
diff --git a/tools/aapt2/link/ProductFilter.cpp b/tools/aapt2/process/ProductFilter.cpp
similarity index 60%
rename from tools/aapt2/link/ProductFilter.cpp
rename to tools/aapt2/process/ProductFilter.cpp
index 9544986..0b1c0a6 100644
--- a/tools/aapt2/link/ProductFilter.cpp
+++ b/tools/aapt2/process/ProductFilter.cpp
@@ -14,16 +14,18 @@
* limitations under the License.
*/
-#include "link/Linkers.h"
+#include "process/ProductFilter.h"
+
+#include <algorithm>
#include "ResourceTable.h"
#include "trace/TraceBuffer.h"
namespace aapt {
-ProductFilter::ResourceConfigValueIter ProductFilter::SelectProductToKeep(
- const ResourceNameRef& name, const ResourceConfigValueIter begin,
- const ResourceConfigValueIter end, android::IDiagnostics* diag) {
+std::optional<ProductFilter::ResourceConfigValueIter> ProductFilter::SelectProductToKeep(
+ const ResourceNameRef& name, ResourceConfigValueIter begin, ResourceConfigValueIter end,
+ android::IDiagnostics* diag) {
ResourceConfigValueIter default_product_iter = end;
ResourceConfigValueIter selected_product_iter = end;
@@ -36,12 +38,11 @@
<< "selection of product '" << config_value->product << "' for resource "
<< name << " is ambiguous");
- ResourceConfigValue* previously_selected_config_value =
- selected_product_iter->get();
+ ResourceConfigValue* previously_selected_config_value = selected_product_iter->get();
diag->Note(android::DiagMessage(previously_selected_config_value->value->GetSource())
<< "product '" << previously_selected_config_value->product
<< "' is also a candidate");
- return end;
+ return std::nullopt;
}
// Select this product.
@@ -54,11 +55,10 @@
diag->Error(android::DiagMessage(config_value->value->GetSource())
<< "multiple default products defined for resource " << name);
- ResourceConfigValue* previously_default_config_value =
- default_product_iter->get();
+ ResourceConfigValue* previously_default_config_value = default_product_iter->get();
diag->Note(android::DiagMessage(previously_default_config_value->value->GetSource())
<< "default product also defined here");
- return end;
+ return std::nullopt;
}
// Mark the default.
@@ -66,9 +66,16 @@
}
}
+ if (remove_default_config_values_) {
+ // If we are leaving only a specific product, return early here instead of selecting the default
+ // value. Returning end here will cause this value set to be skipped, and will be removed with
+ // ClearEmptyValues method.
+ return selected_product_iter;
+ }
+
if (default_product_iter == end) {
diag->Error(android::DiagMessage() << "no default product defined for resource " << name);
- return end;
+ return std::nullopt;
}
if (selected_product_iter == end) {
@@ -89,20 +96,27 @@
ResourceConfigValueIter start_range_iter = iter;
while (iter != entry->values.end()) {
++iter;
- if (iter == entry->values.end() ||
- (*iter)->config != (*start_range_iter)->config) {
+ if (iter == entry->values.end() || (*iter)->config != (*start_range_iter)->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->named_type, entry->name);
- auto value_to_keep = SelectProductToKeep(
- name, start_range_iter, iter, context->GetDiagnostics());
- if (value_to_keep == iter) {
+ auto value_to_keep =
+ SelectProductToKeep(name, start_range_iter, iter, context->GetDiagnostics());
+ if (!value_to_keep.has_value()) {
// An error occurred, we could not pick a product.
error = true;
- } else {
+ } else if (auto val = value_to_keep.value(); val != iter) {
// We selected a product to keep. Move it to the new array.
- new_values.push_back(std::move(*value_to_keep));
+ if (remove_default_config_values_) {
+ // We are filtering values with the given product. The selected value here will be
+ // a new default value, and all other values will be removed.
+ new_values.push_back(
+ std::make_unique<ResourceConfigValue>((*val)->config, android::StringPiece{}));
+ new_values.back()->value = std::move((*val)->value);
+ } else {
+ new_values.push_back(std::move(*val));
+ }
}
// Start the next range of products.
@@ -115,7 +129,27 @@
}
}
}
+
+ if (remove_default_config_values_) {
+ ClearEmptyValues(table);
+ }
+
return !error;
}
+void ProductFilter::ClearEmptyValues(ResourceTable* table) {
+ // Clear any empty packages/types/entries, as remove_default_config_values_ may remove an entire
+ // value set.
+ CHECK(remove_default_config_values_)
+ << __func__ << " should only be called when remove_default_config_values_ is set";
+
+ for (auto& pkg : table->packages) {
+ for (auto& type : pkg->types) {
+ std::erase_if(type->entries, [](auto& entry) { return entry->values.empty(); });
+ }
+ std::erase_if(pkg->types, [](auto& type) { return type->entries.empty(); });
+ }
+ std::erase_if(table->packages, [](auto& package) { return package->types.empty(); });
+}
+
} // namespace aapt
diff --git a/tools/aapt2/process/ProductFilter.h b/tools/aapt2/process/ProductFilter.h
new file mode 100644
index 0000000..0ec2f00
--- /dev/null
+++ b/tools/aapt2/process/ProductFilter.h
@@ -0,0 +1,65 @@
+#pragma once
+
+#include <memory>
+#include <optional>
+#include <string>
+#include <unordered_set>
+#include <utility>
+#include <vector>
+
+#include "Resource.h"
+#include "android-base/macros.h"
+#include "androidfw/ConfigDescription.h"
+#include "androidfw/IDiagnostics.h"
+#include "process/IResourceTableConsumer.h"
+
+namespace aapt {
+
+class ResourceConfigValue;
+
+class ProductFilter : public IResourceTableConsumer {
+ public:
+ using ResourceConfigValueIter = std::vector<std::unique_ptr<ResourceConfigValue>>::iterator;
+
+ // Setting remove_default_config_values will remove all values other than
+ // specified product, including default. For example, if the following table
+ //
+ // <string name="foo" product="default">foo_default</string>
+ // <string name="foo" product="tablet">foo_tablet</string>
+ // <string name="bar">bar</string>
+ //
+ // is consumed with tablet, it will result in
+ //
+ // <string name="foo">foo_tablet</string>
+ //
+ // removing foo_default and bar. This option is to generate an RRO package
+ // with given product.
+ explicit ProductFilter(std::unordered_set<std::string> products,
+ bool remove_default_config_values)
+ : products_(std::move(products)),
+ remove_default_config_values_(remove_default_config_values) {
+ }
+
+ bool Consume(IAaptContext* context, ResourceTable* table) override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ProductFilter);
+
+ // SelectProductToKeep returns an iterator for the selected value.
+ //
+ // Returns std::nullopt in case of failure (e.g. ambiguous values, missing or duplicated default
+ // values).
+ // Returns `end` if keep_as_default_product is set and no value for the specified product was
+ // found.
+ std::optional<ResourceConfigValueIter> SelectProductToKeep(const ResourceNameRef& name,
+ ResourceConfigValueIter begin,
+ ResourceConfigValueIter end,
+ android::IDiagnostics* diag);
+
+ void ClearEmptyValues(ResourceTable* table);
+
+ std::unordered_set<std::string> products_;
+ bool remove_default_config_values_;
+};
+
+} // namespace aapt
diff --git a/tools/aapt2/process/ProductFilter_test.cpp b/tools/aapt2/process/ProductFilter_test.cpp
new file mode 100644
index 0000000..27a82dc
--- /dev/null
+++ b/tools/aapt2/process/ProductFilter_test.cpp
@@ -0,0 +1,208 @@
+/*
+ * 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 "process/ProductFilter.h"
+
+#include "test/Test.h"
+
+using ::android::ConfigDescription;
+
+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(
+ NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
+ .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("land/default.xml")).Build(),
+ land)
+ .Build(),
+ context->GetDiagnostics()));
+
+ ASSERT_TRUE(table.AddResource(
+ NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
+ .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("land/tablet.xml")).Build(),
+ land, "tablet")
+ .Build(),
+ context->GetDiagnostics()));
+
+ ASSERT_TRUE(table.AddResource(
+ NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
+ .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("port/default.xml")).Build(),
+ port)
+ .Build(),
+ context->GetDiagnostics()));
+
+ ASSERT_TRUE(table.AddResource(
+ NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
+ .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("port/tablet.xml")).Build(),
+ port, "tablet")
+ .Build(),
+ context->GetDiagnostics()));
+
+ ProductFilter filter({"tablet"}, /* remove_default_config_values = */ false);
+ ASSERT_TRUE(filter.Consume(context.get(), &table));
+
+ EXPECT_EQ(nullptr, test::GetValueForConfigAndProduct<Id>(&table, "android:string/one", land, ""));
+ EXPECT_NE(nullptr,
+ test::GetValueForConfigAndProduct<Id>(&table, "android:string/one", land, "tablet"));
+ EXPECT_EQ(nullptr, test::GetValueForConfigAndProduct<Id>(&table, "android:string/one", port, ""));
+ EXPECT_NE(nullptr,
+ test::GetValueForConfigAndProduct<Id>(&table, "android:string/one", port, "tablet"));
+}
+
+TEST(ProductFilterTest, SelectDefaultProduct) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+
+ ResourceTable table;
+ ASSERT_TRUE(table.AddResource(
+ NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
+ .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("default.xml")).Build())
+ .Build(),
+ context->GetDiagnostics()));
+
+ ASSERT_TRUE(table.AddResource(
+ NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
+ .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("tablet.xml")).Build(), {},
+ "tablet")
+ .Build(),
+ context->GetDiagnostics()));
+ ;
+
+ ProductFilter filter(std::unordered_set<std::string>{},
+ /* remove_default_config_values = */ false);
+ ASSERT_TRUE(filter.Consume(context.get(), &table));
+
+ EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<Id>(&table, "android:string/one",
+ ConfigDescription::DefaultConfig(), ""));
+ EXPECT_EQ(nullptr,
+ test::GetValueForConfigAndProduct<Id>(&table, "android:string/one",
+ ConfigDescription::DefaultConfig(), "tablet"));
+}
+
+TEST(ProductFilterTest, FailOnAmbiguousProduct) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+
+ ResourceTable table;
+ ASSERT_TRUE(table.AddResource(
+ NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
+ .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("default.xml")).Build())
+ .Build(),
+ context->GetDiagnostics()));
+
+ ASSERT_TRUE(table.AddResource(
+ NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
+ .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("tablet.xml")).Build(), {},
+ "tablet")
+ .Build(),
+ context->GetDiagnostics()));
+
+ ASSERT_TRUE(table.AddResource(
+ NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
+ .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("no-sdcard.xml")).Build(),
+ {}, "no-sdcard")
+ .Build(),
+ context->GetDiagnostics()));
+
+ ProductFilter filter({"tablet", "no-sdcard"}, /* remove_default_config_values = */ false);
+ ASSERT_FALSE(filter.Consume(context.get(), &table));
+}
+
+TEST(ProductFilterTest, FailOnMultipleDefaults) {
+ std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
+
+ ResourceTable table;
+ ASSERT_TRUE(table.AddResource(
+ NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
+ .SetValue(test::ValueBuilder<Id>().SetSource(android::Source(".xml")).Build())
+ .Build(),
+ context->GetDiagnostics()));
+
+ ASSERT_TRUE(table.AddResource(
+ NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
+ .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("default.xml")).Build(), {},
+ "default")
+ .Build(),
+ context->GetDiagnostics()));
+
+ ProductFilter filter(std::unordered_set<std::string>{},
+ /* remove_default_config_values = */ false);
+ ASSERT_FALSE(filter.Consume(context.get(), &table));
+}
+
+TEST(ProductFilterTest, RemoveDefaultConfigValues) {
+ 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(
+ NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
+ .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("land/default.xml")).Build(),
+ land)
+ .Build(),
+ context->GetDiagnostics()));
+
+ ASSERT_TRUE(table.AddResource(
+ NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
+ .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("land/tablet.xml")).Build(),
+ land, "tablet")
+ .Build(),
+ context->GetDiagnostics()));
+
+ ASSERT_TRUE(table.AddResource(
+ NewResourceBuilder(test::ParseNameOrDie("android:string/two"))
+ .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("land/default.xml")).Build(),
+ land)
+ .Build(),
+ context->GetDiagnostics()));
+
+ ASSERT_TRUE(table.AddResource(
+ NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
+ .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("port/default.xml")).Build(),
+ port)
+ .Build(),
+ context->GetDiagnostics()));
+
+ ASSERT_TRUE(table.AddResource(
+ NewResourceBuilder(test::ParseNameOrDie("android:string/one"))
+ .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("port/tablet.xml")).Build(),
+ port, "tablet")
+ .Build(),
+ context->GetDiagnostics()));
+
+ ASSERT_TRUE(table.AddResource(
+ NewResourceBuilder(test::ParseNameOrDie("android:string/two"))
+ .SetValue(test::ValueBuilder<Id>().SetSource(android::Source("port/default.xml")).Build(),
+ port)
+ .Build(),
+ context->GetDiagnostics()));
+
+ ProductFilter filter({"tablet"}, /* remove_default_config_values = */ true);
+ ASSERT_TRUE(filter.Consume(context.get(), &table));
+
+ EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<Id>(&table, "android:string/one", land, ""));
+ EXPECT_EQ(nullptr, test::GetValueForConfigAndProduct<Id>(&table, "android:string/two", land, ""));
+ EXPECT_NE(nullptr, test::GetValueForConfigAndProduct<Id>(&table, "android:string/one", port, ""));
+ EXPECT_EQ(nullptr, test::GetValueForConfigAndProduct<Id>(&table, "android:string/two", port, ""));
+}
+
+} // namespace aapt