RRO: Added partition policies for overlays
<overlayable> tags can now have policy elements that indicate which partition the overlay apk must reside on in order to be allowed to overlay a resource. This change only adds parsing of <policy> and encoding of policy in the proto ResourceTable. A later change will add the encoding of policy and overlayable in the binary APK. <overlayable> <policy type="system|vendor|product|product_services|public" > <item type="string" name="oof" /> </policy> </overlayable> Bug: 110869880 Test: make aapt2_tests Change-Id: I8d4ed7b0e01f981149c6e3190af1681073b79b03
This commit is contained in:
@ -306,8 +306,29 @@ void Debug::PrintTable(const ResourceTable& table, const DebugPrintTableOptions&
|
||||
break;
|
||||
}
|
||||
|
||||
if (entry->overlayable) {
|
||||
printer->Print(" OVERLAYABLE");
|
||||
for (size_t i = 0; i < entry->overlayable_declarations.size(); i++) {
|
||||
printer->Print((i == 0) ? " " : "|");
|
||||
printer->Print("OVERLAYABLE");
|
||||
|
||||
if (entry->overlayable_declarations[i].policy) {
|
||||
switch (entry->overlayable_declarations[i].policy.value()) {
|
||||
case Overlayable::Policy::kProduct:
|
||||
printer->Print("_PRODUCT");
|
||||
break;
|
||||
case Overlayable::Policy::kProductServices:
|
||||
printer->Print("_PRODUCT_SERVICES");
|
||||
break;
|
||||
case Overlayable::Policy::kSystem:
|
||||
printer->Print("_SYSTEM");
|
||||
break;
|
||||
case Overlayable::Policy::kVendor:
|
||||
printer->Print("_VENDOR");
|
||||
break;
|
||||
case Overlayable::Policy::kPublic:
|
||||
printer->Print("_PUBLIC");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
printer->Println();
|
||||
|
@ -99,7 +99,7 @@ struct ParsedResource {
|
||||
ResourceId id;
|
||||
Visibility::Level visibility_level = Visibility::Level::kUndefined;
|
||||
bool allow_new = false;
|
||||
bool overlayable = false;
|
||||
std::vector<Overlayable> overlayable_declarations;
|
||||
|
||||
std::string comment;
|
||||
std::unique_ptr<Value> value;
|
||||
@ -133,11 +133,8 @@ static bool AddResourcesToTable(ResourceTable* table, IDiagnostics* diag, Parsed
|
||||
}
|
||||
}
|
||||
|
||||
if (res->overlayable) {
|
||||
Overlayable overlayable;
|
||||
overlayable.source = res->source;
|
||||
overlayable.comment = res->comment;
|
||||
if (!table->SetOverlayable(res->name, overlayable, diag)) {
|
||||
for (auto& overlayable : res->overlayable_declarations) {
|
||||
if (!table->AddOverlayable(res->name, overlayable, diag)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -673,7 +670,7 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser,
|
||||
if (can_be_bag) {
|
||||
const auto bag_iter = elToBagMap.find(resource_type);
|
||||
if (bag_iter != elToBagMap.end()) {
|
||||
// Ensure we have a name (unless this is a <public-group>).
|
||||
// Ensure we have a name (unless this is a <public-group> or <overlayable>).
|
||||
if (resource_type != "public-group" && resource_type != "overlayable") {
|
||||
if (!maybe_name) {
|
||||
diag_->Error(DiagMessage(out_resource->source)
|
||||
@ -1062,74 +1059,137 @@ bool ResourceParser::ParseSymbol(xml::XmlPullParser* parser, ParsedResource* out
|
||||
bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource* out_resource) {
|
||||
if (out_resource->config != ConfigDescription::DefaultConfig()) {
|
||||
diag_->Warn(DiagMessage(out_resource->source)
|
||||
<< "ignoring configuration '" << out_resource->config << "' for <overlayable> tag");
|
||||
<< "ignoring configuration '" << out_resource->config
|
||||
<< "' for <overlayable> tag");
|
||||
}
|
||||
|
||||
if (Maybe<StringPiece> maybe_policy = xml::FindNonEmptyAttribute(parser, "policy")) {
|
||||
const StringPiece& policy = maybe_policy.value();
|
||||
if (policy != "system") {
|
||||
diag_->Error(DiagMessage(out_resource->source)
|
||||
<< "<overlayable> has invalid policy '" << policy << "'");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
std::string comment;
|
||||
std::vector<Overlayable::Policy> policies;
|
||||
|
||||
bool error = false;
|
||||
const size_t depth = parser->depth();
|
||||
while (xml::XmlPullParser::NextChildNode(parser, depth)) {
|
||||
if (parser->event() != xml::XmlPullParser::Event::kStartElement) {
|
||||
// Skip text/comments.
|
||||
const size_t start_depth = parser->depth();
|
||||
while (xml::XmlPullParser::IsGoodEvent(parser->Next())) {
|
||||
xml::XmlPullParser::Event event = parser->event();
|
||||
if (event == xml::XmlPullParser::Event::kEndElement && parser->depth() == start_depth) {
|
||||
// Break the loop when exiting the overyabale element
|
||||
break;
|
||||
} else if (event == xml::XmlPullParser::Event::kEndElement
|
||||
&& parser->depth() == start_depth + 1) {
|
||||
// Clear the current policies when exiting the policy element
|
||||
policies.clear();
|
||||
continue;
|
||||
} else if (event == xml::XmlPullParser::Event::kComment) {
|
||||
// Get the comment of individual item elements
|
||||
comment = parser->comment();
|
||||
continue;
|
||||
} else if (event != xml::XmlPullParser::Event::kStartElement) {
|
||||
// Skip to the next element
|
||||
continue;
|
||||
}
|
||||
|
||||
const Source item_source = source_.WithLine(parser->line_number());
|
||||
const std::string& element_namespace = parser->element_namespace();
|
||||
const std::string& element_name = parser->element_name();
|
||||
const std::string& element_namespace = parser->element_namespace();
|
||||
|
||||
if (element_namespace.empty() && element_name == "item") {
|
||||
Maybe<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name");
|
||||
if (!maybe_name) {
|
||||
diag_->Error(DiagMessage(item_source)
|
||||
<< "<item> within an <overlayable> tag must have a 'name' attribute");
|
||||
if (!ParseOverlayableItem(parser, policies, comment, out_resource)) {
|
||||
error = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type");
|
||||
if (!maybe_type) {
|
||||
diag_->Error(DiagMessage(item_source)
|
||||
<< "<item> within an <overlayable> tag must have a 'type' attribute");
|
||||
} else if (element_namespace.empty() && element_name == "policy") {
|
||||
if (!policies.empty()) {
|
||||
// If the policy list is not empty, then we are currently inside a policy element
|
||||
diag_->Error(DiagMessage(item_source) << "<policy> blocks cannot be recursively nested");
|
||||
error = true;
|
||||
continue;
|
||||
break;
|
||||
} else if (Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type")) {
|
||||
// Parse the polices separated by vertical bar characters to allow for specifying multiple
|
||||
// policies at once
|
||||
for (StringPiece part : util::Tokenize(maybe_type.value(), '|')) {
|
||||
StringPiece trimmed_part = util::TrimWhitespace(part);
|
||||
if (trimmed_part == "public") {
|
||||
policies.push_back(Overlayable::Policy::kPublic);
|
||||
} else if (trimmed_part == "product") {
|
||||
policies.push_back(Overlayable::Policy::kProduct);
|
||||
} else if (trimmed_part == "product_services") {
|
||||
policies.push_back(Overlayable::Policy::kProductServices);
|
||||
} else if (trimmed_part == "system") {
|
||||
policies.push_back(Overlayable::Policy::kSystem);
|
||||
} else if (trimmed_part == "vendor") {
|
||||
policies.push_back(Overlayable::Policy::kVendor);
|
||||
} else {
|
||||
diag_->Error(DiagMessage(out_resource->source)
|
||||
<< "<policy> has unsupported type '" << trimmed_part << "'");
|
||||
error = true;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const ResourceType* type = ParseResourceType(maybe_type.value());
|
||||
if (type == nullptr) {
|
||||
diag_->Error(DiagMessage(out_resource->source)
|
||||
<< "invalid resource type '" << maybe_type.value()
|
||||
<< "' in <item> within an <overlayable>");
|
||||
error = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
ParsedResource child_resource;
|
||||
child_resource.name.type = *type;
|
||||
child_resource.name.entry = maybe_name.value().to_string();
|
||||
child_resource.source = item_source;
|
||||
child_resource.overlayable = true;
|
||||
if (options_.visibility) {
|
||||
child_resource.visibility_level = options_.visibility.value();
|
||||
}
|
||||
out_resource->child_resources.push_back(std::move(child_resource));
|
||||
|
||||
xml::XmlPullParser::SkipCurrentElement(parser);
|
||||
} else if (!ShouldIgnoreElement(element_namespace, element_name)) {
|
||||
diag_->Error(DiagMessage(item_source) << ":" << element_name << ">");
|
||||
diag_->Error(DiagMessage(item_source) << "invalid element <" << element_name << "> in "
|
||||
<< " <overlayable>");
|
||||
error = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return !error;
|
||||
}
|
||||
|
||||
bool ResourceParser::ParseOverlayableItem(xml::XmlPullParser* parser,
|
||||
const std::vector<Overlayable::Policy>& policies,
|
||||
const std::string& comment,
|
||||
ParsedResource* out_resource) {
|
||||
const Source item_source = source_.WithLine(parser->line_number());
|
||||
|
||||
Maybe<StringPiece> maybe_name = xml::FindNonEmptyAttribute(parser, "name");
|
||||
if (!maybe_name) {
|
||||
diag_->Error(DiagMessage(item_source)
|
||||
<< "<item> within an <overlayable> tag must have a 'name' attribute");
|
||||
return false;
|
||||
}
|
||||
|
||||
Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type");
|
||||
if (!maybe_type) {
|
||||
diag_->Error(DiagMessage(item_source)
|
||||
<< "<item> within an <overlayable> tag must have a 'type' attribute");
|
||||
return false;
|
||||
}
|
||||
|
||||
const ResourceType* type = ParseResourceType(maybe_type.value());
|
||||
if (type == nullptr) {
|
||||
diag_->Error(DiagMessage(out_resource->source)
|
||||
<< "invalid resource type '" << maybe_type.value()
|
||||
<< "' in <item> within an <overlayable>");
|
||||
return false;
|
||||
}
|
||||
|
||||
ParsedResource child_resource;
|
||||
child_resource.name.type = *type;
|
||||
child_resource.name.entry = maybe_name.value().to_string();
|
||||
child_resource.source = item_source;
|
||||
|
||||
if (policies.empty()) {
|
||||
Overlayable overlayable;
|
||||
overlayable.source = item_source;
|
||||
overlayable.comment = comment;
|
||||
child_resource.overlayable_declarations.push_back(overlayable);
|
||||
} else {
|
||||
for (Overlayable::Policy policy : policies) {
|
||||
Overlayable overlayable;
|
||||
overlayable.policy = policy;
|
||||
overlayable.source = item_source;
|
||||
overlayable.comment = comment;
|
||||
child_resource.overlayable_declarations.push_back(overlayable);
|
||||
}
|
||||
}
|
||||
|
||||
if (options_.visibility) {
|
||||
child_resource.visibility_level = options_.visibility.value();
|
||||
}
|
||||
out_resource->child_resources.push_back(std::move(child_resource));
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ResourceParser::ParseAddResource(xml::XmlPullParser* parser, ParsedResource* out_resource) {
|
||||
if (ParseSymbolImpl(parser, out_resource)) {
|
||||
out_resource->visibility_level = Visibility::Level::kUndefined;
|
||||
|
@ -96,6 +96,10 @@ class ResourceParser {
|
||||
bool ParseSymbolImpl(xml::XmlPullParser* parser, ParsedResource* out_resource);
|
||||
bool ParseSymbol(xml::XmlPullParser* parser, ParsedResource* out_resource);
|
||||
bool ParseOverlayable(xml::XmlPullParser* parser, ParsedResource* out_resource);
|
||||
bool ParseOverlayableItem(xml::XmlPullParser* parser,
|
||||
const std::vector<Overlayable::Policy>& policies,
|
||||
const std::string& comment,
|
||||
ParsedResource* out_resource);
|
||||
bool ParseAddResource(xml::XmlPullParser* parser, ParsedResource* out_resource);
|
||||
bool ParseAttr(xml::XmlPullParser* parser, ParsedResource* out_resource);
|
||||
bool ParseAttrImpl(xml::XmlPullParser* parser, ParsedResource* out_resource, bool weak);
|
||||
|
@ -891,56 +891,162 @@ TEST_F(ResourceParserTest, ParsePlatformIndependentNewline) {
|
||||
ASSERT_TRUE(TestParse(R"(<string name="foo">%1$s %n %2$s</string>)"));
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParseOverlayableTagWithSystemPolicy) {
|
||||
std::string input = R"(
|
||||
<overlayable policy="illegal_policy">
|
||||
<item type="string" name="foo" />
|
||||
</overlayable>)";
|
||||
EXPECT_FALSE(TestParse(input));
|
||||
|
||||
input = R"(
|
||||
<overlayable policy="system">
|
||||
<item name="foo" />
|
||||
</overlayable>)";
|
||||
EXPECT_FALSE(TestParse(input));
|
||||
|
||||
input = R"(
|
||||
<overlayable policy="system">
|
||||
<item type="attr" />
|
||||
</overlayable>)";
|
||||
EXPECT_FALSE(TestParse(input));
|
||||
|
||||
input = R"(
|
||||
<overlayable policy="system">
|
||||
<item type="bad_type" name="foo" />
|
||||
</overlayable>)";
|
||||
EXPECT_FALSE(TestParse(input));
|
||||
|
||||
input = R"(<overlayable policy="system" />)";
|
||||
TEST_F(ResourceParserTest, ParseOverlayable) {
|
||||
std::string input = R"(<overlayable />)";
|
||||
EXPECT_TRUE(TestParse(input));
|
||||
|
||||
input = R"(<overlayable />)";
|
||||
EXPECT_TRUE(TestParse(input));
|
||||
|
||||
input = R"(
|
||||
<overlayable policy="system">
|
||||
<item type="string" name="foo" />
|
||||
<item type="dimen" name="foo" />
|
||||
</overlayable>)";
|
||||
ASSERT_TRUE(TestParse(input));
|
||||
|
||||
input = R"(
|
||||
<overlayable>
|
||||
<item type="string" name="bar" />
|
||||
<item type="string" name="foo" />
|
||||
<item type="drawable" name="bar" />
|
||||
</overlayable>)";
|
||||
ASSERT_TRUE(TestParse(input));
|
||||
|
||||
Maybe<ResourceTable::SearchResult> search_result =
|
||||
table_.FindResource(test::ParseNameOrDie("string/bar"));
|
||||
auto search_result = table_.FindResource(test::ParseNameOrDie("string/foo"));
|
||||
ASSERT_TRUE(search_result);
|
||||
ASSERT_THAT(search_result.value().entry, NotNull());
|
||||
EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined));
|
||||
EXPECT_TRUE(search_result.value().entry->overlayable);
|
||||
EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(1));
|
||||
EXPECT_FALSE(search_result.value().entry->overlayable_declarations[0].policy);
|
||||
|
||||
search_result = table_.FindResource(test::ParseNameOrDie("drawable/bar"));
|
||||
ASSERT_TRUE(search_result);
|
||||
ASSERT_THAT(search_result.value().entry, NotNull());
|
||||
EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined));
|
||||
EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(1));
|
||||
EXPECT_FALSE(search_result.value().entry->overlayable_declarations[0].policy);
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParseOverlayablePolicy) {
|
||||
std::string input = R"(<overlayable />)";
|
||||
EXPECT_TRUE(TestParse(input));
|
||||
|
||||
input = R"(
|
||||
<overlayable>
|
||||
<item type="string" name="foo" />
|
||||
<policy type="product">
|
||||
<item type="string" name="bar" />
|
||||
</policy>
|
||||
<policy type="product_services">
|
||||
<item type="string" name="baz" />
|
||||
</policy>
|
||||
<policy type="system">
|
||||
<item type="string" name="fiz" />
|
||||
</policy>
|
||||
<policy type="vendor">
|
||||
<item type="string" name="fuz" />
|
||||
</policy>
|
||||
<policy type="public">
|
||||
<item type="string" name="faz" />
|
||||
</policy>
|
||||
</overlayable>)";
|
||||
ASSERT_TRUE(TestParse(input));
|
||||
|
||||
auto search_result = table_.FindResource(test::ParseNameOrDie("string/foo"));
|
||||
ASSERT_TRUE(search_result);
|
||||
ASSERT_THAT(search_result.value().entry, NotNull());
|
||||
EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined));
|
||||
EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(1));
|
||||
EXPECT_FALSE(search_result.value().entry->overlayable_declarations[0].policy);
|
||||
|
||||
search_result = table_.FindResource(test::ParseNameOrDie("string/bar"));
|
||||
ASSERT_TRUE(search_result);
|
||||
ASSERT_THAT(search_result.value().entry, NotNull());
|
||||
EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined));
|
||||
EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(1));
|
||||
EXPECT_THAT(search_result.value().entry->overlayable_declarations[0].policy,
|
||||
Eq(Overlayable::Policy::kProduct));
|
||||
|
||||
search_result = table_.FindResource(test::ParseNameOrDie("string/baz"));
|
||||
ASSERT_TRUE(search_result);
|
||||
ASSERT_THAT(search_result.value().entry, NotNull());
|
||||
EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined));
|
||||
EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(1));
|
||||
EXPECT_THAT(search_result.value().entry->overlayable_declarations[0].policy,
|
||||
Eq(Overlayable::Policy::kProductServices));
|
||||
|
||||
search_result = table_.FindResource(test::ParseNameOrDie("string/fiz"));
|
||||
ASSERT_TRUE(search_result);
|
||||
ASSERT_THAT(search_result.value().entry, NotNull());
|
||||
EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined));
|
||||
EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(1));
|
||||
EXPECT_THAT(search_result.value().entry->overlayable_declarations[0].policy,
|
||||
Eq(Overlayable::Policy::kSystem));
|
||||
|
||||
search_result = table_.FindResource(test::ParseNameOrDie("string/fuz"));
|
||||
ASSERT_TRUE(search_result);
|
||||
ASSERT_THAT(search_result.value().entry, NotNull());
|
||||
EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined));
|
||||
EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(1));
|
||||
EXPECT_THAT(search_result.value().entry->overlayable_declarations[0].policy,
|
||||
Eq(Overlayable::Policy::kVendor));
|
||||
|
||||
search_result = table_.FindResource(test::ParseNameOrDie("string/faz"));
|
||||
ASSERT_TRUE(search_result);
|
||||
ASSERT_THAT(search_result.value().entry, NotNull());
|
||||
EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined));
|
||||
EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(1));
|
||||
EXPECT_THAT(search_result.value().entry->overlayable_declarations[0].policy,
|
||||
Eq(Overlayable::Policy::kPublic));
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParseOverlayableBadPolicyError) {
|
||||
std::string input = R"(
|
||||
<overlayable>
|
||||
<policy type="illegal_policy">
|
||||
<item type="string" name="foo" />
|
||||
</policy>
|
||||
</overlayable>)";
|
||||
EXPECT_FALSE(TestParse(input));
|
||||
|
||||
input = R"(
|
||||
<overlayable>
|
||||
<policy type="product">
|
||||
<item name="foo" />
|
||||
</policy>
|
||||
</overlayable>)";
|
||||
EXPECT_FALSE(TestParse(input));
|
||||
|
||||
input = R"(
|
||||
<overlayable>
|
||||
<policy type="vendor">
|
||||
<item type="string" />
|
||||
</policy>
|
||||
</overlayable>)";
|
||||
EXPECT_FALSE(TestParse(input));
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParseOverlayableMultiplePolicy) {
|
||||
std::string input = R"(
|
||||
<overlayable>
|
||||
<policy type="vendor|product_services">
|
||||
<item type="string" name="foo" />
|
||||
</policy>
|
||||
<policy type="product|system">
|
||||
<item type="string" name="bar" />
|
||||
</policy>
|
||||
</overlayable>)";
|
||||
ASSERT_TRUE(TestParse(input));
|
||||
|
||||
auto search_result = table_.FindResource(test::ParseNameOrDie("string/foo"));
|
||||
ASSERT_TRUE(search_result);
|
||||
ASSERT_THAT(search_result.value().entry, NotNull());
|
||||
EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined));
|
||||
EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(2));
|
||||
EXPECT_THAT(search_result.value().entry->overlayable_declarations[0].policy,
|
||||
Eq(Overlayable::Policy::kVendor));
|
||||
EXPECT_THAT(search_result.value().entry->overlayable_declarations[1].policy,
|
||||
Eq(Overlayable::Policy::kProductServices));
|
||||
|
||||
search_result = table_.FindResource(test::ParseNameOrDie("string/bar"));
|
||||
ASSERT_TRUE(search_result);
|
||||
ASSERT_THAT(search_result.value().entry, NotNull());
|
||||
EXPECT_THAT(search_result.value().entry->visibility.level, Eq(Visibility::Level::kUndefined));
|
||||
EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(2));
|
||||
EXPECT_THAT(search_result.value().entry->overlayable_declarations[0].policy,
|
||||
Eq(Overlayable::Policy::kProduct));
|
||||
EXPECT_THAT(search_result.value().entry->overlayable_declarations[1].policy,
|
||||
Eq(Overlayable::Policy::kSystem));
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, DuplicateOverlayableIsError) {
|
||||
@ -950,6 +1056,85 @@ TEST_F(ResourceParserTest, DuplicateOverlayableIsError) {
|
||||
<item type="string" name="foo" />
|
||||
</overlayable>)";
|
||||
EXPECT_FALSE(TestParse(input));
|
||||
|
||||
input = R"(
|
||||
<overlayable>
|
||||
<item type="string" name="foo" />
|
||||
</overlayable>
|
||||
<overlayable>
|
||||
<item type="string" name="foo" />
|
||||
</overlayable>)";
|
||||
EXPECT_FALSE(TestParse(input));
|
||||
|
||||
input = R"(
|
||||
<overlayable">
|
||||
<policy type="product">
|
||||
<item type="string" name="foo" />
|
||||
<item type="string" name="foo" />
|
||||
</policy>
|
||||
</overlayable>)";
|
||||
EXPECT_FALSE(TestParse(input));
|
||||
|
||||
input = R"(
|
||||
<overlayable>
|
||||
<policy type="product">
|
||||
<item type="string" name="foo" />
|
||||
</policy>
|
||||
</overlayable>
|
||||
|
||||
<overlayable>
|
||||
<policy type="product">
|
||||
<item type="string" name="foo" />
|
||||
</policy>
|
||||
</overlayable>)";
|
||||
EXPECT_FALSE(TestParse(input));
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, PolicyAndNonPolicyOverlayableError) {
|
||||
std::string input = R"(
|
||||
<overlayable policy="product">
|
||||
<item type="string" name="foo" />
|
||||
</overlayable>
|
||||
<overlayable policy="">
|
||||
<item type="string" name="foo" />
|
||||
</overlayable>)";
|
||||
EXPECT_FALSE(TestParse(input));
|
||||
|
||||
input = R"(
|
||||
<overlayable policy="">
|
||||
<item type="string" name="foo" />
|
||||
</overlayable>
|
||||
<overlayable policy="product">
|
||||
<item type="string" name="foo" />
|
||||
</overlayable>)";
|
||||
EXPECT_FALSE(TestParse(input));
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, DuplicateOverlayableMultiplePolicyError) {
|
||||
std::string input = R"(
|
||||
<overlayable>
|
||||
<policy type="vendor|product">
|
||||
<item type="string" name="foo" />
|
||||
</policy>
|
||||
</overlayable>
|
||||
<overlayable>
|
||||
<policy type="product_services|vendor">
|
||||
<item type="string" name="foo" />
|
||||
</policy>
|
||||
</overlayable>)";
|
||||
EXPECT_FALSE(TestParse(input));
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, NestPolicyInOverlayableError) {
|
||||
std::string input = R"(
|
||||
<overlayable>
|
||||
<policy type="vendor|product">
|
||||
<policy type="product_services">
|
||||
<item type="string" name="foo" />
|
||||
</policy>
|
||||
</policy>
|
||||
</overlayable>)";
|
||||
EXPECT_FALSE(TestParse(input));
|
||||
}
|
||||
|
||||
TEST_F(ResourceParserTest, ParseIdItem) {
|
||||
|
@ -625,17 +625,17 @@ bool ResourceTable::SetAllowNewImpl(const ResourceNameRef& name, const AllowNew&
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ResourceTable::SetOverlayable(const ResourceNameRef& name, const Overlayable& overlayable,
|
||||
bool ResourceTable::AddOverlayable(const ResourceNameRef& name, const Overlayable& overlayable,
|
||||
IDiagnostics* diag) {
|
||||
return SetOverlayableImpl(name, overlayable, ResourceNameValidator, diag);
|
||||
return AddOverlayableImpl(name, overlayable, ResourceNameValidator, diag);
|
||||
}
|
||||
|
||||
bool ResourceTable::SetOverlayableMangled(const ResourceNameRef& name,
|
||||
bool ResourceTable::AddOverlayableMangled(const ResourceNameRef& name,
|
||||
const Overlayable& overlayable, IDiagnostics* diag) {
|
||||
return SetOverlayableImpl(name, overlayable, SkipNameValidator, diag);
|
||||
return AddOverlayableImpl(name, overlayable, SkipNameValidator, diag);
|
||||
}
|
||||
|
||||
bool ResourceTable::SetOverlayableImpl(const ResourceNameRef& name, const Overlayable& overlayable,
|
||||
bool ResourceTable::AddOverlayableImpl(const ResourceNameRef& name, const Overlayable& overlayable,
|
||||
NameValidator name_validator, IDiagnostics* diag) {
|
||||
CHECK(diag != nullptr);
|
||||
|
||||
@ -646,13 +646,28 @@ bool ResourceTable::SetOverlayableImpl(const ResourceNameRef& name, const Overla
|
||||
ResourceTablePackage* package = FindOrCreatePackage(name.package);
|
||||
ResourceTableType* type = package->FindOrCreateType(name.type);
|
||||
ResourceEntry* entry = type->FindOrCreateEntry(name.entry);
|
||||
if (entry->overlayable) {
|
||||
diag->Error(DiagMessage(overlayable.source)
|
||||
<< "duplicate overlayable declaration for resource '" << name << "'");
|
||||
diag->Error(DiagMessage(entry->overlayable.value().source) << "previous declaration here");
|
||||
return false;
|
||||
|
||||
for (auto& overlayable_declaration : entry->overlayable_declarations) {
|
||||
// An overlayable resource cannot be declared twice with the same policy
|
||||
if (overlayable.policy == overlayable_declaration.policy) {
|
||||
diag->Error(DiagMessage(overlayable.source)
|
||||
<< "duplicate overlayable declaration for resource '" << name << "'");
|
||||
diag->Error(DiagMessage(overlayable_declaration.source) << "previous declaration here");
|
||||
return false;
|
||||
}
|
||||
|
||||
// An overlayable resource cannot be declared once with a policy and without a policy because
|
||||
// the policy becomes unused
|
||||
if (!overlayable.policy || !overlayable_declaration.policy) {
|
||||
diag->Error(DiagMessage(overlayable.source)
|
||||
<< "overlayable resource '" << name << "'"
|
||||
<< " declared once with a policy and once with no policy");
|
||||
diag->Error(DiagMessage(overlayable_declaration.source) << "previous declaration here");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
entry->overlayable = overlayable;
|
||||
|
||||
entry->overlayable_declarations.push_back(overlayable);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -688,7 +703,7 @@ std::unique_ptr<ResourceTable> ResourceTable::Clone() const {
|
||||
new_entry->id = entry->id;
|
||||
new_entry->visibility = entry->visibility;
|
||||
new_entry->allow_new = entry->allow_new;
|
||||
new_entry->overlayable = entry->overlayable;
|
||||
new_entry->overlayable_declarations = entry->overlayable_declarations;
|
||||
|
||||
for (const auto& config_value : entry->values) {
|
||||
ResourceConfigValue* new_value =
|
||||
|
@ -57,8 +57,27 @@ struct AllowNew {
|
||||
std::string comment;
|
||||
};
|
||||
|
||||
// The policy dictating whether an entry is overlayable at runtime by RROs.
|
||||
// Represents a declaration that a resource is overayable at runtime.
|
||||
struct Overlayable {
|
||||
// Represents the types overlays that are allowed to overlay the resource.
|
||||
enum class Policy {
|
||||
// The resource can be overlaid by any overlay.
|
||||
kPublic,
|
||||
|
||||
// The resource can be overlaid by any overlay on the system partition.
|
||||
kSystem,
|
||||
|
||||
// The resource can be overlaid by any overlay on the vendor partition.
|
||||
kVendor,
|
||||
|
||||
// The resource can be overlaid by any overlay on the product partition.
|
||||
kProduct,
|
||||
|
||||
// The resource can be overlaid by any overlay on the product services partition.
|
||||
kProductServices,
|
||||
};
|
||||
|
||||
Maybe<Policy> policy;
|
||||
Source source;
|
||||
std::string comment;
|
||||
};
|
||||
@ -96,7 +115,8 @@ class ResourceEntry {
|
||||
|
||||
Maybe<AllowNew> allow_new;
|
||||
|
||||
Maybe<Overlayable> overlayable;
|
||||
// The declarations of this resource as overlayable for RROs
|
||||
std::vector<Overlayable> overlayable_declarations;
|
||||
|
||||
// The resource's values for each configuration.
|
||||
std::vector<std::unique_ptr<ResourceConfigValue>> values;
|
||||
@ -226,9 +246,9 @@ class ResourceTable {
|
||||
bool SetVisibilityWithIdMangled(const ResourceNameRef& name, const Visibility& visibility,
|
||||
const ResourceId& res_id, IDiagnostics* diag);
|
||||
|
||||
bool SetOverlayable(const ResourceNameRef& name, const Overlayable& overlayable,
|
||||
bool AddOverlayable(const ResourceNameRef& name, const Overlayable& overlayable,
|
||||
IDiagnostics* diag);
|
||||
bool SetOverlayableMangled(const ResourceNameRef& name, const Overlayable& overlayable,
|
||||
bool AddOverlayableMangled(const ResourceNameRef& name, const Overlayable& overlayable,
|
||||
IDiagnostics* diag);
|
||||
|
||||
bool SetAllowNew(const ResourceNameRef& name, const AllowNew& allow_new, IDiagnostics* diag);
|
||||
@ -303,7 +323,7 @@ class ResourceTable {
|
||||
bool SetAllowNewImpl(const ResourceNameRef& name, const AllowNew& allow_new,
|
||||
NameValidator name_validator, IDiagnostics* diag);
|
||||
|
||||
bool SetOverlayableImpl(const ResourceNameRef& name, const Overlayable& overlayable,
|
||||
bool AddOverlayableImpl(const ResourceNameRef& name, const Overlayable& overlayable,
|
||||
NameValidator name_validator, IDiagnostics* diag);
|
||||
|
||||
bool SetSymbolStateImpl(const ResourceNameRef& name, const ResourceId& res_id,
|
||||
|
@ -242,21 +242,69 @@ TEST(ResourceTableTest, SetAllowNew) {
|
||||
ASSERT_THAT(result.value().entry->allow_new.value().comment, StrEq("second"));
|
||||
}
|
||||
|
||||
TEST(ResourceTableTest, SetOverlayable) {
|
||||
TEST(ResourceTableTest, AddOverlayable) {
|
||||
ResourceTable table;
|
||||
const ResourceName name = test::ParseNameOrDie("android:string/foo");
|
||||
|
||||
Overlayable overlayable;
|
||||
|
||||
overlayable.policy = Overlayable::Policy::kProduct;
|
||||
overlayable.comment = "first";
|
||||
ASSERT_TRUE(table.SetOverlayable(name, overlayable, test::GetDiagnostics()));
|
||||
ASSERT_TRUE(table.AddOverlayable(name, overlayable, test::GetDiagnostics()));
|
||||
Maybe<ResourceTable::SearchResult> result = table.FindResource(name);
|
||||
ASSERT_TRUE(result);
|
||||
ASSERT_TRUE(result.value().entry->overlayable);
|
||||
ASSERT_THAT(result.value().entry->overlayable.value().comment, StrEq("first"));
|
||||
ASSERT_THAT(result.value().entry->overlayable_declarations.size(), Eq(1));
|
||||
ASSERT_THAT(result.value().entry->overlayable_declarations[0].comment, StrEq("first"));
|
||||
ASSERT_THAT(result.value().entry->overlayable_declarations[0].policy,
|
||||
Eq(Overlayable::Policy::kProduct));
|
||||
|
||||
overlayable.comment = "second";
|
||||
ASSERT_FALSE(table.SetOverlayable(name, overlayable, test::GetDiagnostics()));
|
||||
Overlayable overlayable2;
|
||||
overlayable2.comment = "second";
|
||||
overlayable2.policy = Overlayable::Policy::kProductServices;
|
||||
ASSERT_TRUE(table.AddOverlayable(name, overlayable2, test::GetDiagnostics()));
|
||||
result = table.FindResource(name);
|
||||
ASSERT_TRUE(result);
|
||||
ASSERT_THAT(result.value().entry->overlayable_declarations.size(), Eq(2));
|
||||
ASSERT_THAT(result.value().entry->overlayable_declarations[0].comment, StrEq("first"));
|
||||
ASSERT_THAT(result.value().entry->overlayable_declarations[0].policy,
|
||||
Eq(Overlayable::Policy::kProduct));
|
||||
ASSERT_THAT(result.value().entry->overlayable_declarations[1].comment, StrEq("second"));
|
||||
ASSERT_THAT(result.value().entry->overlayable_declarations[1].policy,
|
||||
Eq(Overlayable::Policy::kProductServices));
|
||||
}
|
||||
|
||||
TEST(ResourceTableTest, AddDuplicateOverlayableFail) {
|
||||
ResourceTable table;
|
||||
const ResourceName name = test::ParseNameOrDie("android:string/foo");
|
||||
|
||||
Overlayable overlayable;
|
||||
overlayable.policy = Overlayable::Policy::kProduct;
|
||||
ASSERT_TRUE(table.AddOverlayable(name, overlayable, test::GetDiagnostics()));
|
||||
|
||||
Overlayable overlayable2;
|
||||
overlayable2.policy = Overlayable::Policy::kProduct;
|
||||
ASSERT_FALSE(table.AddOverlayable(name, overlayable2, test::GetDiagnostics()));
|
||||
}
|
||||
|
||||
TEST(ResourceTableTest, AddOverlayablePolicyAndNoneFirstFail) {
|
||||
ResourceTable table;
|
||||
const ResourceName name = test::ParseNameOrDie("android:string/foo");
|
||||
|
||||
ASSERT_TRUE(table.AddOverlayable(name, {}, test::GetDiagnostics()));
|
||||
|
||||
Overlayable overlayable2;
|
||||
overlayable2.policy = Overlayable::Policy::kProduct;
|
||||
ASSERT_FALSE(table.AddOverlayable(name, overlayable2, test::GetDiagnostics()));
|
||||
}
|
||||
|
||||
TEST(ResourceTableTest, AddOverlayablePolicyAndNoneLastFail) {
|
||||
ResourceTable table;
|
||||
const ResourceName name = test::ParseNameOrDie("android:string/foo");
|
||||
|
||||
Overlayable overlayable;
|
||||
overlayable.policy = Overlayable::Policy::kProduct;
|
||||
ASSERT_TRUE(table.AddOverlayable(name, overlayable, test::GetDiagnostics()));
|
||||
|
||||
ASSERT_FALSE(table.AddOverlayable(name, {}, test::GetDiagnostics()));
|
||||
}
|
||||
|
||||
TEST(ResourceTableTest, AllowDuplictaeResourcesNames) {
|
||||
|
@ -133,13 +133,25 @@ message AllowNew {
|
||||
string comment = 2;
|
||||
}
|
||||
|
||||
// Whether a resource is overlayable by runtime resource overlays (RRO).
|
||||
// Represents a declaration that a resource is overayable at runtime.
|
||||
message Overlayable {
|
||||
enum Policy {
|
||||
NONE = 0;
|
||||
PUBLIC = 1;
|
||||
SYSTEM = 2;
|
||||
VENDOR = 3;
|
||||
PRODUCT = 4;
|
||||
PRODUCT_SERVICES = 5;
|
||||
}
|
||||
|
||||
// Where this declaration was defined in source.
|
||||
Source source = 1;
|
||||
|
||||
// Any comment associated with the declaration.
|
||||
string comment = 2;
|
||||
|
||||
// The policy of the overlayable declaration
|
||||
Policy policy = 3;
|
||||
}
|
||||
|
||||
// An entry ID in the range [0x0000, 0xffff].
|
||||
@ -169,7 +181,7 @@ message Entry {
|
||||
AllowNew allow_new = 4;
|
||||
|
||||
// Whether this resource can be overlaid by a runtime resource overlay (RRO).
|
||||
Overlayable overlayable = 5;
|
||||
repeated Overlayable overlayable = 5;
|
||||
|
||||
// The set of values defined for this entry, each corresponding to a different
|
||||
// configuration/variant.
|
||||
|
@ -398,7 +398,7 @@ bool BinaryResourceParser::ParseType(const ResourceTablePackage* package,
|
||||
if (type_spec_flags & ResTable_typeSpec::SPEC_OVERLAYABLE) {
|
||||
Overlayable overlayable;
|
||||
overlayable.source = source_.WithLine(0);
|
||||
if (!table_->SetOverlayableMangled(name, overlayable, diag_)) {
|
||||
if (!table_->AddOverlayableMangled(name, overlayable, diag_)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -446,7 +446,7 @@ class PackageFlattener {
|
||||
config_masks[entry->id.value()] |= util::HostToDevice32(ResTable_typeSpec::SPEC_PUBLIC);
|
||||
}
|
||||
|
||||
if (entry->overlayable) {
|
||||
if (!entry->overlayable_declarations.empty()) {
|
||||
config_masks[entry->id.value()] |=
|
||||
util::HostToDevice32(ResTable_typeSpec::SPEC_OVERLAYABLE);
|
||||
}
|
||||
|
@ -634,7 +634,7 @@ TEST_F(TableFlattenerTest, FlattenOverlayable) {
|
||||
.AddSimple("com.app.test:integer/overlayable", ResourceId(0x7f020000))
|
||||
.Build();
|
||||
|
||||
ASSERT_TRUE(table->SetOverlayable(test::ParseNameOrDie("com.app.test:integer/overlayable"),
|
||||
ASSERT_TRUE(table->AddOverlayable(test::ParseNameOrDie("com.app.test:integer/overlayable"),
|
||||
Overlayable{}, test::GetDiagnostics()));
|
||||
|
||||
ResTable res_table;
|
||||
|
@ -437,15 +437,37 @@ static bool DeserializePackageFromPb(const pb::Package& pb_package, const ResStr
|
||||
entry->allow_new = std::move(allow_new);
|
||||
}
|
||||
|
||||
if (pb_entry.has_overlayable()) {
|
||||
const pb::Overlayable& pb_overlayable = pb_entry.overlayable();
|
||||
|
||||
for (const pb::Overlayable& pb_overlayable : pb_entry.overlayable()) {
|
||||
Overlayable overlayable;
|
||||
switch (pb_overlayable.policy()) {
|
||||
case pb::Overlayable::NONE:
|
||||
overlayable.policy = {};
|
||||
break;
|
||||
case pb::Overlayable::PUBLIC:
|
||||
overlayable.policy = Overlayable::Policy::kPublic;
|
||||
break;
|
||||
case pb::Overlayable::PRODUCT:
|
||||
overlayable.policy = Overlayable::Policy::kProduct;
|
||||
break;
|
||||
case pb::Overlayable::PRODUCT_SERVICES:
|
||||
overlayable.policy = Overlayable::Policy::kProductServices;
|
||||
break;
|
||||
case pb::Overlayable::SYSTEM:
|
||||
overlayable.policy = Overlayable::Policy::kSystem;
|
||||
break;
|
||||
case pb::Overlayable::VENDOR:
|
||||
overlayable.policy = Overlayable::Policy::kVendor;
|
||||
break;
|
||||
default:
|
||||
*out_error = "unknown overlayable policy";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pb_overlayable.has_source()) {
|
||||
DeserializeSourceFromPb(pb_overlayable.source(), src_pool, &overlayable.source);
|
||||
}
|
||||
overlayable.comment = pb_overlayable.comment();
|
||||
entry->overlayable = std::move(overlayable);
|
||||
entry->overlayable_declarations.push_back(overlayable);
|
||||
}
|
||||
|
||||
ResourceId resid(pb_package.package_id().id(), pb_type.type_id().id(),
|
||||
|
@ -310,11 +310,31 @@ void SerializeTableToPb(const ResourceTable& table, pb::ResourceTable* out_table
|
||||
pb_allow_new->set_comment(entry->allow_new.value().comment);
|
||||
}
|
||||
|
||||
if (entry->overlayable) {
|
||||
pb::Overlayable* pb_overlayable = pb_entry->mutable_overlayable();
|
||||
SerializeSourceToPb(entry->overlayable.value().source, &source_pool,
|
||||
for (const Overlayable& overlayable : entry->overlayable_declarations) {
|
||||
pb::Overlayable* pb_overlayable = pb_entry->add_overlayable();
|
||||
if (overlayable.policy) {
|
||||
switch (overlayable.policy.value()) {
|
||||
case Overlayable::Policy::kPublic:
|
||||
pb_overlayable->set_policy(pb::Overlayable::PUBLIC);
|
||||
break;
|
||||
case Overlayable::Policy::kProduct:
|
||||
pb_overlayable->set_policy(pb::Overlayable::PRODUCT);
|
||||
break;
|
||||
case Overlayable::Policy::kProductServices:
|
||||
pb_overlayable->set_policy(pb::Overlayable::PRODUCT_SERVICES);
|
||||
break;
|
||||
case Overlayable::Policy::kSystem:
|
||||
pb_overlayable->set_policy(pb::Overlayable::SYSTEM);
|
||||
break;
|
||||
case Overlayable::Policy::kVendor:
|
||||
pb_overlayable->set_policy(pb::Overlayable::VENDOR);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SerializeSourceToPb(overlayable.source, &source_pool,
|
||||
pb_overlayable->mutable_source());
|
||||
pb_overlayable->set_comment(entry->overlayable.value().comment);
|
||||
pb_overlayable->set_comment(overlayable.comment);
|
||||
}
|
||||
|
||||
for (const std::unique_ptr<ResourceConfigValue>& config_value : entry->values) {
|
||||
|
@ -93,7 +93,7 @@ TEST(ProtoSerializeTest, SerializeSinglePackage) {
|
||||
util::make_unique<Reference>(expected_ref), context->GetDiagnostics()));
|
||||
|
||||
// Make an overlayable resource.
|
||||
ASSERT_TRUE(table->SetOverlayable(test::ParseNameOrDie("com.app.a:integer/overlayable"),
|
||||
ASSERT_TRUE(table->AddOverlayable(test::ParseNameOrDie("com.app.a:integer/overlayable"),
|
||||
Overlayable{}, test::GetDiagnostics()));
|
||||
|
||||
pb::ResourceTable pb_table;
|
||||
@ -106,7 +106,7 @@ TEST(ProtoSerializeTest, SerializeSinglePackage) {
|
||||
|
||||
ResourceTable new_table;
|
||||
std::string error;
|
||||
ASSERT_TRUE(DeserializeTableFromPb(pb_table, &files, &new_table, &error));
|
||||
ASSERT_TRUE(DeserializeTableFromPb(pb_table, &files, &new_table, &error)) << error;
|
||||
EXPECT_THAT(error, IsEmpty());
|
||||
|
||||
Id* new_id = test::GetValue<Id>(&new_table, "com.app.a:id/foo");
|
||||
@ -160,7 +160,8 @@ TEST(ProtoSerializeTest, SerializeSinglePackage) {
|
||||
new_table.FindResource(test::ParseNameOrDie("com.app.a:integer/overlayable"));
|
||||
ASSERT_TRUE(search_result);
|
||||
ASSERT_THAT(search_result.value().entry, NotNull());
|
||||
EXPECT_TRUE(search_result.value().entry->overlayable);
|
||||
EXPECT_THAT(search_result.value().entry->overlayable_declarations.size(), Eq(1));
|
||||
EXPECT_FALSE(search_result.value().entry->overlayable_declarations[0].policy);
|
||||
}
|
||||
|
||||
TEST(ProtoSerializeTest, SerializeAndDeserializeXml) {
|
||||
@ -464,4 +465,59 @@ TEST(ProtoSerializeTest, SerializeDeserializeConfiguration) {
|
||||
"night-xhdpi-stylus-keysexposed-qwerty-navhidden-dpad-300x200-v23");
|
||||
}
|
||||
|
||||
TEST(ProtoSerializeTest, SerializeAndDeserializeOverlayable) {
|
||||
std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
|
||||
std::unique_ptr<ResourceTable> table =
|
||||
test::ResourceTableBuilder()
|
||||
.AddOverlayable("com.app.a:bool/foo", Overlayable::Policy::kSystem)
|
||||
.AddOverlayable("com.app.a:bool/foo", Overlayable::Policy::kProduct)
|
||||
.AddOverlayable("com.app.a:bool/bar", Overlayable::Policy::kProductServices)
|
||||
.AddOverlayable("com.app.a:bool/bar", Overlayable::Policy::kVendor)
|
||||
.AddOverlayable("com.app.a:bool/baz", Overlayable::Policy::kPublic)
|
||||
.AddOverlayable("com.app.a:bool/biz", {})
|
||||
.AddValue("com.app.a:bool/fiz", ResourceUtils::TryParseBool("true"))
|
||||
.Build();
|
||||
|
||||
pb::ResourceTable pb_table;
|
||||
SerializeTableToPb(*table, &pb_table, context->GetDiagnostics());
|
||||
|
||||
MockFileCollection files;
|
||||
ResourceTable new_table;
|
||||
std::string error;
|
||||
ASSERT_TRUE(DeserializeTableFromPb(pb_table, &files, &new_table, &error));
|
||||
EXPECT_THAT(error, IsEmpty());
|
||||
|
||||
Maybe<ResourceTable::SearchResult> result =
|
||||
new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/foo"));
|
||||
ASSERT_TRUE(result);
|
||||
ASSERT_THAT(result.value().entry->overlayable_declarations.size(), Eq(2));
|
||||
EXPECT_THAT(result.value().entry->overlayable_declarations[0].policy,
|
||||
Eq(Overlayable::Policy::kSystem));
|
||||
EXPECT_THAT(result.value().entry->overlayable_declarations[1].policy,
|
||||
Eq(Overlayable::Policy::kProduct));
|
||||
|
||||
result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/bar"));
|
||||
ASSERT_TRUE(result);
|
||||
ASSERT_THAT(result.value().entry->overlayable_declarations.size(), Eq(2));
|
||||
EXPECT_THAT(result.value().entry->overlayable_declarations[0].policy,
|
||||
Eq(Overlayable::Policy::kProductServices));
|
||||
EXPECT_THAT(result.value().entry->overlayable_declarations[1].policy,
|
||||
Eq(Overlayable::Policy::kVendor));
|
||||
|
||||
result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/baz"));
|
||||
ASSERT_TRUE(result);
|
||||
ASSERT_THAT(result.value().entry->overlayable_declarations.size(), Eq(1));
|
||||
EXPECT_THAT(result.value().entry->overlayable_declarations[0].policy,
|
||||
Eq(Overlayable::Policy::kPublic));
|
||||
|
||||
result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/biz"));
|
||||
ASSERT_TRUE(result);
|
||||
ASSERT_THAT(result.value().entry->overlayable_declarations.size(), Eq(1));
|
||||
EXPECT_FALSE(result.value().entry->overlayable_declarations[0].policy);
|
||||
|
||||
result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/fiz"));
|
||||
ASSERT_TRUE(result);
|
||||
EXPECT_THAT(result.value().entry->overlayable_declarations.size(), Eq(0));
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
@ -101,7 +101,7 @@ static bool MergeType(IAaptContext* context, const Source& src, ResourceTableTyp
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool MergeEntry(IAaptContext* context, const Source& src, bool overlay,
|
||||
static bool MergeEntry(IAaptContext* context, const Source& src,
|
||||
ResourceEntry* dst_entry, ResourceEntry* src_entry,
|
||||
bool strict_visibility) {
|
||||
if (strict_visibility
|
||||
@ -134,17 +134,35 @@ static bool MergeEntry(IAaptContext* context, const Source& src, bool overlay,
|
||||
dst_entry->allow_new = std::move(src_entry->allow_new);
|
||||
}
|
||||
|
||||
if (src_entry->overlayable) {
|
||||
if (dst_entry->overlayable && !overlay) {
|
||||
context->GetDiagnostics()->Error(DiagMessage(src_entry->overlayable.value().source)
|
||||
<< "duplicate overlayable declaration for resource '"
|
||||
<< src_entry->name << "'");
|
||||
context->GetDiagnostics()->Error(DiagMessage(dst_entry->overlayable.value().source)
|
||||
<< "previous declaration here");
|
||||
return false;
|
||||
for (auto& src_overlayable : src_entry->overlayable_declarations) {
|
||||
for (auto& dst_overlayable : dst_entry->overlayable_declarations) {
|
||||
// An overlayable resource cannot be declared twice with the same policy
|
||||
if (src_overlayable.policy == dst_overlayable.policy) {
|
||||
context->GetDiagnostics()->Error(DiagMessage(src_overlayable.source)
|
||||
<< "duplicate overlayable declaration for resource '"
|
||||
<< src_entry->name << "'");
|
||||
context->GetDiagnostics()->Error(DiagMessage(dst_overlayable.source)
|
||||
<< "previous declaration here");
|
||||
return false;
|
||||
}
|
||||
|
||||
// An overlayable resource cannot be declared once with a policy and without a policy because
|
||||
// the policy becomes unused
|
||||
if (!src_overlayable.policy || !dst_overlayable.policy) {
|
||||
context->GetDiagnostics()->Error(DiagMessage(src_overlayable.source)
|
||||
<< "overlayable resource '" << src_entry->name
|
||||
<< "' declared once with a policy and once with no "
|
||||
<< "policy");
|
||||
context->GetDiagnostics()->Error(DiagMessage(dst_overlayable.source)
|
||||
<< "previous declaration here");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
dst_entry->overlayable = std::move(src_entry->overlayable);
|
||||
}
|
||||
|
||||
dst_entry->overlayable_declarations.insert(dst_entry->overlayable_declarations.end(),
|
||||
src_entry->overlayable_declarations.begin(),
|
||||
src_entry->overlayable_declarations.end());
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -244,7 +262,7 @@ bool TableMerger::DoMerge(const Source& src, ResourceTable* src_table,
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!MergeEntry(context_, src, overlay, dst_entry, src_entry.get(), options_.strict_visibility)) {
|
||||
if (!MergeEntry(context_, src, dst_entry, src_entry.get(), options_.strict_visibility)) {
|
||||
error = true;
|
||||
continue;
|
||||
}
|
||||
|
@ -436,4 +436,97 @@ TEST_F(TableMergerTest, OverlaidStyleablesAndStylesShouldBeMerged) {
|
||||
Eq(make_value(Reference(test::ParseNameOrDie("com.app.a:style/OverlayParent")))));
|
||||
}
|
||||
|
||||
TEST_F(TableMergerTest, AddOverlayable) {
|
||||
std::unique_ptr<ResourceTable> table_a =
|
||||
test::ResourceTableBuilder()
|
||||
.SetPackageId("com.app.a", 0x7f)
|
||||
.AddOverlayable("bool/foo", Overlayable::Policy::kProduct)
|
||||
.Build();
|
||||
|
||||
std::unique_ptr<ResourceTable> table_b =
|
||||
test::ResourceTableBuilder()
|
||||
.SetPackageId("com.app.a", 0x7f)
|
||||
.AddOverlayable("bool/foo", Overlayable::Policy::kProductServices)
|
||||
.Build();
|
||||
|
||||
ResourceTable final_table;
|
||||
TableMergerOptions options;
|
||||
options.auto_add_overlay = true;
|
||||
TableMerger merger(context_.get(), &final_table, options);
|
||||
ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/));
|
||||
ASSERT_TRUE(merger.Merge({}, table_b.get(), false /*overlay*/));
|
||||
|
||||
const ResourceName name = test::ParseNameOrDie("com.app.a:bool/foo");
|
||||
Maybe<ResourceTable::SearchResult> result = final_table.FindResource(name);
|
||||
ASSERT_TRUE(result);
|
||||
ASSERT_THAT(result.value().entry->overlayable_declarations.size(), Eq(2));
|
||||
ASSERT_THAT(result.value().entry->overlayable_declarations[0].policy,
|
||||
Eq(Overlayable::Policy::kProduct));
|
||||
ASSERT_THAT(result.value().entry->overlayable_declarations[1].policy,
|
||||
Eq(Overlayable::Policy::kProductServices));
|
||||
}
|
||||
|
||||
TEST_F(TableMergerTest, AddDuplicateOverlayableFail) {
|
||||
std::unique_ptr<ResourceTable> table_a =
|
||||
test::ResourceTableBuilder()
|
||||
.SetPackageId("com.app.a", 0x7f)
|
||||
.AddOverlayable("bool/foo", Overlayable::Policy::kProduct)
|
||||
.Build();
|
||||
|
||||
std::unique_ptr<ResourceTable> table_b =
|
||||
test::ResourceTableBuilder()
|
||||
.SetPackageId("com.app.a", 0x7f)
|
||||
.AddOverlayable("bool/foo", Overlayable::Policy::kProduct)
|
||||
.Build();
|
||||
|
||||
ResourceTable final_table;
|
||||
TableMergerOptions options;
|
||||
options.auto_add_overlay = true;
|
||||
TableMerger merger(context_.get(), &final_table, options);
|
||||
ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/));
|
||||
ASSERT_FALSE(merger.Merge({}, table_b.get(), false /*overlay*/));
|
||||
}
|
||||
|
||||
TEST_F(TableMergerTest, AddOverlayablePolicyAndNoneFirstFail) {
|
||||
std::unique_ptr<ResourceTable> table_a =
|
||||
test::ResourceTableBuilder()
|
||||
.SetPackageId("com.app.a", 0x7f)
|
||||
.AddOverlayable("bool/foo", {})
|
||||
.Build();
|
||||
|
||||
std::unique_ptr<ResourceTable> table_b =
|
||||
test::ResourceTableBuilder()
|
||||
.SetPackageId("com.app.a", 0x7f)
|
||||
.AddOverlayable("bool/foo", Overlayable::Policy::kProduct)
|
||||
.Build();
|
||||
|
||||
ResourceTable final_table;
|
||||
TableMergerOptions options;
|
||||
options.auto_add_overlay = true;
|
||||
TableMerger merger(context_.get(), &final_table, options);
|
||||
ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/));
|
||||
ASSERT_FALSE(merger.Merge({}, table_b.get(), false /*overlay*/));
|
||||
}
|
||||
|
||||
TEST_F(TableMergerTest, AddOverlayablePolicyAndNoneLastFail) {
|
||||
std::unique_ptr<ResourceTable> table_a =
|
||||
test::ResourceTableBuilder()
|
||||
.SetPackageId("com.app.a", 0x7f)
|
||||
.AddOverlayable("bool/foo", Overlayable::Policy::kProduct)
|
||||
.Build();
|
||||
|
||||
std::unique_ptr<ResourceTable> table_b =
|
||||
test::ResourceTableBuilder()
|
||||
.SetPackageId("com.app.a", 0x7f)
|
||||
.AddOverlayable("bool/foo", {})
|
||||
.Build();
|
||||
|
||||
ResourceTable final_table;
|
||||
TableMergerOptions options;
|
||||
options.auto_add_overlay = true;
|
||||
TableMerger merger(context_.get(), &final_table, options);
|
||||
ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/));
|
||||
ASSERT_FALSE(merger.Merge({}, table_b.get(), false /*overlay*/));
|
||||
}
|
||||
|
||||
} // namespace aapt
|
||||
|
@ -135,6 +135,15 @@ ResourceTableBuilder& ResourceTableBuilder::SetSymbolState(const StringPiece& na
|
||||
return *this;
|
||||
}
|
||||
|
||||
ResourceTableBuilder& ResourceTableBuilder::AddOverlayable(const StringPiece& name,
|
||||
const Maybe<Overlayable::Policy> p) {
|
||||
ResourceName res_name = ParseNameOrDie(name);
|
||||
Overlayable overlayable;
|
||||
overlayable.policy = p;
|
||||
CHECK(table_->AddOverlayable(res_name, overlayable, GetDiagnostics()));
|
||||
return *this;
|
||||
}
|
||||
|
||||
StringPool* ResourceTableBuilder::string_pool() {
|
||||
return &table_->string_pool;
|
||||
}
|
||||
|
@ -73,6 +73,8 @@ class ResourceTableBuilder {
|
||||
const ResourceId& id, std::unique_ptr<Value> value);
|
||||
ResourceTableBuilder& SetSymbolState(const android::StringPiece& name, const ResourceId& id,
|
||||
Visibility::Level level, bool allow_new = false);
|
||||
ResourceTableBuilder& AddOverlayable(const android::StringPiece& name,
|
||||
Maybe<Overlayable::Policy> policy);
|
||||
|
||||
StringPool* string_pool();
|
||||
std::unique_ptr<ResourceTable> Build();
|
||||
|
Reference in New Issue
Block a user