diff options
Diffstat (limited to 'tools/aapt2')
84 files changed, 2274 insertions, 612 deletions
diff --git a/tools/aapt2/Android.bp b/tools/aapt2/Android.bp index 53372bff3e67..c1d05e47bc19 100644 --- a/tools/aapt2/Android.bp +++ b/tools/aapt2/Android.bp @@ -56,9 +56,10 @@ cc_defaults { "libziparchive", "libpng", "libbase", - "libprotobuf-cpp-lite", + "libprotobuf-cpp-full", "libz", "libbuildversion", + "libidmap2_policies", ], stl: "libc++_static", group_static_libs: true, @@ -197,6 +198,7 @@ cc_test_host { cc_binary_host { name: "aapt2", srcs: ["Main.cpp"] + toolSources, + use_version_lib: true, static_libs: ["libaapt2"], defaults: ["aapt2_defaults"], } @@ -209,6 +211,7 @@ genrule { tools: [":soong_zip"], srcs: [ "Configuration.proto", + "ResourcesInternal.proto", "Resources.proto", ], out: ["aapt2-protos.zip"], diff --git a/tools/aapt2/AppInfo.h b/tools/aapt2/AppInfo.h index 75123537116f..d3ca357b0305 100644 --- a/tools/aapt2/AppInfo.h +++ b/tools/aapt2/AppInfo.h @@ -17,6 +17,7 @@ #ifndef AAPT_APP_INFO_H #define AAPT_APP_INFO_H +#include <set> #include <string> #include "util/Maybe.h" @@ -42,6 +43,9 @@ struct AppInfo { // The app's split name, if it is a split. Maybe<std::string> split_name; + + // The split names that this split depends on. + std::set<std::string> split_name_dependencies; }; } // namespace aapt diff --git a/tools/aapt2/Configuration.proto b/tools/aapt2/Configuration.proto index fc636a43ec40..8a4644c9a219 100644 --- a/tools/aapt2/Configuration.proto +++ b/tools/aapt2/Configuration.proto @@ -19,7 +19,6 @@ syntax = "proto3"; package aapt.pb; option java_package = "com.android.aapt"; -option optimize_for = LITE_RUNTIME; // A description of the requirements a device must have in order for a // resource to be matched and selected. diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp index 3da22b4fb9fa..1eb7d95f381a 100644 --- a/tools/aapt2/Debug.cpp +++ b/tools/aapt2/Debug.cpp @@ -32,10 +32,16 @@ #include "text/Printer.h" #include "util/Util.h" +#include "idmap2/Policies.h" + using ::aapt::text::Printer; using ::android::StringPiece; using ::android::base::StringPrintf; +using android::idmap2::policy::kPolicyStringToFlag; + +using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags; + namespace aapt { namespace { @@ -178,19 +184,17 @@ class ValueBodyPrinter : public ConstValueVisitor { void Visit(const Array* array) override { const size_t count = array->elements.size(); printer_->Print("["); - if (count > 0) { - for (size_t i = 0u; i < count; i++) { - if (i != 0u && i % 4u == 0u) { - printer_->Println(); - printer_->Print(" "); - } - PrintItem(*array->elements[i]); - if (i != count - 1) { - printer_->Print(", "); - } + for (size_t i = 0u; i < count; i++) { + if (i != 0u && i % 4u == 0u) { + printer_->Println(); + printer_->Print(" "); + } + PrintItem(*array->elements[i]); + if (i != count - 1) { + printer_->Print(", "); } - printer_->Println("]"); } + printer_->Println("]"); } void Visit(const Plural* plural) override { @@ -248,6 +252,29 @@ class ValueBodyPrinter : public ConstValueVisitor { Printer* printer_; }; +std::string OverlayablePoliciesToString(PolicyFlags policies) { + std::string str; + + uint32_t remaining = policies; + for (auto const& policy : kPolicyStringToFlag) { + if ((policies & policy.second) != policy.second) { + continue; + } + if (!str.empty()) { + str.append("|"); + } + str.append(policy.first.data()); + remaining &= ~policy.second; + } + if (remaining != 0) { + if (!str.empty()) { + str.append("|"); + } + str.append(StringPrintf("0x%08x", remaining)); + } + return !str.empty() ? str : "none"; +} + } // namespace void Debug::PrintTable(const ResourceTable& table, const DebugPrintTableOptions& options, @@ -314,6 +341,10 @@ void Debug::PrintTable(const ResourceTable& table, const DebugPrintTableOptions& break; } + if (entry->overlayable_item) { + printer->Print(" OVERLAYABLE"); + } + printer->Println(); if (options.show_values) { @@ -527,4 +558,62 @@ void Debug::DumpXml(const xml::XmlResource& doc, Printer* printer) { doc.root->Accept(&xml_visitor); } +struct DumpOverlayableEntry { + std::string overlayable_section; + std::string policy_subsection; + std::string resource_name; +}; + +void Debug::DumpOverlayable(const ResourceTable& table, text::Printer* printer) { + std::vector<DumpOverlayableEntry> items; + for (const auto& package : table.packages) { + for (const auto& type : package->types) { + for (const auto& entry : type->entries) { + if (entry->overlayable_item) { + const auto& overlayable_item = entry->overlayable_item.value(); + const auto overlayable_section = StringPrintf(R"(name="%s" actor="%s")", + overlayable_item.overlayable->name.c_str(), + overlayable_item.overlayable->actor.c_str()); + const auto policy_subsection = StringPrintf(R"(policies="%s")", + OverlayablePoliciesToString(overlayable_item.policies).c_str()); + const auto value = + StringPrintf("%s/%s", to_string(type->type).data(), entry->name.c_str()); + items.push_back(DumpOverlayableEntry{overlayable_section, policy_subsection, value}); + } + } + } + } + + std::sort(items.begin(), items.end(), + [](const DumpOverlayableEntry& a, const DumpOverlayableEntry& b) { + if (a.overlayable_section != b.overlayable_section) { + return a.overlayable_section < b.overlayable_section; + } + if (a.policy_subsection != b.policy_subsection) { + return a.policy_subsection < b.policy_subsection; + } + return a.resource_name < b.resource_name; + }); + + std::string last_overlayable_section; + std::string last_policy_subsection; + for (const auto& item : items) { + if (last_overlayable_section != item.overlayable_section) { + printer->Println(item.overlayable_section); + last_overlayable_section = item.overlayable_section; + } + if (last_policy_subsection != item.policy_subsection) { + printer->Indent(); + printer->Println(item.policy_subsection); + last_policy_subsection = item.policy_subsection; + printer->Undent(); + } + printer->Indent(); + printer->Indent(); + printer->Println(item.resource_name); + printer->Undent(); + printer->Undent(); + } +} + } // namespace aapt diff --git a/tools/aapt2/Debug.h b/tools/aapt2/Debug.h index a43197cacf7b..9443d606d7e5 100644 --- a/tools/aapt2/Debug.h +++ b/tools/aapt2/Debug.h @@ -39,6 +39,7 @@ struct Debug { static void DumpHex(const void* data, size_t len); static void DumpXml(const xml::XmlResource& doc, text::Printer* printer); static void DumpResStringPool(const android::ResStringPool* pool, text::Printer* printer); + static void DumpOverlayable(const ResourceTable& table, text::Printer* printer); }; } // namespace aapt diff --git a/tools/aapt2/Resource.h b/tools/aapt2/Resource.h index 67ba895e51d1..c49c370bcc44 100644 --- a/tools/aapt2/Resource.h +++ b/tools/aapt2/Resource.h @@ -147,10 +147,11 @@ struct ResourceId { ResourceId(uint32_t res_id); // NOLINT(google-explicit-constructor) ResourceId(uint8_t p, uint8_t t, uint16_t e); - bool is_valid() const; + // Returns true if the ID is a valid ID that is not dynamic (package ID cannot be 0) + bool is_valid_static() const; // Returns true if the ID is a valid ID or dynamic ID (package ID can be 0). - bool is_valid_dynamic() const; + bool is_valid() const; uint8_t package_id() const; uint8_t type_id() const; @@ -233,11 +234,11 @@ inline ResourceId::ResourceId(uint32_t res_id) : id(res_id) {} inline ResourceId::ResourceId(uint8_t p, uint8_t t, uint16_t e) : id((p << 24) | (t << 16) | e) {} -inline bool ResourceId::is_valid() const { +inline bool ResourceId::is_valid_static() const { return (id & 0xff000000u) != 0 && (id & 0x00ff0000u) != 0; } -inline bool ResourceId::is_valid_dynamic() const { +inline bool ResourceId::is_valid() const { return (id & 0x00ff0000u) != 0; } diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp index 40eae545c06b..234cbc4b37e0 100644 --- a/tools/aapt2/ResourceParser.cpp +++ b/tools/aapt2/ResourceParser.cpp @@ -32,11 +32,15 @@ #include "util/Util.h" #include "xml/XmlPullParser.h" +#include "idmap2/Policies.h" + using ::aapt::ResourceUtils::StringBuilder; using ::aapt::text::Utf8Iterator; using ::android::ConfigDescription; using ::android::StringPiece; +using android::idmap2::policy::kPolicyStringToFlag; + namespace aapt { constexpr const char* sXliffNamespaceUri = "urn:oasis:names:tc:xliff:document:1.2"; @@ -769,16 +773,14 @@ std::unique_ptr<Item> ResourceParser::ParseXml(xml::XmlPullParser* parser, return std::move(string); } - // If the text is empty, and the value is not allowed to be a string, encode it as a @null. - if (util::TrimWhitespace(raw_value).empty()) { - return ResourceUtils::MakeNull(); - } - if (allow_raw_value) { // We can't parse this so return a RawString if we are allowed. return util::make_unique<RawString>( table_->string_pool.MakeRef(util::TrimWhitespace(raw_value), StringPool::Context(config_))); + } else if (util::TrimWhitespace(raw_value).empty()) { + // If the text is empty, and the value is not allowed to be a string, encode it as a @null. + return ResourceUtils::MakeNull(); } return {}; } @@ -1065,7 +1067,7 @@ bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource bool error = false; std::string comment; - OverlayableItem::PolicyFlags current_policies = OverlayableItem::Policy::kNone; + PolicyFlags current_policies = PolicyFlags::NONE; const size_t start_depth = parser->depth(); while (xml::XmlPullParser::IsGoodEvent(parser->Next())) { xml::XmlPullParser::Event event = parser->event(); @@ -1075,7 +1077,7 @@ bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource } else if (event == xml::XmlPullParser::Event::kEndElement && parser->depth() == start_depth + 1) { // Clear the current policies when exiting the <policy> tags - current_policies = OverlayableItem::Policy::kNone; + current_policies = PolicyFlags::NONE; continue; } else if (event == xml::XmlPullParser::Event::kComment) { // Retrieve the comment of individual <item> tags @@ -1090,7 +1092,7 @@ bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource const std::string& element_name = parser->element_name(); const std::string& element_namespace = parser->element_namespace(); if (element_namespace.empty() && element_name == "item") { - if (current_policies == OverlayableItem::Policy::kNone) { + if (current_policies == PolicyFlags::NONE) { diag_->Error(DiagMessage(element_source) << "<item> within an <overlayable> must be inside a <policy> block"); error = true; @@ -1135,7 +1137,7 @@ bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource out_resource->child_resources.push_back(std::move(child_resource)); } else if (element_namespace.empty() && element_name == "policy") { - if (current_policies != OverlayableItem::Policy::kNone) { + if (current_policies != PolicyFlags::NONE) { // If the policy list is not empty, then we are currently inside a policy element diag_->Error(DiagMessage(element_source) << "<policy> blocks cannot be recursively nested"); error = true; @@ -1143,21 +1145,14 @@ bool ResourceParser::ParseOverlayable(xml::XmlPullParser* parser, ParsedResource } else if (Maybe<StringPiece> maybe_type = xml::FindNonEmptyAttribute(parser, "type")) { // Parse the polices separated by vertical bar characters to allow for specifying multiple // policies. Items within the policy tag will have the specified policy. - static const auto kPolicyMap = - ImmutableMap<StringPiece, OverlayableItem::Policy>::CreatePreSorted({ - {"odm", OverlayableItem::Policy::kOdm}, - {"oem", OverlayableItem::Policy::kOem}, - {"product", OverlayableItem::Policy::kProduct}, - {"public", OverlayableItem::Policy::kPublic}, - {"signature", OverlayableItem::Policy::kSignature}, - {"system", OverlayableItem::Policy::kSystem}, - {"vendor", OverlayableItem::Policy::kVendor}, - }); - for (const StringPiece& part : util::Tokenize(maybe_type.value(), '|')) { StringPiece trimmed_part = util::TrimWhitespace(part); - const auto policy = kPolicyMap.find(trimmed_part); - if (policy == kPolicyMap.end()) { + const auto policy = std::find_if(kPolicyStringToFlag.begin(), + kPolicyStringToFlag.end(), + [trimmed_part](const auto& it) { + return trimmed_part == it.first; + }); + if (policy == kPolicyStringToFlag.end()) { diag_->Error(DiagMessage(element_source) << "<policy> has unsupported type '" << trimmed_part << "'"); error = true; @@ -1389,7 +1384,7 @@ Maybe<Attribute::Symbol> ResourceParser::ParseEnumOrFlagItem( return Attribute::Symbol{ Reference(ResourceNameRef({}, ResourceType::kId, maybe_name.value())), - val.data}; + val.data, val.dataType}; } bool ResourceParser::ParseStyleItem(xml::XmlPullParser* parser, Style* style) { diff --git a/tools/aapt2/ResourceParser_test.cpp b/tools/aapt2/ResourceParser_test.cpp index 46ad7cbe49e9..9b70079a98c9 100644 --- a/tools/aapt2/ResourceParser_test.cpp +++ b/tools/aapt2/ResourceParser_test.cpp @@ -41,6 +41,8 @@ using ::testing::Pointee; using ::testing::SizeIs; using ::testing::StrEq; +using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags; + namespace aapt { constexpr const char* kXmlPreamble = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"; @@ -401,7 +403,7 @@ TEST_F(ResourceParserTest, ParseEnumAttr) { std::string input = R"( <attr name="foo"> <enum name="bar" value="0"/> - <enum name="bat" value="1"/> + <enum name="bat" value="0x1"/> <enum name="baz" value="2"/> </attr>)"; ASSERT_TRUE(TestParse(input)); @@ -414,14 +416,17 @@ TEST_F(ResourceParserTest, ParseEnumAttr) { ASSERT_TRUE(enum_attr->symbols[0].symbol.name); EXPECT_THAT(enum_attr->symbols[0].symbol.name.value().entry, Eq("bar")); EXPECT_THAT(enum_attr->symbols[0].value, Eq(0u)); + EXPECT_THAT(enum_attr->symbols[0].type, Eq(Res_value::TYPE_INT_DEC)); ASSERT_TRUE(enum_attr->symbols[1].symbol.name); EXPECT_THAT(enum_attr->symbols[1].symbol.name.value().entry, Eq("bat")); EXPECT_THAT(enum_attr->symbols[1].value, Eq(1u)); + EXPECT_THAT(enum_attr->symbols[1].type, Eq(Res_value::TYPE_INT_HEX)); ASSERT_TRUE(enum_attr->symbols[2].symbol.name); EXPECT_THAT(enum_attr->symbols[2].symbol.name.value().entry, Eq("baz")); EXPECT_THAT(enum_attr->symbols[2].value, Eq(2u)); + EXPECT_THAT(enum_attr->symbols[2].type, Eq(Res_value::TYPE_INT_DEC)); } TEST_F(ResourceParserTest, ParseFlagAttr) { @@ -956,7 +961,7 @@ TEST_F(ResourceParserTest, ParseOverlayable) { OverlayableItem& result_overlayable_item = search_result.value().entry->overlayable_item.value(); EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); EXPECT_THAT(result_overlayable_item.overlayable->actor, Eq("overlay://theme")); - EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kSignature)); + EXPECT_THAT(result_overlayable_item.policies, Eq(PolicyFlags::SIGNATURE)); search_result = table_.FindResource(test::ParseNameOrDie("drawable/bar")); ASSERT_TRUE(search_result); @@ -965,7 +970,7 @@ TEST_F(ResourceParserTest, ParseOverlayable) { result_overlayable_item = search_result.value().entry->overlayable_item.value(); EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); EXPECT_THAT(result_overlayable_item.overlayable->actor, Eq("overlay://theme")); - EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kSignature)); + EXPECT_THAT(result_overlayable_item.policies, Eq(PolicyFlags::SIGNATURE)); } TEST_F(ResourceParserTest, ParseOverlayableRequiresName) { @@ -1002,6 +1007,9 @@ TEST_F(ResourceParserTest, ParseOverlayablePolicy) { <policy type="oem"> <item type="string" name="buz" /> </policy> + <policy type="actor"> + <item type="string" name="actor" /> + </policy> </overlayable>)"; ASSERT_TRUE(TestParse(input)); @@ -1011,7 +1019,7 @@ TEST_F(ResourceParserTest, ParseOverlayablePolicy) { ASSERT_TRUE(search_result.value().entry->overlayable_item); OverlayableItem result_overlayable_item = search_result.value().entry->overlayable_item.value(); EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); - EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kProduct)); + EXPECT_THAT(result_overlayable_item.policies, Eq(PolicyFlags::PRODUCT_PARTITION)); search_result = table_.FindResource(test::ParseNameOrDie("string/fiz")); ASSERT_TRUE(search_result); @@ -1019,7 +1027,7 @@ TEST_F(ResourceParserTest, ParseOverlayablePolicy) { ASSERT_TRUE(search_result.value().entry->overlayable_item); result_overlayable_item = search_result.value().entry->overlayable_item.value(); EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); - EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kSystem)); + EXPECT_THAT(result_overlayable_item.policies, Eq(PolicyFlags::SYSTEM_PARTITION)); search_result = table_.FindResource(test::ParseNameOrDie("string/fuz")); ASSERT_TRUE(search_result); @@ -1027,7 +1035,7 @@ TEST_F(ResourceParserTest, ParseOverlayablePolicy) { ASSERT_TRUE(search_result.value().entry->overlayable_item); result_overlayable_item = search_result.value().entry->overlayable_item.value(); EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); - EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kVendor)); + EXPECT_THAT(result_overlayable_item.policies, Eq(PolicyFlags::VENDOR_PARTITION)); search_result = table_.FindResource(test::ParseNameOrDie("string/faz")); ASSERT_TRUE(search_result); @@ -1035,7 +1043,7 @@ TEST_F(ResourceParserTest, ParseOverlayablePolicy) { ASSERT_TRUE(search_result.value().entry->overlayable_item); result_overlayable_item = search_result.value().entry->overlayable_item.value(); EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); - EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kPublic)); + EXPECT_THAT(result_overlayable_item.policies, Eq(PolicyFlags::PUBLIC)); search_result = table_.FindResource(test::ParseNameOrDie("string/foz")); ASSERT_TRUE(search_result); @@ -1043,7 +1051,7 @@ TEST_F(ResourceParserTest, ParseOverlayablePolicy) { ASSERT_TRUE(search_result.value().entry->overlayable_item); result_overlayable_item = search_result.value().entry->overlayable_item.value(); EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); - EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kSignature)); + EXPECT_THAT(result_overlayable_item.policies, Eq(PolicyFlags::SIGNATURE)); search_result = table_.FindResource(test::ParseNameOrDie("string/biz")); ASSERT_TRUE(search_result); @@ -1051,7 +1059,7 @@ TEST_F(ResourceParserTest, ParseOverlayablePolicy) { ASSERT_TRUE(search_result.value().entry->overlayable_item); result_overlayable_item = search_result.value().entry->overlayable_item.value(); EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); - EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kOdm)); + EXPECT_THAT(result_overlayable_item.policies, Eq(PolicyFlags::ODM_PARTITION)); search_result = table_.FindResource(test::ParseNameOrDie("string/buz")); ASSERT_TRUE(search_result); @@ -1059,7 +1067,15 @@ TEST_F(ResourceParserTest, ParseOverlayablePolicy) { ASSERT_TRUE(search_result.value().entry->overlayable_item); result_overlayable_item = search_result.value().entry->overlayable_item.value(); EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); - EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kOem)); + EXPECT_THAT(result_overlayable_item.policies, Eq(PolicyFlags::OEM_PARTITION)); + + search_result = table_.FindResource(test::ParseNameOrDie("string/actor")); + ASSERT_TRUE(search_result); + ASSERT_THAT(search_result.value().entry, NotNull()); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + result_overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); + EXPECT_THAT(result_overlayable_item.policies, Eq(PolicyFlags::ACTOR_SIGNATURE)); } TEST_F(ResourceParserTest, ParseOverlayableNoPolicyError) { @@ -1122,8 +1138,8 @@ TEST_F(ResourceParserTest, ParseOverlayableMultiplePolicy) { ASSERT_TRUE(search_result.value().entry->overlayable_item); OverlayableItem result_overlayable_item = search_result.value().entry->overlayable_item.value(); EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); - EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kVendor - | OverlayableItem::Policy::kPublic)); + EXPECT_THAT(result_overlayable_item.policies, Eq(PolicyFlags::VENDOR_PARTITION + | PolicyFlags::PUBLIC)); search_result = table_.FindResource(test::ParseNameOrDie("string/bar")); ASSERT_TRUE(search_result); @@ -1131,8 +1147,8 @@ TEST_F(ResourceParserTest, ParseOverlayableMultiplePolicy) { ASSERT_TRUE(search_result.value().entry->overlayable_item); result_overlayable_item = search_result.value().entry->overlayable_item.value(); EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("Name")); - EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kProduct - | OverlayableItem::Policy::kSystem)); + EXPECT_THAT(result_overlayable_item.policies, Eq(PolicyFlags::PRODUCT_PARTITION + | PolicyFlags::SYSTEM_PARTITION)); } TEST_F(ResourceParserTest, DuplicateOverlayableIsError) { diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp index 836e199593fc..e0a9a31eee8b 100644 --- a/tools/aapt2/ResourceTable.cpp +++ b/tools/aapt2/ResourceTable.cpp @@ -267,8 +267,7 @@ bool ResourceEntry::HasDefaultValue() const { // A DECL will override a USE without error. Two DECLs must match in their format for there to be // no error. ResourceTable::CollisionResult ResourceTable::ResolveValueCollision(Value* existing, - Value* incoming, - bool overlay) { + Value* incoming) { Attribute* existing_attr = ValueCast<Attribute>(existing); Attribute* incoming_attr = ValueCast<Attribute>(incoming); if (!incoming_attr) { @@ -282,7 +281,7 @@ ResourceTable::CollisionResult ResourceTable::ResolveValueCollision(Value* exist } // The existing and incoming values are strong, this is an error // if the values are not both attributes. - return overlay ? CollisionResult::kTakeNew : CollisionResult::kConflict; + return CollisionResult::kConflict; } if (!existing_attr) { @@ -293,7 +292,7 @@ ResourceTable::CollisionResult ResourceTable::ResolveValueCollision(Value* exist } // The existing value is not an attribute and it is strong, // so the incoming attribute value is an error. - return overlay ? CollisionResult::kTakeNew : CollisionResult::kConflict; + return CollisionResult::kConflict; } CHECK(incoming_attr != nullptr && existing_attr != nullptr); @@ -324,9 +323,8 @@ ResourceTable::CollisionResult ResourceTable::ResolveValueCollision(Value* exist return CollisionResult::kConflict; } -ResourceTable::CollisionResult ResourceTable::IgnoreCollision(Value* /* existing */, - Value* /* incoming */, - bool /* overlay */) { +ResourceTable::CollisionResult ResourceTable::IgnoreCollision(Value* /** existing **/, + Value* /** incoming **/) { return CollisionResult::kKeepBoth; } @@ -400,7 +398,7 @@ bool ResourceTable::AddResourceImpl(const ResourceNameRef& name, const ResourceI // Check for package names appearing twice with two different package ids ResourceTablePackage* package = FindOrCreatePackage(name.package); - if (res_id.is_valid_dynamic() && package->id && package->id.value() != res_id.package_id()) { + if (res_id.is_valid() && package->id && package->id.value() != res_id.package_id()) { diag->Error(DiagMessage(source) << "trying to add resource '" << name << "' with ID " << res_id << " but package '" << package->name << "' already has ID " @@ -409,9 +407,9 @@ bool ResourceTable::AddResourceImpl(const ResourceNameRef& name, const ResourceI } // Whether or not to error on duplicate resources - bool check_id = validate_resources_ && res_id.is_valid_dynamic(); + bool check_id = validate_resources_ && res_id.is_valid(); // Whether or not to create a duplicate resource if the id does not match - bool use_id = !validate_resources_ && res_id.is_valid_dynamic(); + bool use_id = !validate_resources_ && res_id.is_valid(); ResourceTableType* type = package->FindOrCreateType(name.type, use_id ? res_id.type_id() : Maybe<uint8_t>()); @@ -442,7 +440,7 @@ bool ResourceTable::AddResourceImpl(const ResourceNameRef& name, const ResourceI // Resource does not exist, add it now. config_value->value = std::move(value); } else { - switch (conflict_resolver(config_value->value.get(), value.get(), false /* overlay */)) { + switch (conflict_resolver(config_value->value.get(), value.get())) { case CollisionResult::kKeepBoth: // Insert the value ignoring for duplicate configurations entry->values.push_back(util::make_unique<ResourceConfigValue>(config, product)); @@ -465,7 +463,7 @@ bool ResourceTable::AddResourceImpl(const ResourceNameRef& name, const ResourceI } } - if (res_id.is_valid_dynamic()) { + if (res_id.is_valid()) { package->id = res_id.package_id(); type->id = res_id.type_id(); entry->id = res_id.entry_id(); @@ -506,7 +504,7 @@ bool ResourceTable::SetVisibilityImpl(const ResourceNameRef& name, const Visibil // Check for package names appearing twice with two different package ids ResourceTablePackage* package = FindOrCreatePackage(name.package); - if (res_id.is_valid_dynamic() && package->id && package->id.value() != res_id.package_id()) { + if (res_id.is_valid() && package->id && package->id.value() != res_id.package_id()) { diag->Error(DiagMessage(source) << "trying to add resource '" << name << "' with ID " << res_id << " but package '" << package->name << "' already has ID " @@ -515,9 +513,9 @@ bool ResourceTable::SetVisibilityImpl(const ResourceNameRef& name, const Visibil } // Whether or not to error on duplicate resources - bool check_id = validate_resources_ && res_id.is_valid_dynamic(); + bool check_id = validate_resources_ && res_id.is_valid(); // Whether or not to create a duplicate resource if the id does not match - bool use_id = !validate_resources_ && res_id.is_valid_dynamic(); + bool use_id = !validate_resources_ && res_id.is_valid(); ResourceTableType* type = package->FindOrCreateType(name.type, use_id ? res_id.type_id() : Maybe<uint8_t>()); @@ -543,7 +541,7 @@ bool ResourceTable::SetVisibilityImpl(const ResourceNameRef& name, const Visibil return false; } - if (res_id.is_valid_dynamic()) { + if (res_id.is_valid()) { package->id = res_id.package_id(); type->id = res_id.type_id(); entry->id = res_id.entry_id(); diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h index e8793800b148..93a7a314d6ba 100644 --- a/tools/aapt2/ResourceTable.h +++ b/tools/aapt2/ResourceTable.h @@ -36,6 +36,8 @@ #include <unordered_map> #include <vector> +using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags; + namespace aapt { // The Public status of a resource. @@ -75,36 +77,8 @@ struct Overlayable { struct OverlayableItem { explicit OverlayableItem(const std::shared_ptr<Overlayable>& overlayable) : overlayable(overlayable) {} - - // Represents the types overlays that are allowed to overlay the resource. - typedef uint32_t PolicyFlags; - enum Policy : uint32_t { - kNone = 0x00000000, - - // The resource can be overlaid by any overlay. - kPublic = 0x00000001, - - // The resource can be overlaid by any overlay on the system partition. - kSystem = 0x00000002, - - // The resource can be overlaid by any overlay on the vendor partition. - kVendor = 0x00000004, - - // The resource can be overlaid by any overlay on the product partition. - kProduct = 0x00000008, - - // The resource can be overlaid by any overlay signed with the same signature as its actor. - kSignature = 0x00000010, - - // The resource can be overlaid by any overlay on the odm partition. - kOdm = 0x00000020, - - // The resource can be overlaid by any overlay on the oem partition. - kOem = 0x00000040, - }; - std::shared_ptr<Overlayable> overlayable; - PolicyFlags policies = Policy::kNone; + PolicyFlags policies = PolicyFlags::NONE; std::string comment; Source source; }; @@ -228,13 +202,13 @@ class ResourceTable { enum class CollisionResult { kKeepBoth, kKeepOriginal, kConflict, kTakeNew }; - using CollisionResolverFunc = std::function<CollisionResult(Value*, Value*, bool)>; + using CollisionResolverFunc = std::function<CollisionResult(Value*, Value*)>; // When a collision of resources occurs, this method decides which value to keep. - static CollisionResult ResolveValueCollision(Value* existing, Value* incoming, bool overlay); + static CollisionResult ResolveValueCollision(Value* existing, Value* incoming); // When a collision of resources occurs, this method keeps both values - static CollisionResult IgnoreCollision(Value* existing, Value* incoming, bool overlay); + static CollisionResult IgnoreCollision(Value* existing, Value* incoming); bool AddResource(const ResourceNameRef& name, const android::ConfigDescription& config, const android::StringPiece& product, std::unique_ptr<Value> value, diff --git a/tools/aapt2/ResourceTable_test.cpp b/tools/aapt2/ResourceTable_test.cpp index b97dc6b205ca..9271a7e6bae1 100644 --- a/tools/aapt2/ResourceTable_test.cpp +++ b/tools/aapt2/ResourceTable_test.cpp @@ -30,6 +30,8 @@ using ::testing::Eq; using ::testing::NotNull; using ::testing::StrEq; +using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags; + namespace aapt { TEST(ResourceTableTest, FailToAddResourceWithBadName) { @@ -247,8 +249,8 @@ TEST(ResourceTableTest, SetOverlayable) { auto overlayable = std::make_shared<Overlayable>("Name", "overlay://theme", Source("res/values/overlayable.xml", 40)); OverlayableItem overlayable_item(overlayable); - overlayable_item.policies |= OverlayableItem::Policy::kProduct; - overlayable_item.policies |= OverlayableItem::Policy::kVendor; + overlayable_item.policies |= PolicyFlags::PRODUCT_PARTITION; + overlayable_item.policies |= PolicyFlags::VENDOR_PARTITION; overlayable_item.comment = "comment"; overlayable_item.source = Source("res/values/overlayable.xml", 42); @@ -264,8 +266,8 @@ TEST(ResourceTableTest, SetOverlayable) { EXPECT_THAT(result_overlayable_item.overlayable->actor, Eq("overlay://theme")); EXPECT_THAT(result_overlayable_item.overlayable->source.path, Eq("res/values/overlayable.xml")); EXPECT_THAT(result_overlayable_item.overlayable->source.line, 40); - EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kProduct - | OverlayableItem::Policy::kVendor)); + EXPECT_THAT(result_overlayable_item.policies, Eq(PolicyFlags::PRODUCT_PARTITION + | PolicyFlags::VENDOR_PARTITION)); ASSERT_THAT(result_overlayable_item.comment, StrEq("comment")); EXPECT_THAT(result_overlayable_item.source.path, Eq("res/values/overlayable.xml")); EXPECT_THAT(result_overlayable_item.source.line, 42); @@ -277,17 +279,17 @@ TEST(ResourceTableTest, SetMultipleOverlayableResources) { const ResourceName foo = test::ParseNameOrDie("android:string/foo"); auto group = std::make_shared<Overlayable>("Name", "overlay://theme"); OverlayableItem overlayable(group); - overlayable.policies = OverlayableItem::Policy::kProduct; + overlayable.policies = PolicyFlags::PRODUCT_PARTITION; ASSERT_TRUE(table.SetOverlayable(foo, overlayable, test::GetDiagnostics())); const ResourceName bar = test::ParseNameOrDie("android:string/bar"); OverlayableItem overlayable2(group); - overlayable2.policies = OverlayableItem::Policy::kProduct; + overlayable2.policies = PolicyFlags::PRODUCT_PARTITION; ASSERT_TRUE(table.SetOverlayable(bar, overlayable2, test::GetDiagnostics())); const ResourceName baz = test::ParseNameOrDie("android:string/baz"); OverlayableItem overlayable3(group); - overlayable3.policies = OverlayableItem::Policy::kVendor; + overlayable3.policies = PolicyFlags::VENDOR_PARTITION; ASSERT_TRUE(table.SetOverlayable(baz, overlayable3, test::GetDiagnostics())); } @@ -296,12 +298,12 @@ TEST(ResourceTableTest, SetOverlayableDifferentResourcesDifferentName) { const ResourceName foo = test::ParseNameOrDie("android:string/foo"); OverlayableItem overlayable_item(std::make_shared<Overlayable>("Name", "overlay://theme")); - overlayable_item.policies = OverlayableItem::Policy::kProduct; + overlayable_item.policies = PolicyFlags::PRODUCT_PARTITION; ASSERT_TRUE(table.SetOverlayable(foo, overlayable_item, test::GetDiagnostics())); const ResourceName bar = test::ParseNameOrDie("android:string/bar"); OverlayableItem overlayable_item2(std::make_shared<Overlayable>("Name2", "overlay://theme")); - overlayable_item2.policies = OverlayableItem::Policy::kProduct; + overlayable_item2.policies = PolicyFlags::PRODUCT_PARTITION; ASSERT_TRUE(table.SetOverlayable(bar, overlayable_item2, test::GetDiagnostics())); } diff --git a/tools/aapt2/ResourceUtils.cpp b/tools/aapt2/ResourceUtils.cpp index e0040e486a23..469128b1e50b 100644 --- a/tools/aapt2/ResourceUtils.cpp +++ b/tools/aapt2/ResourceUtils.cpp @@ -378,7 +378,7 @@ std::unique_ptr<BinaryPrimitive> TryParseEnumSymbol(const Attribute* enum_attr, const ResourceName& enum_symbol_resource_name = symbol.symbol.name.value(); if (trimmed_str == enum_symbol_resource_name.entry) { android::Res_value value = {}; - value.dataType = android::Res_value::TYPE_INT_DEC; + value.dataType = symbol.type; value.data = symbol.value; return util::make_unique<BinaryPrimitive>(value); } @@ -516,7 +516,7 @@ Maybe<ResourceId> ParseResourceId(const StringPiece& str) { if (android::ResTable::stringToInt(str16.data(), str16.size(), &value)) { if (value.dataType == android::Res_value::TYPE_INT_HEX) { ResourceId id(value.data); - if (id.is_valid_dynamic()) { + if (id.is_valid()) { return id; } } @@ -738,7 +738,13 @@ std::unique_ptr<Item> ParseBinaryResValue(const ResourceType& type, const Config const android::Res_value& res_value, StringPool* dst_pool) { if (type == ResourceType::kId) { - return util::make_unique<Id>(); + if (res_value.dataType != android::Res_value::TYPE_REFERENCE && + res_value.dataType != android::Res_value::TYPE_DYNAMIC_REFERENCE) { + // plain "id" resources are actually encoded as dummy values (aapt1 uses an empty string, + // while aapt2 uses a false boolean). + return util::make_unique<Id>(); + } + // fall through to regular reference deserialization logic } const uint32_t data = util::DeviceToHost32(res_value.data); @@ -794,7 +800,12 @@ std::unique_ptr<Item> ParseBinaryResValue(const ResourceType& type, const Config } // This is a normal reference. - return util::make_unique<Reference>(data, ref_type); + auto reference = util::make_unique<Reference>(data, ref_type); + if (res_value.dataType == android::Res_value::TYPE_DYNAMIC_REFERENCE || + res_value.dataType == android::Res_value::TYPE_DYNAMIC_ATTRIBUTE) { + reference->is_dynamic = true; + } + return reference; } break; } diff --git a/tools/aapt2/ResourceUtils_test.cpp b/tools/aapt2/ResourceUtils_test.cpp index c016cb44af00..b08bf9a1ff17 100644 --- a/tools/aapt2/ResourceUtils_test.cpp +++ b/tools/aapt2/ResourceUtils_test.cpp @@ -109,6 +109,20 @@ TEST(ResourceUtilsTest, ParsePrivateReference) { EXPECT_TRUE(private_ref); } +TEST(ResourceUtilsTest, ParseBinaryDynamicReference) { + android::Res_value value = {}; + value.data = util::HostToDevice32(0x01); + value.dataType = android::Res_value::TYPE_DYNAMIC_REFERENCE; + std::unique_ptr<Item> item = ResourceUtils::ParseBinaryResValue(ResourceType::kId, + android::ConfigDescription(), + android::ResStringPool(), value, + nullptr); + + Reference* ref = ValueCast<Reference>(item.get()); + EXPECT_TRUE(ref->is_dynamic); + EXPECT_EQ(ref->id.value().id, 0x01); +} + TEST(ResourceUtilsTest, FailToParseAutoCreateNonIdReference) { bool create = false; bool private_ref = false; diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp index 696012786e6d..4f0fa8ae29ba 100644 --- a/tools/aapt2/ResourceValues.cpp +++ b/tools/aapt2/ResourceValues.cpp @@ -117,7 +117,7 @@ bool Reference::Equals(const Value* value) const { bool Reference::Flatten(android::Res_value* out_value) const { const ResourceId resid = id.value_or_default(ResourceId(0)); - const bool dynamic = resid.is_valid_dynamic() && is_dynamic; + const bool dynamic = resid.is_valid() && is_dynamic; if (reference_type == Reference::Type::kResource) { if (dynamic) { @@ -159,7 +159,7 @@ void Reference::Print(std::ostream* out) const { *out << name.value(); } - if (id && id.value().is_valid_dynamic()) { + if (id && id.value().is_valid()) { if (name) { *out << " "; } @@ -196,7 +196,7 @@ static void PrettyPrintReferenceImpl(const Reference& ref, bool print_package, P printer->Print("/"); printer->Print(name.entry); } - } else if (ref.id && ref.id.value().is_valid_dynamic()) { + } else if (ref.id && ref.id.value().is_valid()) { printer->Print(ref.id.value().to_string()); } } @@ -574,10 +574,6 @@ bool Attribute::Equals(const Value* value) const { } bool Attribute::IsCompatibleWith(const Attribute& attr) const { - if (Equals(&attr)) { - return true; - } - // If the high bits are set on any of these attribute type masks, then they are incompatible. // We don't check that flags and enums are identical. if ((type_mask & ~android::ResTable_map::TYPE_ANY) != 0 || diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h index 168ad61784e7..fe0883be50aa 100644 --- a/tools/aapt2/ResourceValues.h +++ b/tools/aapt2/ResourceValues.h @@ -292,6 +292,7 @@ struct Attribute : public BaseValue<Attribute> { struct Symbol { Reference symbol; uint32_t value; + uint8_t type; friend std::ostream& operator<<(std::ostream& out, const Symbol& symbol); }; diff --git a/tools/aapt2/ResourceValues_test.cpp b/tools/aapt2/ResourceValues_test.cpp index dbf51143f720..c4a1108ac62a 100644 --- a/tools/aapt2/ResourceValues_test.cpp +++ b/tools/aapt2/ResourceValues_test.cpp @@ -284,58 +284,8 @@ TEST(ResourcesValuesTest, AttributeIsCompatible) { EXPECT_FALSE(attr_three.IsCompatibleWith(attr_one)); EXPECT_FALSE(attr_three.IsCompatibleWith(attr_two)); - EXPECT_TRUE(attr_three.IsCompatibleWith(attr_three)); + EXPECT_FALSE(attr_three.IsCompatibleWith(attr_three)); EXPECT_FALSE(attr_three.IsCompatibleWith(attr_four)); - - EXPECT_FALSE(attr_four.IsCompatibleWith(attr_one)); - EXPECT_FALSE(attr_four.IsCompatibleWith(attr_two)); - EXPECT_FALSE(attr_four.IsCompatibleWith(attr_three)); - EXPECT_TRUE(attr_four.IsCompatibleWith(attr_four)); -} - -TEST(ResourcesValuesTest, AttributeEnumIsCompatible) { - Attribute attr_one(TYPE_ENUM); - attr_one.symbols.push_back( - Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/foo")), 0x01u}); - attr_one.symbols.push_back( - Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/bar")), 0x07u}); - - Attribute attr_two(TYPE_ENUM); - attr_two.symbols.push_back( - Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/foo")), 0x01u}); - attr_two.symbols.push_back( - Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/bar")), 0x07u}); - EXPECT_TRUE(attr_one.IsCompatibleWith(attr_two)); -} - -TEST(ResourcesValuesTest, DifferentAttributeEnumDifferentNameIsNotCompatible) { - Attribute attr_one(TYPE_ENUM); - attr_one.symbols.push_back( - Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/foo")), 0x01u}); - attr_one.symbols.push_back( - Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/bar")), 0x07u}); - - Attribute attr_two(TYPE_ENUM); - attr_two.symbols.push_back( - Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/foo")), 0x01u}); - attr_one.symbols.push_back( - Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/baz")), 0x07u}); - EXPECT_FALSE(attr_one.IsCompatibleWith(attr_two)); -} - -TEST(ResourcesValuesTest, DifferentAttributeEnumDifferentValueIsNotCompatible) { - Attribute attr_one(TYPE_ENUM); - attr_one.symbols.push_back( - Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/foo")), 0x01u}); - attr_one.symbols.push_back( - Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/bar")), 0x07u}); - - Attribute attr_two(TYPE_ENUM); - attr_two.symbols.push_back( - Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/foo")), 0x01u}); - attr_two.symbols.push_back( - Attribute::Symbol{Reference(test::ParseNameOrDie("android:id/bar")), 0x09u}); - EXPECT_FALSE(attr_one.IsCompatibleWith(attr_two)); } } // namespace aapt diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto index b2fc08423d34..ab9ce66b0ae3 100644 --- a/tools/aapt2/Resources.proto +++ b/tools/aapt2/Resources.proto @@ -21,7 +21,6 @@ import "frameworks/base/tools/aapt2/Configuration.proto"; package aapt.pb; option java_package = "com.android.aapt"; -option optimize_for = LITE_RUNTIME; // A string pool that wraps the binary form of the C++ class android::ResStringPool. message StringPool { @@ -168,6 +167,7 @@ message OverlayableItem { SIGNATURE = 5; ODM = 6; OEM = 7; + ACTOR = 8; } // The location of the <item> declaration in source. @@ -270,6 +270,11 @@ message CompoundValue { } } +// Message holding a boolean, so it can be optionally encoded. +message Boolean { + bool value = 1; +} + // A value that is a reference to another resource. This reference can be by name or resource ID. message Reference { enum Type { @@ -290,6 +295,9 @@ message Reference { // Whether this reference is referencing a private resource (@*package:type/entry). bool private = 4; + + // Whether this reference is dynamic. + Boolean is_dynamic = 5; } // A value that represents an ID. This is just a placeholder, as ID values are used to occupy a @@ -388,6 +396,9 @@ message Attribute { // The value of the enum/flag. uint32 value = 4; + + // The data type of the enum/flag as defined in android::Res_value. + uint32 type = 5; } // Bitmask of formats allowed for an attribute. diff --git a/tools/aapt2/ResourcesInternal.proto b/tools/aapt2/ResourcesInternal.proto index 520b242ee509..b0ed3da33368 100644 --- a/tools/aapt2/ResourcesInternal.proto +++ b/tools/aapt2/ResourcesInternal.proto @@ -22,7 +22,6 @@ import "frameworks/base/tools/aapt2/Resources.proto"; package aapt.pb.internal; option java_package = "android.aapt.pb.internal"; -option optimize_for = LITE_RUNTIME; // The top level message representing an external resource file (layout XML, PNG, etc). // This is used to represent a compiled file before it is linked. Only useful to aapt2. diff --git a/tools/aapt2/cmd/Compile.cpp b/tools/aapt2/cmd/Compile.cpp index 21719705838d..32686538c10d 100644 --- a/tools/aapt2/cmd/Compile.cpp +++ b/tools/aapt2/cmd/Compile.cpp @@ -630,6 +630,12 @@ class CompileContext : public IAaptContext { return 0; } + const std::set<std::string>& GetSplitNameDependencies() override { + UNIMPLEMENTED(FATAL) << "No Split Name Dependencies be needed in compile phase"; + static std::set<std::string> empty; + return empty; + } + private: DISALLOW_COPY_AND_ASSIGN(CompileContext); @@ -735,7 +741,6 @@ int CompileCommand::Action(const std::vector<std::string>& args) { } std::unique_ptr<io::IFileCollection> file_collection; - std::unique_ptr<IArchiveWriter> archive_writer; // Collect the resources files to compile if (options_.res_dir && options_.res_zip) { @@ -756,8 +761,6 @@ int CompileCommand::Action(const std::vector<std::string>& args) { context.GetDiagnostics()->Error(DiagMessage(options_.res_dir.value()) << err); return 1; } - - archive_writer = CreateZipFileArchiveWriter(context.GetDiagnostics(), options_.output_path); } else if (options_.res_zip) { if (!args.empty()) { context.GetDiagnostics()->Error(DiagMessage() << "files given but --zip specified"); @@ -772,8 +775,6 @@ int CompileCommand::Action(const std::vector<std::string>& args) { context.GetDiagnostics()->Error(DiagMessage(options_.res_zip.value()) << err); return 1; } - - archive_writer = CreateZipFileArchiveWriter(context.GetDiagnostics(), options_.output_path); } else { auto collection = util::make_unique<io::FileCollection>(); @@ -786,7 +787,14 @@ int CompileCommand::Action(const std::vector<std::string>& args) { } file_collection = std::move(collection); + } + + std::unique_ptr<IArchiveWriter> archive_writer; + file::FileType output_file_type = file::GetFileType(options_.output_path); + if (output_file_type == file::FileType::kDirectory) { archive_writer = CreateDirectoryArchiveWriter(context.GetDiagnostics(), options_.output_path); + } else { + archive_writer = CreateZipFileArchiveWriter(context.GetDiagnostics(), options_.output_path); } if (!archive_writer) { diff --git a/tools/aapt2/cmd/Compile_test.cpp b/tools/aapt2/cmd/Compile_test.cpp index 5f637bd8d582..fb786a31360e 100644 --- a/tools/aapt2/cmd/Compile_test.cpp +++ b/tools/aapt2/cmd/Compile_test.cpp @@ -200,7 +200,7 @@ static void AssertTranslations(CommandTestFixture *ctf, std::string file_name, const std::string compiled_files_dir = ctf->GetTestPath("/compiled_" + file_name); const std::string out_apk = ctf->GetTestPath("/" + file_name + ".apk"); - CHECK(ctf->WriteFile(source_file, sTranslatableXmlContent)); + ctf->WriteFile(source_file, sTranslatableXmlContent); CHECK(file::mkdirs(compiled_files_dir.data())); ASSERT_EQ(CompileCommand(&diag).Execute({ diff --git a/tools/aapt2/cmd/Convert.cpp b/tools/aapt2/cmd/Convert.cpp index 0cf86ccdd59f..22bcd8589ce9 100644 --- a/tools/aapt2/cmd/Convert.cpp +++ b/tools/aapt2/cmd/Convert.cpp @@ -243,6 +243,12 @@ class Context : public IAaptContext { return 0u; } + const std::set<std::string>& GetSplitNameDependencies() override { + UNIMPLEMENTED(FATAL) << "Split Name Dependencies should not be necessary"; + static std::set<std::string> empty; + return empty; + } + bool verbose_ = false; std::string package_; diff --git a/tools/aapt2/cmd/Diff.cpp b/tools/aapt2/cmd/Diff.cpp index 262f4fc4e394..d56994e3ae24 100644 --- a/tools/aapt2/cmd/Diff.cpp +++ b/tools/aapt2/cmd/Diff.cpp @@ -65,6 +65,12 @@ class DiffContext : public IAaptContext { return 0; } + const std::set<std::string>& GetSplitNameDependencies() override { + UNIMPLEMENTED(FATAL) << "Split Name Dependencies should not be necessary"; + static std::set<std::string> empty; + return empty; + } + private: std::string empty_; StdErrDiagnostics diagnostics_; diff --git a/tools/aapt2/cmd/Dump.cpp b/tools/aapt2/cmd/Dump.cpp index a23a6a46cf0f..3982d12f6036 100644 --- a/tools/aapt2/cmd/Dump.cpp +++ b/tools/aapt2/cmd/Dump.cpp @@ -118,6 +118,12 @@ class DumpContext : public IAaptContext { return 0; } + const std::set<std::string>& GetSplitNameDependencies() override { + UNIMPLEMENTED(FATAL) << "Split Name Dependencies should not be necessary"; + static std::set<std::string> empty; + return empty; + } + private: StdErrDiagnostics diagnostics_; bool verbose_ = false; @@ -388,6 +394,17 @@ int DumpXmlTreeCommand::Dump(LoadedApk* apk) { return 0; } +int DumpOverlayableCommand::Dump(LoadedApk* apk) { + ResourceTable* table = apk->GetResourceTable(); + if (!table) { + GetDiagnostics()->Error(DiagMessage() << "Failed to retrieve resource table"); + return 1; + } + + Debug::DumpOverlayable(*table, GetPrinter()); + return 0; +} + const char DumpBadgerCommand::kBadgerData[2925] = { 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 95, 46, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, diff --git a/tools/aapt2/cmd/Dump.h b/tools/aapt2/cmd/Dump.h index 7ded9bcf8470..cd51f7a7718c 100644 --- a/tools/aapt2/cmd/Dump.h +++ b/tools/aapt2/cmd/Dump.h @@ -240,6 +240,16 @@ class DumpXmlTreeCommand : public DumpApkCommand { std::vector<std::string> files_; }; +class DumpOverlayableCommand : public DumpApkCommand { + public: + explicit DumpOverlayableCommand(text::Printer* printer, IDiagnostics* diag) + : DumpApkCommand("overlayable", printer, diag) { + SetDescription("Print the <overlayable> resources of an APK."); + } + + int Dump(LoadedApk* apk) override; +}; + /** The default dump command. Performs no action because a subcommand is required. */ class DumpCommand : public Command { public: @@ -255,8 +265,8 @@ class DumpCommand : public Command { AddOptionalSubcommand(util::make_unique<DumpTableCommand>(printer, diag_)); AddOptionalSubcommand(util::make_unique<DumpXmlStringsCommand>(printer, diag_)); AddOptionalSubcommand(util::make_unique<DumpXmlTreeCommand>(printer, diag_)); + AddOptionalSubcommand(util::make_unique<DumpOverlayableCommand>(printer, diag_)); AddOptionalSubcommand(util::make_unique<DumpBadgerCommand>(printer), /* hidden */ true); - // TODO(b/120609160): Add aapt2 overlayable dump command } int Action(const std::vector<std::string>& args) override { diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp index f354bb610224..bf886c2f893f 100644 --- a/tools/aapt2/cmd/Link.cpp +++ b/tools/aapt2/cmd/Link.cpp @@ -140,6 +140,14 @@ class LinkContext : public IAaptContext { min_sdk_version_ = minSdk; } + const std::set<std::string>& GetSplitNameDependencies() override { + return split_name_dependencies_; + } + + void SetSplitNameDependencies(const std::set<std::string>& split_name_dependencies) { + split_name_dependencies_ = split_name_dependencies; + } + private: DISALLOW_COPY_AND_ASSIGN(LinkContext); @@ -151,6 +159,7 @@ class LinkContext : public IAaptContext { SymbolTable symbols_; bool verbose_ = false; int min_sdk_version_ = 0; + std::set<std::string> split_name_dependencies_; }; // A custom delegate that generates compatible pre-O IDs for use with feature splits. @@ -269,6 +278,7 @@ struct ResourceFileFlattenerOptions { bool keep_raw_values = false; bool do_not_compress_anything = false; bool update_proguard_spec = false; + bool do_not_fail_on_missing_resources = false; OutputFormat output_format = OutputFormat::kApk; std::unordered_set<std::string> extensions_to_not_compress; Maybe<std::regex> regex_to_not_compress; @@ -297,6 +307,25 @@ struct R { }; }; +template <typename T> +uint32_t GetCompressionFlags(const StringPiece& str, T options) { + if (options.do_not_compress_anything) { + return 0; + } + + if (options.regex_to_not_compress + && std::regex_search(str.to_string(), options.regex_to_not_compress.value())) { + return 0; + } + + for (const std::string& extension : options.extensions_to_not_compress) { + if (util::EndsWith(str, extension)) { + return 0; + } + } + return ArchiveEntry::kCompress; +} + class ResourceFileFlattener { public: ResourceFileFlattener(const ResourceFileFlattenerOptions& options, IAaptContext* context, @@ -321,8 +350,6 @@ class ResourceFileFlattener { std::string dst_path; }; - uint32_t GetCompressionFlags(const StringPiece& str); - std::vector<std::unique_ptr<xml::XmlResource>> LinkAndVersionXmlFile(ResourceTable* table, FileOperation* file_op); @@ -381,26 +408,6 @@ ResourceFileFlattener::ResourceFileFlattener(const ResourceFileFlattenerOptions& } } -// TODO(rtmitchell): turn this function into a variable that points to a method that retrieves the -// compression flag -uint32_t ResourceFileFlattener::GetCompressionFlags(const StringPiece& str) { - if (options_.do_not_compress_anything) { - return 0; - } - - if (options_.regex_to_not_compress - && std::regex_search(str.to_string(), options_.regex_to_not_compress.value())) { - return 0; - } - - for (const std::string& extension : options_.extensions_to_not_compress) { - if (util::EndsWith(str, extension)) { - return 0; - } - } - return ArchiveEntry::kCompress; -} - static bool IsTransitionElement(const std::string& name) { return name == "fade" || name == "changeBounds" || name == "slide" || name == "explode" || name == "changeImageTransform" || name == "changeTransform" || @@ -438,7 +445,7 @@ std::vector<std::unique_ptr<xml::XmlResource>> ResourceFileFlattener::LinkAndVer xml::StripAndroidStudioAttributes(doc->root.get()); XmlReferenceLinker xml_linker; - if (!xml_linker.Consume(context_, doc)) { + if (!options_.do_not_fail_on_missing_resources && !xml_linker.Consume(context_, doc)) { return {}; } @@ -640,7 +647,8 @@ bool ResourceFileFlattener::Flatten(ResourceTable* table, IArchiveWriter* archiv } } else { error |= !io::CopyFileToArchive(context_, file_op.file_to_copy, file_op.dst_path, - GetCompressionFlags(file_op.dst_path), archive_writer); + GetCompressionFlags(file_op.dst_path, options_), + archive_writer); } } } @@ -887,7 +895,7 @@ class Linker { // android:versionCode from the framework AndroidManifest.xml. ExtractCompileSdkVersions(asset_source->GetAssetManager()); } - } else if (asset_source->IsPackageDynamic(entry.first)) { + } else if (asset_source->IsPackageDynamic(entry.first, entry.second)) { final_table_.included_packages_[entry.first] = entry.second; } } @@ -965,6 +973,17 @@ class Linker { app_info.min_sdk_version = ResourceUtils::ParseSdkVersion(min_sdk->value); } } + + for (const xml::Element* child_el : manifest_el->GetChildElements()) { + if (child_el->namespace_uri.empty() && child_el->name == "uses-split") { + if (const xml::Attribute* split_name = + child_el->FindAttribute(xml::kSchemaAndroid, "name")) { + if (!split_name->value.empty()) { + app_info.split_name_dependencies.insert(split_name->value); + } + } + } + } return app_info; } @@ -1065,7 +1084,8 @@ class Linker { case OutputFormat::kProto: { pb::ResourceTable pb_table; - SerializeTableToPb(*table, &pb_table, context_->GetDiagnostics()); + SerializeTableToPb(*table, &pb_table, context_->GetDiagnostics(), + options_.proto_table_flattener_options); return io::CopyProtoToArchive(context_, &pb_table, kProtoResourceTablePath, ArchiveEntry::kCompress, writer); } break; @@ -1277,7 +1297,8 @@ class Linker { return false; } - proguard::WriteKeepSet(keep_set, &fout, options_.generate_minimal_proguard_rules); + proguard::WriteKeepSet(keep_set, &fout, options_.generate_minimal_proguard_rules, + options_.no_proguard_location_reference); fout.Flush(); if (fout.HadError()) { @@ -1547,16 +1568,7 @@ class Linker { } for (auto& entry : merged_assets) { - uint32_t compression_flags = ArchiveEntry::kCompress; - std::string extension = file::GetExtension(entry.first).to_string(); - - if (options_.do_not_compress_anything - || options_.extensions_to_not_compress.count(extension) > 0 - || (options_.regex_to_not_compress - && std::regex_search(extension, options_.regex_to_not_compress.value()))) { - compression_flags = 0u; - } - + uint32_t compression_flags = GetCompressionFlags(entry.first, options_); if (!io::CopyFileToArchive(context_, entry.second.get(), entry.first, compression_flags, writer)) { return false; @@ -1565,6 +1577,93 @@ class Linker { return true; } + void AliasAdaptiveIcon(xml::XmlResource* manifest, ResourceTable* table) { + xml::Element* application = manifest->root->FindChild("", "application"); + if (!application) { + return; + } + + xml::Attribute* icon = application->FindAttribute(xml::kSchemaAndroid, "icon"); + xml::Attribute* round_icon = application->FindAttribute(xml::kSchemaAndroid, "roundIcon"); + if (!icon || !round_icon) { + return; + } + + // Find the icon resource defined within the application. + auto icon_reference = ValueCast<Reference>(icon->compiled_value.get()); + if (!icon_reference || !icon_reference->name) { + return; + } + auto package = table->FindPackageById(icon_reference->id.value().package_id()); + if (!package) { + return; + } + auto type = package->FindType(icon_reference->name.value().type); + if (!type) { + return; + } + auto icon_entry = type->FindEntry(icon_reference->name.value().entry); + if (!icon_entry) { + return; + } + + int icon_max_sdk = 0; + for (auto& config_value : icon_entry->values) { + icon_max_sdk = (icon_max_sdk < config_value->config.sdkVersion) + ? config_value->config.sdkVersion : icon_max_sdk; + } + if (icon_max_sdk < SDK_O) { + // Adaptive icons must be versioned with v26 qualifiers, so this is not an adaptive icon. + return; + } + + // Find the roundIcon resource defined within the application. + auto round_icon_reference = ValueCast<Reference>(round_icon->compiled_value.get()); + if (!round_icon_reference || !round_icon_reference->name) { + return; + } + package = table->FindPackageById(round_icon_reference->id.value().package_id()); + if (!package) { + return; + } + type = package->FindType(round_icon_reference->name.value().type); + if (!type) { + return; + } + auto round_icon_entry = type->FindEntry(round_icon_reference->name.value().entry); + if (!round_icon_entry) { + return; + } + + int round_icon_max_sdk = 0; + for (auto& config_value : round_icon_entry->values) { + round_icon_max_sdk = (round_icon_max_sdk < config_value->config.sdkVersion) + ? config_value->config.sdkVersion : round_icon_max_sdk; + } + if (round_icon_max_sdk >= SDK_O) { + // The developer explicitly used a v26 compatible drawable as the roundIcon, meaning we should + // not generate an alias to the icon drawable. + return; + } + + // Add an equivalent v26 entry to the roundIcon for each v26 variant of the regular icon. + for (auto& config_value : icon_entry->values) { + if (config_value->config.sdkVersion < SDK_O) { + continue; + } + + context_->GetDiagnostics()->Note(DiagMessage() << "generating " + << round_icon_reference->name.value() + << " with config \"" << config_value->config + << "\" for round icon compatibility"); + + auto value = icon_reference->Clone(&table->string_pool); + auto round_config_value = round_icon_entry->FindOrCreateValue( + config_value->config, config_value->product); + round_config_value->value.reset(value); + } + } + // Writes the AndroidManifest, ResourceTable, and all XML files referenced by the ResourceTable // to the IArchiveWriter. bool WriteApk(IArchiveWriter* writer, proguard::KeepSet* keep_set, xml::XmlResource* manifest, @@ -1578,6 +1677,14 @@ class Linker { return false; } + // When a developer specifies an adaptive application icon, and a non-adaptive round application + // icon, create an alias from the round icon to the regular icon for v26 APIs and up. We do this + // because certain devices prefer android:roundIcon over android:icon regardless of the API + // levels of the drawables set for either. This auto-aliasing behaviour allows an app to prefer + // the android:roundIcon on API 25 devices, and prefer the adaptive icon on API 26 devices. + // See (b/34829129) + AliasAdaptiveIcon(manifest, table); + ResourceFileFlattenerOptions file_flattener_options; file_flattener_options.keep_raw_values = keep_raw_values; file_flattener_options.do_not_compress_anything = options_.do_not_compress_anything; @@ -1590,9 +1697,9 @@ class Linker { file_flattener_options.update_proguard_spec = static_cast<bool>(options_.generate_proguard_rules_path); file_flattener_options.output_format = options_.output_format; + file_flattener_options.do_not_fail_on_missing_resources = options_.merge_only; ResourceFileFlattener file_flattener(file_flattener_options, context_, keep_set); - if (!file_flattener.Flatten(table, writer)) { context_->GetDiagnostics()->Error(DiagMessage() << "failed linking file resources"); return false; @@ -1659,9 +1766,12 @@ class Linker { return 1; } - // First extract the Package name without modifying it (via --rename-manifest-package). - if (Maybe<AppInfo> maybe_app_info = + // Determine the package name under which to merge resources. + if (options_.rename_resources_package) { + context_->SetCompilationPackage(options_.rename_resources_package.value()); + } else if (Maybe<AppInfo> maybe_app_info = ExtractAppInfoFromManifest(manifest_xml.get(), context_->GetDiagnostics())) { + // Extract the package name from the manifest ignoring the value of --rename-manifest-package. const AppInfo& app_info = maybe_app_info.value(); context_->SetCompilationPackage(app_info.package); } @@ -1687,6 +1797,7 @@ class Linker { context_->SetMinSdkVersion(app_info_.min_sdk_version.value_or_default(0)); context_->SetNameManglerPolicy(NameManglerPolicy{context_->GetCompilationPackage()}); + context_->SetSplitNameDependencies(app_info_.split_name_dependencies); // Override the package ID when it is "android". if (context_->GetCompilationPackage() == "android") { @@ -1702,6 +1813,8 @@ class Linker { TableMergerOptions table_merger_options; table_merger_options.auto_add_overlay = options_.auto_add_overlay; + table_merger_options.override_styles_instead_of_overlaying = + options_.override_styles_instead_of_overlaying; table_merger_options.strict_visibility = options_.strict_visibility; table_merger_ = util::make_unique<TableMerger>(context_, &final_table_, table_merger_options); @@ -1816,7 +1929,7 @@ class Linker { } ReferenceLinker linker; - if (!linker.Consume(context_, &final_table_)) { + if (!options_.merge_only && !linker.Consume(context_, &final_table_)) { context_->GetDiagnostics()->Error(DiagMessage() << "failed linking references"); return 1; } @@ -1968,7 +2081,7 @@ class Linker { manifest_xml->file.name.package = context_->GetCompilationPackage(); XmlReferenceLinker manifest_linker; - if (manifest_linker.Consume(context_, manifest_xml.get())) { + if (options_.merge_only || manifest_linker.Consume(context_, manifest_xml.get())) { if (options_.generate_proguard_rules_path && !proguard::CollectProguardRulesForManifest(manifest_xml.get(), &proguard_keep_set)) { error = true; @@ -2102,6 +2215,12 @@ int LinkCommand::Action(const std::vector<std::string>& args) { return 1; } + if (options_.merge_only && !static_lib_) { + context.GetDiagnostics()->Error( + DiagMessage() << "the --merge-only flag can be only used when building a static library"); + return 1; + } + // The default build type. context.SetPackageType(PackageType::kApp); context.SetPackageId(kAppPackageId); diff --git a/tools/aapt2/cmd/Link.h b/tools/aapt2/cmd/Link.h index 7c583858ee1d..e7be43459e16 100644 --- a/tools/aapt2/cmd/Link.h +++ b/tools/aapt2/cmd/Link.h @@ -24,6 +24,7 @@ #include "Resource.h" #include "split/TableSplitter.h" #include "format/binary/TableFlattener.h" +#include "format/proto/ProtoSerialize.h" #include "link/ManifestFixer.h" #include "trace/TraceBuffer.h" @@ -42,7 +43,9 @@ struct LinkOptions { std::vector<std::string> assets_dirs; bool output_to_directory = false; bool auto_add_overlay = false; + bool override_styles_instead_of_overlaying = false; OutputFormat output_format = OutputFormat::kApk; + Maybe<std::string> rename_resources_package; // Java/Proguard options. Maybe<std::string> generate_java_class_path; @@ -54,6 +57,7 @@ struct LinkOptions { bool generate_conditional_proguard_rules = false; bool generate_minimal_proguard_rules = false; bool generate_non_final_ids = false; + bool no_proguard_location_reference = false; std::vector<std::string> javadoc_annotations; Maybe<std::string> private_symbols; @@ -70,6 +74,7 @@ struct LinkOptions { // Static lib options. bool no_static_lib_packages = false; + bool merge_only = false; // AndroidManifest.xml massaging options. ManifestFixerOptions manifest_fixer_options; @@ -79,6 +84,7 @@ struct LinkOptions { // Flattening options. TableFlattenerOptions table_flattener_options; + SerializeTableOptions proto_table_flattener_options; bool keep_raw_values = false; // Split APK options. @@ -211,6 +217,9 @@ class LinkCommand : public Command { "Generates R.java without the final modifier. This is implied when\n" "--static-lib is specified.", &options_.generate_non_final_ids); + AddOptionalSwitch("--no-proguard-location-reference", + "Keep proguard rules files from having a reference to the source file", + &options_.no_proguard_location_reference); AddOptionalFlag("--stable-ids", "File containing a list of name to ID mapping.", &stable_id_file_path_); AddOptionalFlag("--emit-ids", @@ -242,13 +251,19 @@ class LinkCommand : public Command { "Allows the addition of new resources in overlays without\n" "<add-resource> tags.", &options_.auto_add_overlay); + AddOptionalSwitch("--override-styles-instead-of-overlaying", + "Causes styles defined in -R resources to replace previous definitions\n" + "instead of merging into them\n", + &options_.override_styles_instead_of_overlaying); AddOptionalFlag("--rename-manifest-package", "Renames the package in AndroidManifest.xml.", &options_.manifest_fixer_options.rename_manifest_package); + AddOptionalFlag("--rename-resources-package", "Renames the package in resources table", + &options_.rename_resources_package); AddOptionalFlag("--rename-instrumentation-target-package", "Changes the name of the target package for instrumentation. Most useful\n" "when used in conjunction with --rename-manifest-package.", &options_.manifest_fixer_options.rename_instrumentation_target_package); - AddOptionalFlagList("-0", "File extensions not to compress.", + AddOptionalFlagList("-0", "File suffix not to compress.", &options_.extensions_to_not_compress); AddOptionalSwitch("--no-compress", "Do not compress any resources.", &options_.do_not_compress_anything); @@ -256,8 +271,8 @@ class LinkCommand : public Command { &options_.keep_raw_values); AddOptionalFlag("--no-compress-regex", "Do not compress extensions matching the regular expression. Remember to\n" - " use the '$' symbol for end of line. Uses a non case-sensitive\n" - " ECMAScript regular expression grammar.", + "use the '$' symbol for end of line. Uses a case-sensitive ECMAScript" + "regular expression grammar.", &no_compress_regex); AddOptionalSwitch("--warn-manifest-validation", "Treat manifest validation errors as warnings.", @@ -277,9 +292,18 @@ class LinkCommand : public Command { AddOptionalSwitch("--strict-visibility", "Do not allow overlays with different visibility levels.", &options_.strict_visibility); + AddOptionalSwitch("--exclude-sources", + "Do not serialize source file information when generating resources in\n" + "Protobuf format.", + &options_.proto_table_flattener_options.exclude_sources); + AddOptionalFlag("--trace-folder", + "Generate systrace json trace fragment to specified folder.", + &trace_folder_); + AddOptionalSwitch("--merge-only", + "Only merge the resources, without verifying resource references. This flag\n" + "should only be used together with the --static-lib flag.", + &options_.merge_only); AddOptionalSwitch("-v", "Enables verbose logging.", &verbose_); - AddOptionalFlag("--trace-folder", "Generate systrace json trace fragment to specified folder.", - &trace_folder_); } int Action(const std::vector<std::string>& args) override; diff --git a/tools/aapt2/cmd/Link_test.cpp b/tools/aapt2/cmd/Link_test.cpp index 9ea93f638aff..062dd8eac975 100644 --- a/tools/aapt2/cmd/Link_test.cpp +++ b/tools/aapt2/cmd/Link_test.cpp @@ -14,6 +14,7 @@ * limitations under the License. */ +#include "AppInfo.h" #include "Link.h" #include "LoadedApk.h" @@ -43,10 +44,8 @@ TEST_F(LinkTest, RemoveRawXmlStrings) { // Load the binary xml tree android::ResXMLTree tree; std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag); - std::unique_ptr<io::IData> data = OpenFileAsData(apk.get(), "res/xml/test.xml"); ASSERT_THAT(data, Ne(nullptr)); - AssertLoadXml(apk.get(), data.get(), &tree); // Check that the raw string index has not been assigned @@ -71,10 +70,8 @@ TEST_F(LinkTest, KeepRawXmlStrings) { // Load the binary xml tree android::ResXMLTree tree; std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag); - std::unique_ptr<io::IData> data = OpenFileAsData(apk.get(), "res/xml/test.xml"); ASSERT_THAT(data, Ne(nullptr)); - AssertLoadXml(apk.get(), data.get(), &tree); // Check that the raw string index has been set to the correct string pool entry @@ -83,4 +80,241 @@ TEST_F(LinkTest, KeepRawXmlStrings) { EXPECT_THAT(util::GetString(tree.getStrings(), static_cast<size_t>(raw_index)), Eq("007")); } -} // namespace aapt
\ No newline at end of file +TEST_F(LinkTest, NoCompressAssets) { + StdErrDiagnostics diag; + std::string content(500, 'a'); + WriteFile(GetTestPath("assets/testtxt"), content); + WriteFile(GetTestPath("assets/testtxt2"), content); + WriteFile(GetTestPath("assets/test.txt"), content); + WriteFile(GetTestPath("assets/test.hello.txt"), content); + WriteFile(GetTestPath("assets/test.hello.xml"), content); + + const std::string out_apk = GetTestPath("out.apk"); + std::vector<std::string> link_args = { + "--manifest", GetDefaultManifest(), + "-o", out_apk, + "-0", ".txt", + "-0", "txt2", + "-0", ".hello.txt", + "-0", "hello.xml", + "-A", GetTestPath("assets") + }; + + ASSERT_TRUE(Link(link_args, &diag)); + + std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag); + ASSERT_THAT(apk, Ne(nullptr)); + io::IFileCollection* zip = apk->GetFileCollection(); + ASSERT_THAT(zip, Ne(nullptr)); + + auto file = zip->FindFile("assets/testtxt"); + ASSERT_THAT(file, Ne(nullptr)); + EXPECT_TRUE(file->WasCompressed()); + + file = zip->FindFile("assets/testtxt2"); + ASSERT_THAT(file, Ne(nullptr)); + EXPECT_FALSE(file->WasCompressed()); + + file = zip->FindFile("assets/test.txt"); + ASSERT_THAT(file, Ne(nullptr)); + EXPECT_FALSE(file->WasCompressed()); + + file = zip->FindFile("assets/test.hello.txt"); + ASSERT_THAT(file, Ne(nullptr)); + EXPECT_FALSE(file->WasCompressed()); + + file = zip->FindFile("assets/test.hello.xml"); + ASSERT_THAT(file, Ne(nullptr)); + EXPECT_FALSE(file->WasCompressed()); +} + +TEST_F(LinkTest, NoCompressResources) { + StdErrDiagnostics diag; + std::string content(500, 'a'); + const std::string compiled_files_dir = GetTestPath("compiled"); + ASSERT_TRUE(CompileFile(GetTestPath("res/raw/testtxt"), content, compiled_files_dir, &diag)); + ASSERT_TRUE(CompileFile(GetTestPath("res/raw/test.txt"), content, compiled_files_dir, &diag)); + ASSERT_TRUE(CompileFile(GetTestPath("res/raw/test1.hello.txt"), content, compiled_files_dir, + &diag)); + ASSERT_TRUE(CompileFile(GetTestPath("res/raw/test2.goodbye.xml"), content, compiled_files_dir, + &diag)); + + const std::string out_apk = GetTestPath("out.apk"); + std::vector<std::string> link_args = { + "--manifest", GetDefaultManifest(), + "-o", out_apk, + "-0", ".txt", + "-0", ".hello.txt", + "-0", "goodbye.xml", + }; + + ASSERT_TRUE(Link(link_args, compiled_files_dir, &diag)); + + std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag); + ASSERT_THAT(apk, Ne(nullptr)); + io::IFileCollection* zip = apk->GetFileCollection(); + ASSERT_THAT(zip, Ne(nullptr)); + + auto file = zip->FindFile("res/raw/testtxt"); + ASSERT_THAT(file, Ne(nullptr)); + EXPECT_TRUE(file->WasCompressed()); + + file = zip->FindFile("res/raw/test.txt"); + ASSERT_THAT(file, Ne(nullptr)); + EXPECT_FALSE(file->WasCompressed()); + + file = zip->FindFile("res/raw/test1.hello.hello.txt"); + ASSERT_THAT(file, Ne(nullptr)); + EXPECT_FALSE(file->WasCompressed()); + + file = zip->FindFile("res/raw/test2.goodbye.goodbye.xml"); + ASSERT_THAT(file, Ne(nullptr)); + EXPECT_FALSE(file->WasCompressed()); +} + +TEST_F(LinkTest, OverlayStyles) { + StdErrDiagnostics diag; + const std::string compiled_files_dir = GetTestPath("compiled"); + const std::string override_files_dir = GetTestPath("compiled-override"); + ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"), + R"(<resources> + <style name="MyStyle"> + <item name="android:textColor">#123</item> + </style> + </resources>)", + compiled_files_dir, &diag)); + ASSERT_TRUE(CompileFile(GetTestPath("res/values/values-override.xml"), + R"(<resources> + <style name="MyStyle"> + <item name="android:background">#456</item> + </style> + </resources>)", + override_files_dir, &diag)); + + + const std::string out_apk = GetTestPath("out.apk"); + std::vector<std::string> link_args = { + "--manifest", GetDefaultManifest(kDefaultPackageName), + "-o", out_apk, + }; + const auto override_files = file::FindFiles(override_files_dir, &diag); + for (const auto &override_file : override_files.value()) { + link_args.push_back("-R"); + link_args.push_back(file::BuildPath({override_files_dir, override_file})); + } + ASSERT_TRUE(Link(link_args, compiled_files_dir, &diag)); + + std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag); + const Style* actual_style = test::GetValue<Style>( + apk->GetResourceTable(), std::string(kDefaultPackageName) + ":style/MyStyle"); + ASSERT_NE(actual_style, nullptr); + ASSERT_EQ(actual_style->entries.size(), 2); + EXPECT_EQ(actual_style->entries[0].key.id, 0x01010098); // android:textColor + EXPECT_EQ(actual_style->entries[1].key.id, 0x010100d4); // android:background +} + +TEST_F(LinkTest, OverrideStylesInsteadOfOverlaying) { + StdErrDiagnostics diag; + const std::string compiled_files_dir = GetTestPath("compiled"); + const std::string override_files_dir = GetTestPath("compiled-override"); + ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"), + R"(<resources> + <style name="MyStyle"> + <item name="android:textColor">#123</item> + </style> + </resources>)", + compiled_files_dir, &diag)); + ASSERT_TRUE(CompileFile(GetTestPath("res/values/values-override.xml"), + R"(<resources> + <style name="MyStyle"> + <item name="android:background">#456</item> + </style> + </resources>)", + override_files_dir, &diag)); + + + const std::string out_apk = GetTestPath("out.apk"); + std::vector<std::string> link_args = { + "--manifest", GetDefaultManifest(kDefaultPackageName), + "--override-styles-instead-of-overlaying", + "-o", out_apk, + }; + const auto override_files = file::FindFiles(override_files_dir, &diag); + for (const auto &override_file : override_files.value()) { + link_args.push_back("-R"); + link_args.push_back(file::BuildPath({override_files_dir, override_file})); + } + ASSERT_TRUE(Link(link_args, compiled_files_dir, &diag)); + + std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(out_apk, &diag); + const Style* actual_style = test::GetValue<Style>( + apk->GetResourceTable(), std::string(kDefaultPackageName) + ":style/MyStyle"); + ASSERT_NE(actual_style, nullptr); + ASSERT_EQ(actual_style->entries.size(), 1); + EXPECT_EQ(actual_style->entries[0].key.id, 0x010100d4); // android:background +} + +TEST_F(LinkTest, AppInfoWithUsesSplit) { + StdErrDiagnostics diag; + const std::string base_files_dir = GetTestPath("base"); + ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"), + R"(<resources> + <string name="bar">bar</string> + </resources>)", + base_files_dir, &diag)); + const std::string base_apk = GetTestPath("base.apk"); + std::vector<std::string> link_args = { + "--manifest", GetDefaultManifest("com.aapt2.app"), + "-o", base_apk, + }; + ASSERT_TRUE(Link(link_args, base_files_dir, &diag)); + + const std::string feature_manifest = GetTestPath("feature_manifest.xml"); + WriteFile(feature_manifest, android::base::StringPrintf(R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.aapt2.app" split="feature1"> + </manifest>)")); + const std::string feature_files_dir = GetTestPath("feature"); + ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"), + R"(<resources> + <string name="foo">foo</string> + </resources>)", + feature_files_dir, &diag)); + const std::string feature_apk = GetTestPath("feature.apk"); + const std::string feature_package_id = "0x80"; + link_args = { + "--manifest", feature_manifest, + "-I", base_apk, + "--package-id", feature_package_id, + "-o", feature_apk, + }; + ASSERT_TRUE(Link(link_args, feature_files_dir, &diag)); + + const std::string feature2_manifest = GetTestPath("feature2_manifest.xml"); + WriteFile(feature2_manifest, android::base::StringPrintf(R"( + <manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.aapt2.app" split="feature2"> + <uses-split android:name="feature1"/> + </manifest>)")); + const std::string feature2_files_dir = GetTestPath("feature2"); + ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"), + R"(<resources> + <string-array name="string_array"> + <item>@string/bar</item> + <item>@string/foo</item> + </string-array> + </resources>)", + feature2_files_dir, &diag)); + const std::string feature2_apk = GetTestPath("feature2.apk"); + const std::string feature2_package_id = "0x81"; + link_args = { + "--manifest", feature2_manifest, + "-I", base_apk, + "-I", feature_apk, + "--package-id", feature2_package_id, + "-o", feature2_apk, + }; + ASSERT_TRUE(Link(link_args, feature2_files_dir, &diag)); +} + +} // namespace aapt diff --git a/tools/aapt2/cmd/Optimize.cpp b/tools/aapt2/cmd/Optimize.cpp index 2e6af18c1948..e36668e5a043 100644 --- a/tools/aapt2/cmd/Optimize.cpp +++ b/tools/aapt2/cmd/Optimize.cpp @@ -53,9 +53,9 @@ using ::android::ConfigDescription; using ::android::ResTable_config; using ::android::StringPiece; using ::android::base::ReadFileToString; -using ::android::base::WriteStringToFile; using ::android::base::StringAppendF; using ::android::base::StringPrintf; +using ::android::base::WriteStringToFile; namespace aapt { @@ -108,6 +108,12 @@ class OptimizeContext : public IAaptContext { return sdk_version_; } + const std::set<std::string>& GetSplitNameDependencies() override { + UNIMPLEMENTED(FATAL) << "Split Name Dependencies should not be necessary"; + static std::set<std::string> empty; + return empty; + } + private: DISALLOW_COPY_AND_ASSIGN(OptimizeContext); @@ -294,29 +300,7 @@ class Optimizer { OptimizeContext* context_; }; -bool ExtractObfuscationWhitelistFromConfig(const std::string& path, OptimizeContext* context, - OptimizeOptions* options) { - std::string contents; - if (!ReadFileToString(path, &contents, true)) { - context->GetDiagnostics()->Error(DiagMessage() - << "failed to parse whitelist from config file: " << path); - return false; - } - for (StringPiece resource_name : util::Tokenize(contents, ',')) { - options->table_flattener_options.whitelisted_resources.insert( - resource_name.to_string()); - } - return true; -} - -bool ExtractConfig(const std::string& path, OptimizeContext* context, - OptimizeOptions* options) { - std::string content; - if (!android::base::ReadFileToString(path, &content, true /*follow_symlinks*/)) { - context->GetDiagnostics()->Error(DiagMessage(path) << "failed reading whitelist"); - return false; - } - +bool ParseConfig(const std::string& content, IAaptContext* context, OptimizeOptions* options) { size_t line_no = 0; for (StringPiece line : util::Tokenize(content, '\n')) { line_no++; @@ -345,15 +329,24 @@ bool ExtractConfig(const std::string& path, OptimizeContext* context, for (StringPiece directive : util::Tokenize(directives, ',')) { if (directive == "remove") { options->resources_blacklist.insert(resource_name.ToResourceName()); - } else if (directive == "no_obfuscate") { - options->table_flattener_options.whitelisted_resources.insert( - resource_name.entry.to_string()); + } else if (directive == "no_collapse" || directive == "no_obfuscate") { + options->table_flattener_options.name_collapse_exemptions.insert( + resource_name.ToResourceName()); } } } return true; } +bool ExtractConfig(const std::string& path, IAaptContext* context, OptimizeOptions* options) { + std::string content; + if (!android::base::ReadFileToString(path, &content, true /*follow_symlinks*/)) { + context->GetDiagnostics()->Error(DiagMessage(path) << "failed reading config file"); + return false; + } + return ParseConfig(content, context, options); +} + bool ExtractAppDataFromManifest(OptimizeContext* context, const LoadedApk* apk, OptimizeOptions* out_options) { const xml::XmlResource* manifest = apk->GetManifest(); @@ -461,15 +454,6 @@ int OptimizeCommand::Action(const std::vector<std::string>& args) { } } - if (options_.table_flattener_options.collapse_key_stringpool) { - if (whitelist_path_) { - std::string& path = whitelist_path_.value(); - if (!ExtractObfuscationWhitelistFromConfig(path, &context, &options_)) { - return 1; - } - } - } - if (resources_config_path_) { std::string& path = resources_config_path_.value(); if (!ExtractConfig(path, &context, &options_)) { diff --git a/tools/aapt2/cmd/Optimize.h b/tools/aapt2/cmd/Optimize.h index 7f4a3ed85364..5070ccc8afbf 100644 --- a/tools/aapt2/cmd/Optimize.h +++ b/tools/aapt2/cmd/Optimize.h @@ -57,7 +57,7 @@ struct OptimizeOptions { std::unordered_set<std::string> kept_artifacts; // Whether or not to shorten resource paths in the APK. - bool shorten_resource_paths; + bool shorten_resource_paths = false; // Path to the output map of original resource paths to shortened paths. Maybe<std::string> shortened_paths_map_path; @@ -78,10 +78,6 @@ class OptimizeCommand : public Command { "All the resources that would be unused on devices of the given densities will be \n" "removed from the APK.", &target_densities_); - AddOptionalFlag("--whitelist-path", - "Path to the whitelist.cfg file containing whitelisted resources \n" - "whose names should not be altered in final resource tables.", - &whitelist_path_); AddOptionalFlag("--resources-config-path", "Path to the resources.cfg file containing the list of resources and \n" "directives to each resource. \n" @@ -104,11 +100,13 @@ class OptimizeCommand : public Command { "Enables encoding sparse entries using a binary search tree.\n" "This decreases APK size at the cost of resource retrieval performance.", &options_.table_flattener_options.use_sparse_entries); - AddOptionalSwitch("--enable-resource-obfuscation", - "Enables obfuscation of key string pool to single value", + AddOptionalSwitch("--collapse-resource-names", + "Collapses resource names to a single value in the key string pool. Resources can \n" + "be exempted using the \"no_collapse\" directive in a file specified by " + "--resources-config-path.", &options_.table_flattener_options.collapse_key_stringpool); - AddOptionalSwitch("--enable-resource-path-shortening", - "Enables shortening of the path of the resources inside the APK.", + AddOptionalSwitch("--shorten-resource-paths", + "Shortens the paths of resources inside the APK.", &options_.shorten_resource_paths); AddOptionalFlag("--resource-path-shortening-map", "Path to output the map of old resource paths to shortened paths.", @@ -125,7 +123,6 @@ class OptimizeCommand : public Command { const std::string &file_path); Maybe<std::string> config_path_; - Maybe<std::string> whitelist_path_; Maybe<std::string> resources_config_path_; Maybe<std::string> target_densities_; std::vector<std::string> configs_; diff --git a/tools/aapt2/cmd/Optimize_test.cpp b/tools/aapt2/cmd/Optimize_test.cpp new file mode 100644 index 000000000000..ac681e85b3d6 --- /dev/null +++ b/tools/aapt2/cmd/Optimize_test.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2019 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 "Optimize.h" + +#include "AppInfo.h" +#include "Diagnostics.h" +#include "LoadedApk.h" +#include "Resource.h" +#include "test/Test.h" + +using testing::Contains; +using testing::Eq; + +namespace aapt { + +bool ParseConfig(const std::string&, IAaptContext*, OptimizeOptions*); + +using OptimizeTest = CommandTestFixture; + +TEST_F(OptimizeTest, ParseConfigWithNoCollapseExemptions) { + const std::string& content = R"( +string/foo#no_collapse +dimen/bar#no_collapse +)"; + aapt::test::Context context; + OptimizeOptions options; + ParseConfig(content, &context, &options); + + const std::set<ResourceName>& name_collapse_exemptions = + options.table_flattener_options.name_collapse_exemptions; + + ASSERT_THAT(name_collapse_exemptions.size(), Eq(2)); + EXPECT_THAT(name_collapse_exemptions, Contains(ResourceName({}, ResourceType::kString, "foo"))); + EXPECT_THAT(name_collapse_exemptions, Contains(ResourceName({}, ResourceType::kDimen, "bar"))); +} + +TEST_F(OptimizeTest, ParseConfigWithNoObfuscateExemptions) { + const std::string& content = R"( +string/foo#no_obfuscate +dimen/bar#no_obfuscate +)"; + aapt::test::Context context; + OptimizeOptions options; + ParseConfig(content, &context, &options); + + const std::set<ResourceName>& name_collapse_exemptions = + options.table_flattener_options.name_collapse_exemptions; + + ASSERT_THAT(name_collapse_exemptions.size(), Eq(2)); + EXPECT_THAT(name_collapse_exemptions, Contains(ResourceName({}, ResourceType::kString, "foo"))); + EXPECT_THAT(name_collapse_exemptions, Contains(ResourceName({}, ResourceType::kDimen, "bar"))); +} + +} // namespace aapt diff --git a/tools/aapt2/cmd/Util.cpp b/tools/aapt2/cmd/Util.cpp index e2c65ba74271..7214f1a68d2c 100644 --- a/tools/aapt2/cmd/Util.cpp +++ b/tools/aapt2/cmd/Util.cpp @@ -436,9 +436,9 @@ void SetLongVersionCode(xml::Element* manifest, uint64_t version) { } std::regex GetRegularExpression(const std::string &input) { - // Standard ECMAScript grammar plus case insensitive. + // Standard ECMAScript grammar. std::regex case_insensitive( - input, std::regex_constants::icase | std::regex_constants::ECMAScript); + input, std::regex_constants::ECMAScript); return case_insensitive; } diff --git a/tools/aapt2/cmd/Util_test.cpp b/tools/aapt2/cmd/Util_test.cpp index 7e492610b658..ac1f981d753c 100644 --- a/tools/aapt2/cmd/Util_test.cpp +++ b/tools/aapt2/cmd/Util_test.cpp @@ -383,7 +383,7 @@ TEST (UtilTest, AdjustSplitConstraintsForMinSdk) { EXPECT_NE(*adjusted_contraints[1].configs.begin(), ConfigDescription::DefaultConfig()); } -TEST(UtilTest, RegularExperssions) { +TEST (UtilTest, RegularExperssionsSimple) { std::string valid(".bc$"); std::regex expression = GetRegularExpression(valid); EXPECT_TRUE(std::regex_search("file.abc", expression)); @@ -391,4 +391,24 @@ TEST(UtilTest, RegularExperssions) { EXPECT_FALSE(std::regex_search("abc.zip", expression)); } +TEST (UtilTest, RegularExpressionComplex) { + std::string valid("\\.(d|D)(e|E)(x|X)$"); + std::regex expression = GetRegularExpression(valid); + EXPECT_TRUE(std::regex_search("file.dex", expression)); + EXPECT_TRUE(std::regex_search("file.DEX", expression)); + EXPECT_TRUE(std::regex_search("file.dEx", expression)); + EXPECT_FALSE(std::regex_search("file.dexx", expression)); + EXPECT_FALSE(std::regex_search("dex.file", expression)); + EXPECT_FALSE(std::regex_search("file.adex", expression)); +} + +TEST (UtilTest, RegularExpressionNonEnglish) { + std::string valid("\\.(k|K)(o|O)(ń|Ń)(c|C)(ó|Ó)(w|W)(k|K)(a|A)$"); + std::regex expression = GetRegularExpression(valid); + EXPECT_TRUE(std::regex_search("file.końcówka", expression)); + EXPECT_TRUE(std::regex_search("file.KOŃCÓWKA", expression)); + EXPECT_TRUE(std::regex_search("file.kOńcÓwkA", expression)); + EXPECT_FALSE(std::regex_search("file.koncowka", expression)); +} + } // namespace aapt diff --git a/tools/aapt2/dump/DumpManifest.cpp b/tools/aapt2/dump/DumpManifest.cpp index 42a64716701d..e7a82034c77a 100644 --- a/tools/aapt2/dump/DumpManifest.cpp +++ b/tools/aapt2/dump/DumpManifest.cpp @@ -291,7 +291,10 @@ class ManifestExtractor { } } } - return &attr->value; + + if (!attr->value.empty()) { + return &attr->value; + } } return nullptr; } @@ -425,6 +428,8 @@ class Manifest : public ManifestExtractor::Element { const std::string* split = nullptr; const std::string* platformVersionName = nullptr; const std::string* platformVersionCode = nullptr; + const int32_t* platformVersionNameInt = nullptr; + const int32_t* platformVersionCodeInt = nullptr; const int32_t* compilesdkVersion = nullptr; const std::string* compilesdkVersionCodename = nullptr; const int32_t* installLocation = nullptr; @@ -440,6 +445,10 @@ class Manifest : public ManifestExtractor::Element { "platformBuildVersionName")); platformVersionCode = GetAttributeString(FindAttribute(manifest, {}, "platformBuildVersionCode")); + platformVersionNameInt = GetAttributeInteger(FindAttribute(manifest, {}, + "platformBuildVersionName")); + platformVersionCodeInt = GetAttributeInteger(FindAttribute(manifest, {}, + "platformBuildVersionCode")); // Extract the compile sdk info compilesdkVersion = GetAttributeInteger(FindAttribute(manifest, COMPILE_SDK_VERSION_ATTR)); @@ -459,9 +468,13 @@ class Manifest : public ManifestExtractor::Element { } if (platformVersionName) { printer->Print(StringPrintf(" platformBuildVersionName='%s'", platformVersionName->data())); + } else if (platformVersionNameInt) { + printer->Print(StringPrintf(" platformBuildVersionName='%d'", *platformVersionNameInt)); } if (platformVersionCode) { printer->Print(StringPrintf(" platformBuildVersionCode='%s'", platformVersionCode->data())); + } else if (platformVersionCodeInt) { + printer->Print(StringPrintf(" platformBuildVersionCode='%d'", *platformVersionCodeInt)); } if (compilesdkVersion) { printer->Print(StringPrintf(" compileSdkVersion='%d'", *compilesdkVersion)); diff --git a/tools/aapt2/format/Archive.cpp b/tools/aapt2/format/Archive.cpp index d152a9cc7e62..41f01a01ed7c 100644 --- a/tools/aapt2/format/Archive.cpp +++ b/tools/aapt2/format/Archive.cpp @@ -103,7 +103,13 @@ class DirectoryWriter : public IArchiveWriter { return false; } } - return !in->HadError(); + + if (in->HadError()) { + error_ = in->GetError(); + return false; + } + + return FinishEntry(); } bool HadError() const override { @@ -191,6 +197,7 @@ class ZipFileWriter : public IArchiveWriter { } if (in->HadError()) { + error_ = in->GetError(); return false; } diff --git a/tools/aapt2/format/Archive_test.cpp b/tools/aapt2/format/Archive_test.cpp new file mode 100644 index 000000000000..ceed3740f37a --- /dev/null +++ b/tools/aapt2/format/Archive_test.cpp @@ -0,0 +1,209 @@ +/* + * Copyright (C) 2017 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 "test/Test.h" + +namespace aapt { + +using ArchiveTest = TestDirectoryFixture; + +constexpr size_t kTestDataLength = 100; + +class TestData : public io::MallocData { + public: + TestData(std::unique_ptr<uint8_t[]>& data, size_t size) + : MallocData(std::move(data), size) {} + + bool HadError() const override { return !error_.empty(); } + + std::string GetError() const override { return error_; } + + std::string error_; +}; + +std::unique_ptr<uint8_t[]> MakeTestArray() { + auto array = std::make_unique<uint8_t[]>(kTestDataLength); + for (int index = 0; index < kTestDataLength; ++index) { + array[index] = static_cast<uint8_t>(rand()); + } + return array; +} + +std::unique_ptr<IArchiveWriter> MakeDirectoryWriter(const std::string& output_path) { + file::mkdirs(output_path); + + StdErrDiagnostics diag; + return CreateDirectoryArchiveWriter(&diag, output_path); +} + +std::unique_ptr<IArchiveWriter> MakeZipFileWriter(const std::string& output_path) { + file::mkdirs(file::GetStem(output_path).to_string()); + std::remove(output_path.c_str()); + + StdErrDiagnostics diag; + return CreateZipFileArchiveWriter(&diag, output_path); +} + +void VerifyDirectory(const std::string& path, const std::string& file, const uint8_t array[]) { + std::string file_path = file::BuildPath({path, file}); + auto buffer = std::make_unique<char[]>(kTestDataLength); + std::ifstream stream(file_path); + stream.read(buffer.get(), kTestDataLength); + + for (int index = 0; index < kTestDataLength; ++index) { + ASSERT_EQ(array[index], static_cast<uint8_t>(buffer[index])); + } +} + +void VerifyZipFile(const std::string& output_path, const std::string& file, const uint8_t array[]) { + std::unique_ptr<io::ZipFileCollection> zip = io::ZipFileCollection::Create(output_path, nullptr); + std::unique_ptr<io::InputStream> stream = zip->FindFile(file)->OpenInputStream(); + + std::vector<uint8_t> buffer; + const void* data; + size_t size; + + while (stream->Next(&data, &size)) { + auto pointer = static_cast<const uint8_t*>(data); + buffer.insert(buffer.end(), pointer, pointer + size); + } + + for (int index = 0; index < kTestDataLength; ++index) { + ASSERT_EQ(array[index], buffer[index]); + } +} + +TEST_F(ArchiveTest, DirectoryWriteEntrySuccess) { + std::string output_path = GetTestPath("output"); + std::unique_ptr<IArchiveWriter> writer = MakeDirectoryWriter(output_path); + std::unique_ptr<uint8_t[]> data1 = MakeTestArray(); + std::unique_ptr<uint8_t[]> data2 = MakeTestArray(); + + ASSERT_TRUE(writer->StartEntry("test1", 0)); + ASSERT_TRUE(writer->Write(static_cast<const void*>(data1.get()), kTestDataLength)); + ASSERT_TRUE(writer->FinishEntry()); + ASSERT_FALSE(writer->HadError()); + + ASSERT_TRUE(writer->StartEntry("test2", 0)); + ASSERT_TRUE(writer->Write(static_cast<const void*>(data2.get()), kTestDataLength)); + ASSERT_TRUE(writer->FinishEntry()); + ASSERT_FALSE(writer->HadError()); + + writer.reset(); + + VerifyDirectory(output_path, "test1", data1.get()); + VerifyDirectory(output_path, "test2", data2.get()); +} + +TEST_F(ArchiveTest, DirectoryWriteFileSuccess) { + std::string output_path = GetTestPath("output"); + std::unique_ptr<IArchiveWriter> writer = MakeDirectoryWriter(output_path); + + std::unique_ptr<uint8_t[]> data1 = MakeTestArray(); + auto data1_copy = std::make_unique<uint8_t[]>(kTestDataLength); + std::copy(data1.get(), data1.get() + kTestDataLength, data1_copy.get()); + + std::unique_ptr<uint8_t[]> data2 = MakeTestArray(); + auto data2_copy = std::make_unique<uint8_t[]>(kTestDataLength); + std::copy(data2.get(), data2.get() + kTestDataLength, data2_copy.get()); + + auto input1 = std::make_unique<TestData>(data1_copy, kTestDataLength); + auto input2 = std::make_unique<TestData>(data2_copy, kTestDataLength); + + ASSERT_TRUE(writer->WriteFile("test1", 0, input1.get())); + ASSERT_FALSE(writer->HadError()); + ASSERT_TRUE(writer->WriteFile("test2", 0, input2.get())); + ASSERT_FALSE(writer->HadError()); + + writer.reset(); + + VerifyDirectory(output_path, "test1", data1.get()); + VerifyDirectory(output_path, "test2", data2.get()); +} + +TEST_F(ArchiveTest, DirectoryWriteFileError) { + std::string output_path = GetTestPath("output"); + std::unique_ptr<IArchiveWriter> writer = MakeDirectoryWriter(output_path); + std::unique_ptr<uint8_t[]> data = MakeTestArray(); + auto input = std::make_unique<TestData>(data, kTestDataLength); + input->error_ = "DirectoryWriteFileError"; + + ASSERT_FALSE(writer->WriteFile("test", 0, input.get())); + ASSERT_TRUE(writer->HadError()); + ASSERT_EQ("DirectoryWriteFileError", writer->GetError()); +} + +TEST_F(ArchiveTest, ZipFileWriteEntrySuccess) { + std::string output_path = GetTestPath("output.apk"); + std::unique_ptr<IArchiveWriter> writer = MakeZipFileWriter(output_path); + std::unique_ptr<uint8_t[]> data1 = MakeTestArray(); + std::unique_ptr<uint8_t[]> data2 = MakeTestArray(); + + ASSERT_TRUE(writer->StartEntry("test1", 0)); + ASSERT_TRUE(writer->Write(static_cast<const void*>(data1.get()), kTestDataLength)); + ASSERT_TRUE(writer->FinishEntry()); + ASSERT_FALSE(writer->HadError()); + + ASSERT_TRUE(writer->StartEntry("test2", 0)); + ASSERT_TRUE(writer->Write(static_cast<const void*>(data2.get()), kTestDataLength)); + ASSERT_TRUE(writer->FinishEntry()); + ASSERT_FALSE(writer->HadError()); + + writer.reset(); + + VerifyZipFile(output_path, "test1", data1.get()); + VerifyZipFile(output_path, "test2", data2.get()); +} + +TEST_F(ArchiveTest, ZipFileWriteFileSuccess) { + std::string output_path = GetTestPath("output.apk"); + std::unique_ptr<IArchiveWriter> writer = MakeZipFileWriter(output_path); + + std::unique_ptr<uint8_t[]> data1 = MakeTestArray(); + auto data1_copy = std::make_unique<uint8_t[]>(kTestDataLength); + std::copy(data1.get(), data1.get() + kTestDataLength, data1_copy.get()); + + std::unique_ptr<uint8_t[]> data2 = MakeTestArray(); + auto data2_copy = std::make_unique<uint8_t[]>(kTestDataLength); + std::copy(data2.get(), data2.get() + kTestDataLength, data2_copy.get()); + + auto input1 = std::make_unique<TestData>(data1_copy, kTestDataLength); + auto input2 = std::make_unique<TestData>(data2_copy, kTestDataLength); + + ASSERT_TRUE(writer->WriteFile("test1", 0, input1.get())); + ASSERT_FALSE(writer->HadError()); + ASSERT_TRUE(writer->WriteFile("test2", 0, input2.get())); + ASSERT_FALSE(writer->HadError()); + + writer.reset(); + + VerifyZipFile(output_path, "test1", data1.get()); + VerifyZipFile(output_path, "test2", data2.get()); +} + +TEST_F(ArchiveTest, ZipFileWriteFileError) { + std::string output_path = GetTestPath("output.apk"); + std::unique_ptr<IArchiveWriter> writer = MakeZipFileWriter(output_path); + std::unique_ptr<uint8_t[]> data = MakeTestArray(); + auto input = std::make_unique<TestData>(data, kTestDataLength); + input->error_ = "ZipFileWriteFileError"; + + ASSERT_FALSE(writer->WriteFile("test", 0, input.get())); + ASSERT_TRUE(writer->HadError()); + ASSERT_EQ("ZipFileWriteFileError", writer->GetError()); +} + +} // namespace aapt diff --git a/tools/aapt2/format/binary/BinaryResourceParser.cpp b/tools/aapt2/format/binary/BinaryResourceParser.cpp index fd8e36ebf823..f362744c0942 100644 --- a/tools/aapt2/format/binary/BinaryResourceParser.cpp +++ b/tools/aapt2/format/binary/BinaryResourceParser.cpp @@ -455,35 +455,6 @@ bool BinaryResourceParser::ParseOverlayable(const ResChunk_header* chunk) { const ResTable_overlayable_policy_header* policy_header = ConvertTo<ResTable_overlayable_policy_header>(parser.chunk()); - OverlayableItem::PolicyFlags policies = OverlayableItem::Policy::kNone; - if (policy_header->policy_flags & ResTable_overlayable_policy_header::POLICY_PUBLIC) { - policies |= OverlayableItem::Policy::kPublic; - } - if (policy_header->policy_flags - & ResTable_overlayable_policy_header::POLICY_SYSTEM_PARTITION) { - policies |= OverlayableItem::Policy::kSystem; - } - if (policy_header->policy_flags - & ResTable_overlayable_policy_header::POLICY_VENDOR_PARTITION) { - policies |= OverlayableItem::Policy::kVendor; - } - if (policy_header->policy_flags - & ResTable_overlayable_policy_header::POLICY_PRODUCT_PARTITION) { - policies |= OverlayableItem::Policy::kProduct; - } - if (policy_header->policy_flags - & ResTable_overlayable_policy_header::POLICY_SIGNATURE) { - policies |= OverlayableItem::Policy::kSignature; - } - if (policy_header->policy_flags - & ResTable_overlayable_policy_header::POLICY_ODM_PARTITION) { - policies |= OverlayableItem::Policy::kOdm; - } - if (policy_header->policy_flags - & ResTable_overlayable_policy_header::POLICY_OEM_PARTITION) { - policies |= OverlayableItem::Policy::kOem; - } - const ResTable_ref* const ref_begin = reinterpret_cast<const ResTable_ref*>( ((uint8_t *)policy_header) + util::DeviceToHost32(policy_header->header.headerSize)); const ResTable_ref* const ref_end = ref_begin @@ -501,7 +472,7 @@ bool BinaryResourceParser::ParseOverlayable(const ResChunk_header* chunk) { } OverlayableItem overlayable_item(overlayable); - overlayable_item.policies = policies; + overlayable_item.policies = policy_header->policy_flags; if (!table_->SetOverlayable(iter->second, overlayable_item, diag_)) { return false; } @@ -614,6 +585,7 @@ std::unique_ptr<Attribute> BinaryResourceParser::ParseAttr(const ResourceNameRef if (attr->type_mask & (ResTable_map::TYPE_ENUM | ResTable_map::TYPE_FLAGS)) { Attribute::Symbol symbol; symbol.value = util::DeviceToHost32(map_entry.value.data); + symbol.type = map_entry.value.dataType; symbol.symbol = Reference(util::DeviceToHost32(map_entry.name.ident)); attr->symbols.push_back(std::move(symbol)); } diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp index f2e72da4056a..4784ecf3d12c 100644 --- a/tools/aapt2/format/binary/TableFlattener.cpp +++ b/tools/aapt2/format/binary/TableFlattener.cpp @@ -59,10 +59,22 @@ static void strcpy16_htod(uint16_t* dst, size_t len, const StringPiece16& src) { dst[i] = 0; } +static bool cmp_style_ids(ResourceId a, ResourceId b) { + // If one of a and b is from the framework package (package ID 0x01), and the + // other is a dynamic ID (package ID 0x00), then put the dynamic ID after the + // framework ID. This ensures that when AssetManager resolves the dynamic IDs, + // they will be in sorted order as expected by AssetManager. + if ((a.package_id() == kFrameworkPackageId && b.package_id() == 0x00) || + (a.package_id() == 0x00 && b.package_id() == kFrameworkPackageId)) { + return b < a; + } + return a < b; +} + static bool cmp_style_entries(const Style::Entry& a, const Style::Entry& b) { if (a.key.id) { if (b.key.id) { - return a.key.id.value() < b.key.id.value(); + return cmp_style_ids(a.key.id.value(), b.key.id.value()); } return true; } else if (!b.key.id) { @@ -107,7 +119,7 @@ class MapFlattenVisitor : public ValueVisitor { } for (Attribute::Symbol& s : attr->symbols) { - BinaryPrimitive val(Res_value::TYPE_INT_DEC, s.value); + BinaryPrimitive val(s.type, s.value); FlattenEntry(&s.symbol, &val); } } @@ -221,21 +233,22 @@ class MapFlattenVisitor : public ValueVisitor { struct OverlayableChunk { std::string actor; Source source; - std::map<OverlayableItem::PolicyFlags, std::set<ResourceId>> policy_ids; + std::map<PolicyFlags, std::set<ResourceId>> policy_ids; }; class PackageFlattener { public: PackageFlattener(IAaptContext* context, ResourceTablePackage* package, const std::map<size_t, std::string>* shared_libs, bool use_sparse_entries, - bool collapse_key_stringpool, const std::set<std::string>& whitelisted_resources) + bool collapse_key_stringpool, + const std::set<ResourceName>& name_collapse_exemptions) : context_(context), diag_(context->GetDiagnostics()), package_(package), shared_libs_(shared_libs), use_sparse_entries_(use_sparse_entries), collapse_key_stringpool_(collapse_key_stringpool), - whitelisted_resources_(whitelisted_resources) { + name_collapse_exemptions_(name_collapse_exemptions) { } bool FlattenPackage(BigBuffer* buffer) { @@ -480,35 +493,12 @@ class PackageFlattener { return false; } - uint32_t policy_flags = 0; - if (item.policies & OverlayableItem::Policy::kPublic) { - policy_flags |= ResTable_overlayable_policy_header::POLICY_PUBLIC; - } - if (item.policies & OverlayableItem::Policy::kSystem) { - policy_flags |= ResTable_overlayable_policy_header::POLICY_SYSTEM_PARTITION; - } - if (item.policies & OverlayableItem::Policy::kVendor) { - policy_flags |= ResTable_overlayable_policy_header::POLICY_VENDOR_PARTITION; - } - if (item.policies & OverlayableItem::Policy::kProduct) { - policy_flags |= ResTable_overlayable_policy_header::POLICY_PRODUCT_PARTITION; - } - if (item.policies & OverlayableItem::Policy::kSignature) { - policy_flags |= ResTable_overlayable_policy_header::POLICY_SIGNATURE; - } - if (item.policies & OverlayableItem::Policy::kOdm) { - policy_flags |= ResTable_overlayable_policy_header::POLICY_ODM_PARTITION; - } - if (item.policies & OverlayableItem::Policy::kOem) { - policy_flags |= ResTable_overlayable_policy_header::POLICY_OEM_PARTITION; - } - - auto policy = overlayable_chunk->policy_ids.find(policy_flags); + auto policy = overlayable_chunk->policy_ids.find(item.policies); if (policy != overlayable_chunk->policy_ids.end()) { policy->second.insert(id); } else { overlayable_chunk->policy_ids.insert( - std::make_pair(policy_flags, std::set<ResourceId>{id})); + std::make_pair(item.policies, std::set<ResourceId>{id})); } } } @@ -546,7 +536,8 @@ class PackageFlattener { ChunkWriter policy_writer(buffer); auto* policy_type = policy_writer.StartChunk<ResTable_overlayable_policy_header>( RES_TABLE_OVERLAYABLE_POLICY_TYPE); - policy_type->policy_flags = util::HostToDevice32(static_cast<uint32_t>(policy_ids.first)); + policy_type->policy_flags = + static_cast<PolicyFlags>(util::HostToDevice32(static_cast<uint32_t>(policy_ids.first))); policy_type->entry_count = util::HostToDevice32(static_cast<uint32_t>( policy_ids.second.size())); // Write the ids after the policy header @@ -652,11 +643,12 @@ class PackageFlattener { for (ResourceEntry* entry : sorted_entries) { uint32_t local_key_index; + ResourceName resource_name({}, type->type, entry->name); if (!collapse_key_stringpool_ || - whitelisted_resources_.find(entry->name) != whitelisted_resources_.end()) { + name_collapse_exemptions_.find(resource_name) != name_collapse_exemptions_.end()) { local_key_index = (uint32_t)key_pool_.MakeRef(entry->name).index(); } else { - // resource isn't whitelisted, add it as obfuscated value + // resource isn't exempt from collapse, add it as obfuscated value local_key_index = (uint32_t)key_pool_.MakeRef(obfuscated_resource_name).index(); } // Group values by configuration. @@ -712,7 +704,7 @@ class PackageFlattener { StringPool type_pool_; StringPool key_pool_; bool collapse_key_stringpool_; - const std::set<std::string>& whitelisted_resources_; + const std::set<ResourceName>& name_collapse_exemptions_; }; } // namespace @@ -760,7 +752,7 @@ bool TableFlattener::Consume(IAaptContext* context, ResourceTable* table) { PackageFlattener flattener(context, package.get(), &table->included_packages_, options_.use_sparse_entries, options_.collapse_key_stringpool, - options_.whitelisted_resources); + options_.name_collapse_exemptions); if (!flattener.FlattenPackage(&package_buffer)) { return false; } diff --git a/tools/aapt2/format/binary/TableFlattener.h b/tools/aapt2/format/binary/TableFlattener.h index 73c17295556b..4360db190146 100644 --- a/tools/aapt2/format/binary/TableFlattener.h +++ b/tools/aapt2/format/binary/TableFlattener.h @@ -19,6 +19,7 @@ #include "android-base/macros.h" +#include "Resource.h" #include "ResourceTable.h" #include "process/IResourceTableConsumer.h" #include "util/BigBuffer.h" @@ -41,8 +42,8 @@ struct TableFlattenerOptions { // have name indices that point to this single value bool collapse_key_stringpool = false; - // Set of whitelisted resource names to avoid altering in key stringpool - std::set<std::string> whitelisted_resources; + // Set of resources to avoid collapsing to a single entry in key stringpool. + std::set<ResourceName> name_collapse_exemptions; // Map from original resource paths to shortened resource paths. std::map<std::string, std::string> shortened_path_map; diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp index a9409235e07a..59627ce579af 100644 --- a/tools/aapt2/format/binary/TableFlattener_test.cpp +++ b/tools/aapt2/format/binary/TableFlattener_test.cpp @@ -32,6 +32,8 @@ using ::testing::Gt; using ::testing::IsNull; using ::testing::NotNull; +using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags; + namespace aapt { class TableFlattenerTest : public ::testing::Test { @@ -431,6 +433,47 @@ TEST_F(TableFlattenerTest, FlattenSharedLibrary) { EXPECT_EQ("lib", iter->second); } +TEST_F(TableFlattenerTest, FlattenSharedLibraryWithStyle) { + std::unique_ptr<IAaptContext> context = + test::ContextBuilder().SetCompilationPackage("lib").SetPackageId(0x00).Build(); + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .SetPackageId("lib", 0x00) + .AddValue("lib:style/Theme", + ResourceId(0x00030001), + test::StyleBuilder() + .AddItem("lib:attr/bar", ResourceId(0x00010002), + ResourceUtils::TryParseInt("2")) + .AddItem("lib:attr/foo", ResourceId(0x00010001), + ResourceUtils::TryParseInt("1")) + .AddItem("android:attr/bar", ResourceId(0x01010002), + ResourceUtils::TryParseInt("4")) + .AddItem("android:attr/foo", ResourceId(0x01010001), + ResourceUtils::TryParseInt("3")) + .Build()) + .Build(); + ResourceTable result; + ASSERT_TRUE(Flatten(context.get(), {}, table.get(), &result)); + + Maybe<ResourceTable::SearchResult> search_result = + result.FindResource(test::ParseNameOrDie("lib:style/Theme")); + ASSERT_TRUE(search_result); + EXPECT_EQ(0x00u, search_result.value().package->id.value()); + EXPECT_EQ(0x03u, search_result.value().type->id.value()); + EXPECT_EQ(0x01u, search_result.value().entry->id.value()); + ASSERT_EQ(1u, search_result.value().entry->values.size()); + Value* value = search_result.value().entry->values[0]->value.get(); + Style* style = ValueCast<Style>(value); + ASSERT_TRUE(style); + ASSERT_EQ(4u, style->entries.size()); + // Ensure the attributes from the shared library come after the items from + // android. + EXPECT_EQ(0x01010001, style->entries[0].key.id.value()); + EXPECT_EQ(0x01010002, style->entries[1].key.id.value()); + EXPECT_EQ(0x00010001, style->entries[2].key.id.value()); + EXPECT_EQ(0x00010002, style->entries[3].key.id.value()); +} + TEST_F(TableFlattenerTest, FlattenTableReferencingSharedLibraries) { std::unique_ptr<IAaptContext> context = test::ContextBuilder().SetCompilationPackage("app").SetPackageId(0x7f).Build(); @@ -518,7 +561,7 @@ TEST_F(TableFlattenerTest, LongSharedLibraryPackageNameIsIllegal) { ASSERT_FALSE(Flatten(context.get(), {}, table.get(), &result)); } -TEST_F(TableFlattenerTest, ObfuscatingResourceNamesNoWhitelistSucceeds) { +TEST_F(TableFlattenerTest, ObfuscatingResourceNamesNoNameCollapseExemptionsSucceeds) { std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() .SetPackageId("com.app.test", 0x7f) @@ -572,7 +615,7 @@ TEST_F(TableFlattenerTest, ObfuscatingResourceNamesNoWhitelistSucceeds) { ResourceId(0x7f050000), {}, Res_value::TYPE_STRING, (uint32_t)idx, 0u)); } -TEST_F(TableFlattenerTest, ObfuscatingResourceNamesWithWhitelistSucceeds) { +TEST_F(TableFlattenerTest, ObfuscatingResourceNamesWithNameCollapseExemptionsSucceeds) { std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() .SetPackageId("com.app.test", 0x7f) @@ -591,21 +634,22 @@ TEST_F(TableFlattenerTest, ObfuscatingResourceNamesWithWhitelistSucceeds) { TableFlattenerOptions options; options.collapse_key_stringpool = true; - options.whitelisted_resources.insert("test"); - options.whitelisted_resources.insert("three"); + options.name_collapse_exemptions.insert(ResourceName({}, ResourceType::kId, "one")); + options.name_collapse_exemptions.insert(ResourceName({}, ResourceType::kString, "test")); ResTable res_table; ASSERT_TRUE(Flatten(context_.get(), options, table.get(), &res_table)); - EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated", + EXPECT_TRUE(Exists(&res_table, "com.app.test:id/one", ResourceId(0x7f020000), {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated", ResourceId(0x7f020001), {}, Res_value::TYPE_INT_BOOLEAN, 0u, 0u)); - EXPECT_TRUE(Exists(&res_table, "com.app.test:id/three", ResourceId(0x7f020002), {}, - Res_value::TYPE_REFERENCE, 0x7f020000u, 0u)); + EXPECT_TRUE(Exists(&res_table, "com.app.test:id/0_resource_name_obfuscated", + ResourceId(0x7f020002), {}, Res_value::TYPE_REFERENCE, 0x7f020000u, 0u)); + // Note that this resource is also named "one", but it's a different type, so gets obfuscated. EXPECT_TRUE(Exists(&res_table, "com.app.test:integer/0_resource_name_obfuscated", ResourceId(0x7f030000), {}, Res_value::TYPE_INT_DEC, 1u, ResTable_config::CONFIG_VERSION)); @@ -629,9 +673,9 @@ TEST_F(TableFlattenerTest, ObfuscatingResourceNamesWithWhitelistSucceeds) { TEST_F(TableFlattenerTest, FlattenOverlayable) { OverlayableItem overlayable_item(std::make_shared<Overlayable>("TestName", "overlay://theme")); - overlayable_item.policies |= OverlayableItem::Policy::kProduct; - overlayable_item.policies |= OverlayableItem::Policy::kSystem; - overlayable_item.policies |= OverlayableItem::Policy::kVendor; + overlayable_item.policies |= PolicyFlags::PRODUCT_PARTITION; + overlayable_item.policies |= PolicyFlags::SYSTEM_PARTITION; + overlayable_item.policies |= PolicyFlags::VENDOR_PARTITION; std::string name = "com.app.test:integer/overlayable"; std::unique_ptr<ResourceTable> table = @@ -649,27 +693,27 @@ TEST_F(TableFlattenerTest, FlattenOverlayable) { ASSERT_THAT(search_result.value().entry, NotNull()); ASSERT_TRUE(search_result.value().entry->overlayable_item); OverlayableItem& result_overlayable_item = search_result.value().entry->overlayable_item.value(); - EXPECT_EQ(result_overlayable_item.policies, OverlayableItem::Policy::kSystem - | OverlayableItem::Policy::kVendor - | OverlayableItem::Policy::kProduct); + EXPECT_EQ(result_overlayable_item.policies, PolicyFlags::SYSTEM_PARTITION + | PolicyFlags::VENDOR_PARTITION + | PolicyFlags::PRODUCT_PARTITION); } TEST_F(TableFlattenerTest, FlattenMultipleOverlayablePolicies) { auto overlayable = std::make_shared<Overlayable>("TestName", "overlay://theme"); std::string name_zero = "com.app.test:integer/overlayable_zero_item"; OverlayableItem overlayable_item_zero(overlayable); - overlayable_item_zero.policies |= OverlayableItem::Policy::kProduct; - overlayable_item_zero.policies |= OverlayableItem::Policy::kSystem; + overlayable_item_zero.policies |= PolicyFlags::PRODUCT_PARTITION; + overlayable_item_zero.policies |= PolicyFlags::SYSTEM_PARTITION; std::string name_one = "com.app.test:integer/overlayable_one_item"; OverlayableItem overlayable_item_one(overlayable); - overlayable_item_one.policies |= OverlayableItem::Policy::kPublic; + overlayable_item_one.policies |= PolicyFlags::PUBLIC; std::string name_two = "com.app.test:integer/overlayable_two_item"; OverlayableItem overlayable_item_two(overlayable); - overlayable_item_two.policies |= OverlayableItem::Policy::kProduct; - overlayable_item_two.policies |= OverlayableItem::Policy::kSystem; - overlayable_item_two.policies |= OverlayableItem::Policy::kVendor; + overlayable_item_two.policies |= PolicyFlags::PRODUCT_PARTITION; + overlayable_item_two.policies |= PolicyFlags::SYSTEM_PARTITION; + overlayable_item_two.policies |= PolicyFlags::VENDOR_PARTITION; std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() @@ -690,47 +734,48 @@ TEST_F(TableFlattenerTest, FlattenMultipleOverlayablePolicies) { ASSERT_THAT(search_result.value().entry, NotNull()); ASSERT_TRUE(search_result.value().entry->overlayable_item); OverlayableItem& overlayable_item = search_result.value().entry->overlayable_item.value(); - EXPECT_EQ(overlayable_item.policies, OverlayableItem::Policy::kSystem - | OverlayableItem::Policy::kProduct); + EXPECT_EQ(overlayable_item.policies, PolicyFlags::SYSTEM_PARTITION + | PolicyFlags::PRODUCT_PARTITION); search_result = output_table.FindResource(test::ParseNameOrDie(name_one)); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); ASSERT_TRUE(search_result.value().entry->overlayable_item); overlayable_item = search_result.value().entry->overlayable_item.value(); - EXPECT_EQ(overlayable_item.policies, OverlayableItem::Policy::kPublic); + EXPECT_EQ(overlayable_item.policies, PolicyFlags::PUBLIC); search_result = output_table.FindResource(test::ParseNameOrDie(name_two)); ASSERT_TRUE(search_result); ASSERT_THAT(search_result.value().entry, NotNull()); ASSERT_TRUE(search_result.value().entry->overlayable_item); overlayable_item = search_result.value().entry->overlayable_item.value(); - EXPECT_EQ(overlayable_item.policies, OverlayableItem::Policy::kSystem - | OverlayableItem::Policy::kProduct - | OverlayableItem::Policy::kVendor); + EXPECT_EQ(overlayable_item.policies, PolicyFlags::SYSTEM_PARTITION + | PolicyFlags::PRODUCT_PARTITION + | PolicyFlags::VENDOR_PARTITION); } TEST_F(TableFlattenerTest, FlattenMultipleOverlayable) { auto group = std::make_shared<Overlayable>("TestName", "overlay://theme"); std::string name_zero = "com.app.test:integer/overlayable_zero"; OverlayableItem overlayable_item_zero(group); - overlayable_item_zero.policies |= OverlayableItem::Policy::kProduct; - overlayable_item_zero.policies |= OverlayableItem::Policy::kSystem; + overlayable_item_zero.policies |= PolicyFlags::PRODUCT_PARTITION; + overlayable_item_zero.policies |= PolicyFlags::SYSTEM_PARTITION; auto group_one = std::make_shared<Overlayable>("OtherName", "overlay://customization"); std::string name_one = "com.app.test:integer/overlayable_one"; OverlayableItem overlayable_item_one(group_one); - overlayable_item_one.policies |= OverlayableItem::Policy::kPublic; + overlayable_item_one.policies |= PolicyFlags::PUBLIC; std::string name_two = "com.app.test:integer/overlayable_two"; OverlayableItem overlayable_item_two(group); - overlayable_item_two.policies |= OverlayableItem::Policy::kOdm; - overlayable_item_two.policies |= OverlayableItem::Policy::kOem; - overlayable_item_two.policies |= OverlayableItem::Policy::kVendor; + overlayable_item_two.policies |= PolicyFlags::ODM_PARTITION; + overlayable_item_two.policies |= PolicyFlags::OEM_PARTITION; + overlayable_item_two.policies |= PolicyFlags::VENDOR_PARTITION; std::string name_three = "com.app.test:integer/overlayable_three"; OverlayableItem overlayable_item_three(group_one); - overlayable_item_three.policies |= OverlayableItem::Policy::kSignature; + overlayable_item_three.policies |= PolicyFlags::SIGNATURE; + overlayable_item_three.policies |= PolicyFlags::ACTOR_SIGNATURE; std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() @@ -754,8 +799,8 @@ TEST_F(TableFlattenerTest, FlattenMultipleOverlayable) { OverlayableItem& result_overlayable = search_result.value().entry->overlayable_item.value(); EXPECT_EQ(result_overlayable.overlayable->name, "TestName"); EXPECT_EQ(result_overlayable.overlayable->actor, "overlay://theme"); - EXPECT_EQ(result_overlayable.policies, OverlayableItem::Policy::kSystem - | OverlayableItem::Policy::kProduct); + EXPECT_EQ(result_overlayable.policies, PolicyFlags::SYSTEM_PARTITION + | PolicyFlags::PRODUCT_PARTITION); search_result = output_table.FindResource(test::ParseNameOrDie(name_one)); ASSERT_TRUE(search_result); @@ -764,7 +809,7 @@ TEST_F(TableFlattenerTest, FlattenMultipleOverlayable) { result_overlayable = search_result.value().entry->overlayable_item.value(); EXPECT_EQ(result_overlayable.overlayable->name, "OtherName"); EXPECT_EQ(result_overlayable.overlayable->actor, "overlay://customization"); - EXPECT_EQ(result_overlayable.policies, OverlayableItem::Policy::kPublic); + EXPECT_EQ(result_overlayable.policies, PolicyFlags::PUBLIC); search_result = output_table.FindResource(test::ParseNameOrDie(name_two)); ASSERT_TRUE(search_result); @@ -773,9 +818,9 @@ TEST_F(TableFlattenerTest, FlattenMultipleOverlayable) { result_overlayable = search_result.value().entry->overlayable_item.value(); EXPECT_EQ(result_overlayable.overlayable->name, "TestName"); EXPECT_EQ(result_overlayable.overlayable->actor, "overlay://theme"); - EXPECT_EQ(result_overlayable.policies, OverlayableItem::Policy::kOdm - | OverlayableItem::Policy::kOem - | OverlayableItem::Policy::kVendor); + EXPECT_EQ(result_overlayable.policies, PolicyFlags::ODM_PARTITION + | PolicyFlags::OEM_PARTITION + | PolicyFlags::VENDOR_PARTITION); search_result = output_table.FindResource(test::ParseNameOrDie(name_three)); ASSERT_TRUE(search_result); @@ -784,7 +829,8 @@ TEST_F(TableFlattenerTest, FlattenMultipleOverlayable) { result_overlayable = search_result.value().entry->overlayable_item.value(); EXPECT_EQ(result_overlayable.overlayable->name, "OtherName"); EXPECT_EQ(result_overlayable.overlayable->actor, "overlay://customization"); - EXPECT_EQ(result_overlayable.policies, OverlayableItem::Policy::kSignature); + EXPECT_EQ(result_overlayable.policies, PolicyFlags::SIGNATURE + | PolicyFlags::ACTOR_SIGNATURE); } TEST_F(TableFlattenerTest, FlattenOverlayableNoPolicyFails) { diff --git a/tools/aapt2/format/binary/XmlFlattener_test.cpp b/tools/aapt2/format/binary/XmlFlattener_test.cpp index 1dac493359e4..c24488b16153 100644 --- a/tools/aapt2/format/binary/XmlFlattener_test.cpp +++ b/tools/aapt2/format/binary/XmlFlattener_test.cpp @@ -226,10 +226,10 @@ TEST_F(XmlFlattenerTest, FlattenNonStandardPackageId) { ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); // The tree needs a custom DynamicRefTable since it is not using a standard app ID (0x7f). - android::DynamicRefTable dynamic_ref_table; - dynamic_ref_table.addMapping(0x80, 0x80); + auto dynamic_ref_table = std::make_shared<android::DynamicRefTable>(); + dynamic_ref_table->addMapping(0x80, 0x80); - android::ResXMLTree tree(&dynamic_ref_table); + auto tree = android::ResXMLTree(std::move(dynamic_ref_table)); ASSERT_TRUE(Flatten(doc.get(), &tree)); while (tree.next() != android::ResXMLTree::START_TAG) { diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp index bb21c1c539fb..2fd01d7f3dee 100644 --- a/tools/aapt2/format/proto/ProtoDeserialize.cpp +++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp @@ -30,6 +30,8 @@ using ::android::ConfigDescription; using ::android::LocaleValue; using ::android::ResStringPool; +using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags; + namespace aapt { namespace { @@ -379,25 +381,28 @@ bool DeserializeOverlayableItemFromPb(const pb::OverlayableItem& pb_overlayable, for (const int policy : pb_overlayable.policy()) { switch (policy) { case pb::OverlayableItem::PUBLIC: - out_overlayable->policies |= OverlayableItem::Policy::kPublic; + out_overlayable->policies |= PolicyFlags::PUBLIC; break; case pb::OverlayableItem::SYSTEM: - out_overlayable->policies |= OverlayableItem::Policy::kSystem; + out_overlayable->policies |= PolicyFlags::SYSTEM_PARTITION; break; case pb::OverlayableItem::VENDOR: - out_overlayable->policies |= OverlayableItem::Policy::kVendor; + out_overlayable->policies |= PolicyFlags::VENDOR_PARTITION; break; case pb::OverlayableItem::PRODUCT: - out_overlayable->policies |= OverlayableItem::Policy::kProduct; + out_overlayable->policies |= PolicyFlags::PRODUCT_PARTITION; break; case pb::OverlayableItem::SIGNATURE: - out_overlayable->policies |= OverlayableItem::Policy::kSignature; + out_overlayable->policies |= PolicyFlags::SIGNATURE; break; case pb::OverlayableItem::ODM: - out_overlayable->policies |= OverlayableItem::Policy::kOdm; + out_overlayable->policies |= PolicyFlags::ODM_PARTITION; break; case pb::OverlayableItem::OEM: - out_overlayable->policies |= OverlayableItem::Policy::kOem; + out_overlayable->policies |= PolicyFlags::OEM_PARTITION; + break; + case pb::OverlayableItem::ACTOR: + out_overlayable->policies |= PolicyFlags::ACTOR_SIGNATURE; break; default: *out_error = "unknown overlayable policy"; @@ -634,6 +639,7 @@ static bool DeserializeReferenceFromPb(const pb::Reference& pb_ref, Reference* o std::string* out_error) { out_ref->reference_type = DeserializeReferenceTypeFromPb(pb_ref.type()); out_ref->private_reference = pb_ref.private_(); + out_ref->is_dynamic = pb_ref.is_dynamic().value(); if (pb_ref.id() != 0) { out_ref->id = ResourceId(pb_ref.id()); @@ -708,6 +714,8 @@ std::unique_ptr<Value> DeserializeValueFromPb(const pb::Value& pb_value, return {}; } symbol.value = pb_symbol.value(); + symbol.type = pb_symbol.type() != 0U ? pb_symbol.type() + : android::Res_value::TYPE_INT_DEC; attr->symbols.push_back(std::move(symbol)); } value = std::move(attr); diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp index a54822b20302..ba6df22af9d3 100644 --- a/tools/aapt2/format/proto/ProtoSerialize.cpp +++ b/tools/aapt2/format/proto/ProtoSerialize.cpp @@ -21,6 +21,8 @@ using android::ConfigDescription; +using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags; + namespace aapt { void SerializeStringPoolToPb(const StringPool& pool, pb::StringPool* out_pb_pool, IDiagnostics* diag) { @@ -290,43 +292,51 @@ static void SerializeOverlayableItemToPb(const OverlayableItem& overlayable_item pb::Overlayable* pb_overlayable = pb_table->add_overlayable(); pb_overlayable->set_name(overlayable_item.overlayable->name); pb_overlayable->set_actor(overlayable_item.overlayable->actor); - SerializeSourceToPb(overlayable_item.overlayable->source, source_pool, - pb_overlayable->mutable_source()); + if (source_pool != nullptr) { + SerializeSourceToPb(overlayable_item.overlayable->source, source_pool, + pb_overlayable->mutable_source()); + } } pb::OverlayableItem* pb_overlayable_item = pb_entry->mutable_overlayable_item(); pb_overlayable_item->set_overlayable_idx(i); - if (overlayable_item.policies & OverlayableItem::Policy::kPublic) { + if (overlayable_item.policies & PolicyFlags::PUBLIC) { pb_overlayable_item->add_policy(pb::OverlayableItem::PUBLIC); } - if (overlayable_item.policies & OverlayableItem::Policy::kProduct) { + if (overlayable_item.policies & PolicyFlags::PRODUCT_PARTITION) { pb_overlayable_item->add_policy(pb::OverlayableItem::PRODUCT); } - if (overlayable_item.policies & OverlayableItem::Policy::kSystem) { + if (overlayable_item.policies & PolicyFlags::SYSTEM_PARTITION) { pb_overlayable_item->add_policy(pb::OverlayableItem::SYSTEM); } - if (overlayable_item.policies & OverlayableItem::Policy::kVendor) { + if (overlayable_item.policies & PolicyFlags::VENDOR_PARTITION) { pb_overlayable_item->add_policy(pb::OverlayableItem::VENDOR); } - if (overlayable_item.policies & OverlayableItem::Policy::kSignature) { + if (overlayable_item.policies & PolicyFlags::SIGNATURE) { pb_overlayable_item->add_policy(pb::OverlayableItem::SIGNATURE); } - if (overlayable_item.policies & OverlayableItem::Policy::kOdm) { + if (overlayable_item.policies & PolicyFlags::ODM_PARTITION) { pb_overlayable_item->add_policy(pb::OverlayableItem::ODM); } - if (overlayable_item.policies & OverlayableItem::Policy::kOem) { + if (overlayable_item.policies & PolicyFlags::OEM_PARTITION) { pb_overlayable_item->add_policy(pb::OverlayableItem::OEM); } + if (overlayable_item.policies & PolicyFlags::ACTOR_SIGNATURE) { + pb_overlayable_item->add_policy(pb::OverlayableItem::ACTOR); + } - SerializeSourceToPb(overlayable_item.source, source_pool, - pb_overlayable_item->mutable_source()); + if (source_pool != nullptr) { + SerializeSourceToPb(overlayable_item.source, source_pool, + pb_overlayable_item->mutable_source()); + } pb_overlayable_item->set_comment(overlayable_item.comment); } void SerializeTableToPb(const ResourceTable& table, pb::ResourceTable* out_table, - IDiagnostics* diag) { - StringPool source_pool; + IDiagnostics* diag, SerializeTableOptions options) { + auto source_pool = (options.exclude_sources) ? nullptr : util::make_unique<StringPool>(); + pb::ToolFingerprint* pb_fingerprint = out_table->add_tool_fingerprint(); pb_fingerprint->set_tool(util::GetToolName()); pb_fingerprint->set_version(util::GetToolFingerprint()); @@ -356,32 +366,40 @@ void SerializeTableToPb(const ResourceTable& table, pb::ResourceTable* out_table // Write the Visibility struct. pb::Visibility* pb_visibility = pb_entry->mutable_visibility(); pb_visibility->set_level(SerializeVisibilityToPb(entry->visibility.level)); - SerializeSourceToPb(entry->visibility.source, &source_pool, - pb_visibility->mutable_source()); + if (source_pool != nullptr) { + SerializeSourceToPb(entry->visibility.source, source_pool.get(), + pb_visibility->mutable_source()); + } pb_visibility->set_comment(entry->visibility.comment); if (entry->allow_new) { pb::AllowNew* pb_allow_new = pb_entry->mutable_allow_new(); - SerializeSourceToPb(entry->allow_new.value().source, &source_pool, - pb_allow_new->mutable_source()); + if (source_pool != nullptr) { + SerializeSourceToPb(entry->allow_new.value().source, source_pool.get(), + pb_allow_new->mutable_source()); + } pb_allow_new->set_comment(entry->allow_new.value().comment); } if (entry->overlayable_item) { - SerializeOverlayableItemToPb(entry->overlayable_item.value(), overlayables, &source_pool, - pb_entry, out_table); + SerializeOverlayableItemToPb(entry->overlayable_item.value(), overlayables, + source_pool.get(), pb_entry, out_table); } for (const std::unique_ptr<ResourceConfigValue>& config_value : entry->values) { pb::ConfigValue* pb_config_value = pb_entry->add_config_value(); SerializeConfig(config_value->config, pb_config_value->mutable_config()); pb_config_value->mutable_config()->set_product(config_value->product); - SerializeValueToPb(*config_value->value, pb_config_value->mutable_value(), &source_pool); + SerializeValueToPb(*config_value->value, pb_config_value->mutable_value(), + source_pool.get()); } } } } - SerializeStringPoolToPb(source_pool, out_table->mutable_source_pool(), diag); + + if (source_pool != nullptr) { + SerializeStringPoolToPb(*source_pool, out_table->mutable_source_pool(), diag); + } } static pb::Reference_Type SerializeReferenceTypeToPb(Reference::Type type) { @@ -405,6 +423,9 @@ static void SerializeReferenceToPb(const Reference& ref, pb::Reference* pb_ref) pb_ref->set_private_(ref.private_reference); pb_ref->set_type(SerializeReferenceTypeToPb(ref.reference_type)); + if (ref.is_dynamic) { + pb_ref->mutable_is_dynamic()->set_value(ref.is_dynamic); + } } template <typename T> @@ -552,6 +573,7 @@ class ValueSerializer : public ConstValueVisitor { SerializeItemMetaDataToPb(symbol.symbol, pb_symbol, src_pool_); SerializeReferenceToPb(symbol.symbol, pb_symbol->mutable_name()); pb_symbol->set_value(symbol.value); + pb_symbol->set_type(symbol.type); } } diff --git a/tools/aapt2/format/proto/ProtoSerialize.h b/tools/aapt2/format/proto/ProtoSerialize.h index 33ffd182435b..7a3ea9903732 100644 --- a/tools/aapt2/format/proto/ProtoSerialize.h +++ b/tools/aapt2/format/proto/ProtoSerialize.h @@ -35,6 +35,11 @@ struct SerializeXmlOptions { bool remove_empty_text_nodes = false; }; +struct SerializeTableOptions { + /** Prevent serializing the source pool and source protos. */ + bool exclude_sources = false; +}; + // Serializes a Value to its protobuf representation. An optional StringPool will hold the // source path string. void SerializeValueToPb(const Value& value, pb::Value* out_value, StringPool* src_pool = nullptr); @@ -59,7 +64,8 @@ void SerializeStringPoolToPb(const StringPool& pool, pb::StringPool* out_pb_pool void SerializeConfig(const android::ConfigDescription& config, pb::Configuration* out_pb_config); // Serializes a ResourceTable into its protobuf representation. -void SerializeTableToPb(const ResourceTable& table, pb::ResourceTable* out_table, IDiagnostics* diag); +void SerializeTableToPb(const ResourceTable& table, pb::ResourceTable* out_table, + IDiagnostics* diag, SerializeTableOptions options = {}); // Serializes a ResourceFile into its protobuf representation. void SerializeCompiledFileToPb(const ResourceFile& file, pb::internal::CompiledFile* out_file); diff --git a/tools/aapt2/format/proto/ProtoSerialize_test.cpp b/tools/aapt2/format/proto/ProtoSerialize_test.cpp index f252f33f44fb..1a7de6dc1c48 100644 --- a/tools/aapt2/format/proto/ProtoSerialize_test.cpp +++ b/tools/aapt2/format/proto/ProtoSerialize_test.cpp @@ -28,6 +28,8 @@ using ::testing::NotNull; using ::testing::SizeIs; using ::testing::StrEq; +using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags; + namespace aapt { class MockFileCollection : public io::IFileCollection { @@ -80,7 +82,7 @@ TEST(ProtoSerializeTest, SerializeSinglePackage) { test::BuildPrimitive(android::Res_value::TYPE_INT_DEC, 123u), context->GetDiagnostics())); ASSERT_TRUE(table->AddResource( test::ParseNameOrDie("com.app.a:integer/one"), test::ParseConfigOrDie("land"), "tablet", - test::BuildPrimitive(android::Res_value::TYPE_INT_DEC, 321u), context->GetDiagnostics())); + test::BuildPrimitive(android::Res_value::TYPE_INT_HEX, 321u), context->GetDiagnostics())); // Make a reference with both resource name and resource ID. // The reference should point to a resource outside of this table to test that both name and id @@ -133,11 +135,13 @@ TEST(ProtoSerializeTest, SerializeSinglePackage) { &new_table, "com.app.a:integer/one", test::ParseConfigOrDie("land"), ""); ASSERT_THAT(prim, NotNull()); EXPECT_THAT(prim->value.data, Eq(123u)); + EXPECT_THAT(prim->value.dataType, Eq(0x10)); prim = test::GetValueForConfigAndProduct<BinaryPrimitive>( &new_table, "com.app.a:integer/one", test::ParseConfigOrDie("land"), "tablet"); ASSERT_THAT(prim, NotNull()); EXPECT_THAT(prim->value.data, Eq(321u)); + EXPECT_THAT(prim->value.dataType, Eq(0x11)); Reference* actual_ref = test::GetValue<Reference>(&new_table, "com.app.a:layout/abc"); ASSERT_THAT(actual_ref, NotNull()); @@ -169,7 +173,7 @@ TEST(ProtoSerializeTest, SerializeSinglePackage) { EXPECT_THAT(result_overlayable_item.overlayable->actor, Eq("overlay://theme")); EXPECT_THAT(result_overlayable_item.overlayable->source.path, Eq("res/values/overlayable.xml")); EXPECT_THAT(result_overlayable_item.overlayable->source.line, Eq(40)); - EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kNone)); + EXPECT_THAT(result_overlayable_item.policies, Eq(PolicyFlags::NONE)); EXPECT_THAT(result_overlayable_item.source.path, Eq("res/values/overlayable.xml")); EXPECT_THAT(result_overlayable_item.source.line, Eq(42)); } @@ -514,23 +518,28 @@ TEST(ProtoSerializeTest, SerializeDeserializeConfiguration) { TEST(ProtoSerializeTest, SerializeAndDeserializeOverlayable) { OverlayableItem overlayable_item_foo(std::make_shared<Overlayable>( "CustomizableResources", "overlay://customization")); - overlayable_item_foo.policies |= OverlayableItem::Policy::kSystem; - overlayable_item_foo.policies |= OverlayableItem::Policy::kProduct; + overlayable_item_foo.policies |= PolicyFlags::SYSTEM_PARTITION; + overlayable_item_foo.policies |= PolicyFlags::PRODUCT_PARTITION; OverlayableItem overlayable_item_bar(std::make_shared<Overlayable>( "TaskBar", "overlay://theme")); - overlayable_item_bar.policies |= OverlayableItem::Policy::kPublic; - overlayable_item_bar.policies |= OverlayableItem::Policy::kVendor; + overlayable_item_bar.policies |= PolicyFlags::PUBLIC; + overlayable_item_bar.policies |= PolicyFlags::VENDOR_PARTITION; OverlayableItem overlayable_item_baz(std::make_shared<Overlayable>( "FontPack", "overlay://theme")); - overlayable_item_baz.policies |= OverlayableItem::Policy::kPublic; + overlayable_item_baz.policies |= PolicyFlags::PUBLIC; OverlayableItem overlayable_item_boz(std::make_shared<Overlayable>( "IconPack", "overlay://theme")); - overlayable_item_boz.policies |= OverlayableItem::Policy::kSignature; - overlayable_item_boz.policies |= OverlayableItem::Policy::kOdm; - overlayable_item_boz.policies |= OverlayableItem::Policy::kOem; + overlayable_item_boz.policies |= PolicyFlags::SIGNATURE; + overlayable_item_boz.policies |= PolicyFlags::ODM_PARTITION; + overlayable_item_boz.policies |= PolicyFlags::OEM_PARTITION; + + OverlayableItem overlayable_item_actor_config(std::make_shared<Overlayable>( + "ActorConfig", "overlay://theme")); + overlayable_item_actor_config.policies |= PolicyFlags::SIGNATURE; + overlayable_item_actor_config.policies |= PolicyFlags::ACTOR_SIGNATURE; OverlayableItem overlayable_item_biz(std::make_shared<Overlayable>( "Other", "overlay://customization")); @@ -544,6 +553,7 @@ TEST(ProtoSerializeTest, SerializeAndDeserializeOverlayable) { .SetOverlayable("com.app.a:bool/baz", overlayable_item_baz) .SetOverlayable("com.app.a:bool/boz", overlayable_item_boz) .SetOverlayable("com.app.a:bool/biz", overlayable_item_biz) + .SetOverlayable("com.app.a:bool/actor_config", overlayable_item_actor_config) .AddValue("com.app.a:bool/fiz", ResourceUtils::TryParseBool("true")) .Build(); @@ -563,8 +573,8 @@ TEST(ProtoSerializeTest, SerializeAndDeserializeOverlayable) { OverlayableItem& overlayable_item = search_result.value().entry->overlayable_item.value(); EXPECT_THAT(overlayable_item.overlayable->name, Eq("CustomizableResources")); EXPECT_THAT(overlayable_item.overlayable->actor, Eq("overlay://customization")); - EXPECT_THAT(overlayable_item.policies, Eq(OverlayableItem::Policy::kSystem - | OverlayableItem::Policy::kProduct)); + EXPECT_THAT(overlayable_item.policies, Eq(PolicyFlags::SYSTEM_PARTITION + | PolicyFlags::PRODUCT_PARTITION)); search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/bar")); ASSERT_TRUE(search_result); @@ -572,8 +582,8 @@ TEST(ProtoSerializeTest, SerializeAndDeserializeOverlayable) { overlayable_item = search_result.value().entry->overlayable_item.value(); EXPECT_THAT(overlayable_item.overlayable->name, Eq("TaskBar")); EXPECT_THAT(overlayable_item.overlayable->actor, Eq("overlay://theme")); - EXPECT_THAT(overlayable_item.policies, Eq(OverlayableItem::Policy::kPublic - | OverlayableItem::Policy::kVendor)); + EXPECT_THAT(overlayable_item.policies, Eq(PolicyFlags::PUBLIC + | PolicyFlags::VENDOR_PARTITION)); search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/baz")); ASSERT_TRUE(search_result); @@ -581,7 +591,7 @@ TEST(ProtoSerializeTest, SerializeAndDeserializeOverlayable) { overlayable_item = search_result.value().entry->overlayable_item.value(); EXPECT_THAT(overlayable_item.overlayable->name, Eq("FontPack")); EXPECT_THAT(overlayable_item.overlayable->actor, Eq("overlay://theme")); - EXPECT_THAT(overlayable_item.policies, Eq(OverlayableItem::Policy::kPublic)); + EXPECT_THAT(overlayable_item.policies, Eq(PolicyFlags::PUBLIC)); search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/boz")); ASSERT_TRUE(search_result); @@ -589,16 +599,25 @@ TEST(ProtoSerializeTest, SerializeAndDeserializeOverlayable) { overlayable_item = search_result.value().entry->overlayable_item.value(); EXPECT_THAT(overlayable_item.overlayable->name, Eq("IconPack")); EXPECT_THAT(overlayable_item.overlayable->actor, Eq("overlay://theme")); - EXPECT_THAT(overlayable_item.policies, Eq(OverlayableItem::Policy::kSignature - | OverlayableItem::Policy::kOdm - | OverlayableItem::Policy::kOem)); + EXPECT_THAT(overlayable_item.policies, Eq(PolicyFlags::SIGNATURE + | PolicyFlags::ODM_PARTITION + | PolicyFlags::OEM_PARTITION)); + + search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/actor_config")); + ASSERT_TRUE(search_result); + ASSERT_TRUE(search_result.value().entry->overlayable_item); + overlayable_item = search_result.value().entry->overlayable_item.value(); + EXPECT_THAT(overlayable_item.overlayable->name, Eq("ActorConfig")); + EXPECT_THAT(overlayable_item.overlayable->actor, Eq("overlay://theme")); + EXPECT_THAT(overlayable_item.policies, Eq(PolicyFlags::SIGNATURE + | PolicyFlags::ACTOR_SIGNATURE)); search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/biz")); ASSERT_TRUE(search_result); ASSERT_TRUE(search_result.value().entry->overlayable_item); overlayable_item = search_result.value().entry->overlayable_item.value(); EXPECT_THAT(overlayable_item.overlayable->name, Eq("Other")); - EXPECT_THAT(overlayable_item.policies, Eq(OverlayableItem::Policy::kNone)); + EXPECT_THAT(overlayable_item.policies, Eq(PolicyFlags::NONE)); EXPECT_THAT(overlayable_item.comment, Eq("comment")); search_result = new_table.FindResource(test::ParseNameOrDie("com.app.a:bool/fiz")); @@ -606,4 +625,41 @@ TEST(ProtoSerializeTest, SerializeAndDeserializeOverlayable) { ASSERT_FALSE(search_result.value().entry->overlayable_item); } +TEST(ProtoSerializeTest, SerializeAndDeserializeDynamicReference) { + Reference ref(ResourceId(0x00010001)); + ref.is_dynamic = true; + + pb::Item pb_item; + SerializeItemToPb(ref, &pb_item); + + ASSERT_TRUE(pb_item.has_ref()); + EXPECT_EQ(pb_item.ref().id(), ref.id.value().id); + EXPECT_TRUE(pb_item.ref().is_dynamic().value()); + + std::unique_ptr<Item> item = DeserializeItemFromPb(pb_item, android::ResStringPool(), + android::ConfigDescription(), nullptr, + nullptr, nullptr); + Reference* actual_ref = ValueCast<Reference>(item.get()); + EXPECT_EQ(actual_ref->id.value().id, ref.id.value().id); + EXPECT_TRUE(actual_ref->is_dynamic); +} + +TEST(ProtoSerializeTest, SerializeAndDeserializeNonDynamicReference) { + Reference ref(ResourceId(0x00010001)); + + pb::Item pb_item; + SerializeItemToPb(ref, &pb_item); + + ASSERT_TRUE(pb_item.has_ref()); + EXPECT_EQ(pb_item.ref().id(), ref.id.value().id); + EXPECT_FALSE(pb_item.ref().has_is_dynamic()); + + std::unique_ptr<Item> item = DeserializeItemFromPb(pb_item, android::ResStringPool(), + android::ConfigDescription(), nullptr, + nullptr, nullptr); + Reference* actual_ref = ValueCast<Reference>(item.get()); + EXPECT_EQ(actual_ref->id.value().id, ref.id.value().id); + EXPECT_FALSE(actual_ref->is_dynamic); +} + } // namespace aapt diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/Android.mk b/tools/aapt2/integration-tests/MergeOnlyTest/Android.mk new file mode 100644 index 000000000000..6361f9b8ae7d --- /dev/null +++ b/tools/aapt2/integration-tests/MergeOnlyTest/Android.mk @@ -0,0 +1,2 @@ +LOCAL_PATH := $(call my-dir) +include $(call all-makefiles-under,$(LOCAL_PATH)) diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/App/Android.mk b/tools/aapt2/integration-tests/MergeOnlyTest/App/Android.mk new file mode 100644 index 000000000000..6bc2064c6e63 --- /dev/null +++ b/tools/aapt2/integration-tests/MergeOnlyTest/App/Android.mk @@ -0,0 +1,29 @@ +// +// Copyright (C) 2019 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. +// + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_USE_AAPT2 := true +LOCAL_AAPT_NAMESPACES := true +LOCAL_PACKAGE_NAME := AaptTestMergeOnly_App +LOCAL_SDK_VERSION := current +LOCAL_EXPORT_PACKAGE_RESOURCES := true +LOCAL_MODULE_TAGS := tests +LOCAL_STATIC_ANDROID_LIBRARIES := \ + AaptTestMergeOnly_LeafLib \ + AaptTestMergeOnly_LocalLib +include $(BUILD_PACKAGE)
\ No newline at end of file diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/App/AndroidManifest.xml b/tools/aapt2/integration-tests/MergeOnlyTest/App/AndroidManifest.xml new file mode 100644 index 000000000000..bc3a7e5ebd21 --- /dev/null +++ b/tools/aapt2/integration-tests/MergeOnlyTest/App/AndroidManifest.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2019 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.android.app"> + + <application + android:label="@*com.local.lib:string/lib_string"/> + +</manifest>
\ No newline at end of file diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/Android.mk b/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/Android.mk new file mode 100644 index 000000000000..7bf8cf84426c --- /dev/null +++ b/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/Android.mk @@ -0,0 +1,28 @@ +// +// Copyright (C) 2019 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. +// + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_USE_AAPT2 := true +LOCAL_AAPT_NAMESPACES := true +LOCAL_MODULE := AaptTestMergeOnly_LeafLib +LOCAL_SDK_VERSION := current +LOCAL_MODULE_TAGS := tests +LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res +LOCAL_MIN_SDK_VERSION := 21 +LOCAL_AAPT_FLAGS := --merge-only +include $(BUILD_STATIC_JAVA_LIBRARY)
\ No newline at end of file diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/AndroidManifest.xml b/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/AndroidManifest.xml new file mode 100644 index 000000000000..9907bd98790d --- /dev/null +++ b/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/AndroidManifest.xml @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2019 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. +--> + +<manifest package="com.leaf.lib" /> diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/res/layout/activity.xml b/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/res/layout/activity.xml new file mode 100644 index 000000000000..07de87fa1d33 --- /dev/null +++ b/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/res/layout/activity.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2019 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. +--> + +<RelativeLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:leaf="http://schemas.android.com/apk/res/com.leaf.lib"> + + <TextView android:text="@string/leaf_string" + leaf:leaf_attr="hello" + style="@style/LeafChildStyle"/> + +</RelativeLayout>
\ No newline at end of file diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/res/values/values.xml b/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/res/values/values.xml new file mode 100644 index 000000000000..7f94c26de23c --- /dev/null +++ b/tools/aapt2/integration-tests/MergeOnlyTest/LeafLib/res/values/values.xml @@ -0,0 +1,43 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2019 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. +--> + +<resources> + <attr format="string" name="leaf_attr"/> + <attr format="string" name="leaf_attr2"/> + + <string name="leaf_string">I am a leaf</string> + + <style name="LeafParentStyle"> + <item type="attr" name="leaf_attr"/> + <item type="attr" name="leaf_attr2"/> + </style> + + <style name="LeafChildStyle" parent="LeafParentStyle"> + <item type="attr" name="leaf_attr2">hello</item> + </style> + + <style name="LeafParentStyle.DottedChild"/> + + <declare-styleable name="leaf_ds"> + <attr name="leaf_attr">hello</attr> + </declare-styleable> + + <public type="attr" name="leaf_attr"/> + <public type="attr" name="leaf_attr2"/> + <public type="style" name="LeafParentStyle"/> + <public type="style" name="LeafChildStyle"/> + <public type="style" name="LeafParentStyle.DottedChild"/> +</resources>
\ No newline at end of file diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/Android.mk b/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/Android.mk new file mode 100644 index 000000000000..ba781c56a913 --- /dev/null +++ b/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/Android.mk @@ -0,0 +1,28 @@ +// +// Copyright (C) 2019 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. +// + +LOCAL_PATH := $(call my-dir) + +include $(CLEAR_VARS) +LOCAL_USE_AAPT2 := true +LOCAL_AAPT_NAMESPACES := true +LOCAL_MODULE := AaptTestMergeOnly_LocalLib +LOCAL_SDK_VERSION := current +LOCAL_MODULE_TAGS := tests +LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res +LOCAL_MIN_SDK_VERSION := 21 +LOCAL_AAPT_FLAGS := --merge-only +include $(BUILD_STATIC_JAVA_LIBRARY)
\ No newline at end of file diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/AndroidManifest.xml b/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/AndroidManifest.xml new file mode 100644 index 000000000000..aa0ff5dcb4b6 --- /dev/null +++ b/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/AndroidManifest.xml @@ -0,0 +1,28 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2019 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. +--> + +<manifest xmlns:android="http://schemas.android.com/apk/res/android" + package="com.local.lib"> + + <application> + + <activity + android:name="com.myapp.MyActivity" + android:theme="@com.leaf.lib:style/LeafParentStyle.DottedChild"/> + + </application> + +</manifest> diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/res/layout/activity.xml b/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/res/layout/activity.xml new file mode 100644 index 000000000000..80d2fd6bcd09 --- /dev/null +++ b/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/res/layout/activity.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2019 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. +--> + +<RelativeLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:leaf="http://schemas.android.com/apk/res/com.leaf.lib"> + + <TextView android:text="@*com.leaf.lib:string/leaf_string" + leaf:leaf_attr="hello" + style="@com.leaf.lib:style/LeafChildStyle"/> + +</RelativeLayout>
\ No newline at end of file diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/res/layout/includer.xml b/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/res/layout/includer.xml new file mode 100644 index 000000000000..f06371874a45 --- /dev/null +++ b/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/res/layout/includer.xml @@ -0,0 +1,25 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2019 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. +--> + +<RelativeLayout + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:leaf="http://schemas.android.com/apk/res/com.leaf.lib"> + + <include layout="@layout/activity"/> + + <include layout="@*com.leaf.lib:layout/activity"/> + +</RelativeLayout>
\ No newline at end of file diff --git a/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/res/values/values.xml b/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/res/values/values.xml new file mode 100644 index 000000000000..2f9704df0570 --- /dev/null +++ b/tools/aapt2/integration-tests/MergeOnlyTest/LocalLib/res/values/values.xml @@ -0,0 +1,29 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2019 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. +--> + +<resources> + <string name="lib_string">@*com.leaf.lib:string/leaf_string</string> + + <style name="lib_style" parent="@com.leaf.lib:style/LeafChildStyle"> + <item name="com.leaf.lib:leaf_attr">hello</item> + </style> + + <style name="LeafParentStyle.DottedChild.LocalLibStyle" + parent="@com.leaf.lib:style/LeafParentStyle.DottedChild"/> + + <public type="style" name="LeafParentStyle.DottedChild.LocalLibStyle"/> + +</resources>
\ No newline at end of file diff --git a/tools/aapt2/io/Util.cpp b/tools/aapt2/io/Util.cpp index ce6d9352180d..bb925c9b3f8e 100644 --- a/tools/aapt2/io/Util.cpp +++ b/tools/aapt2/io/Util.cpp @@ -58,7 +58,7 @@ bool CopyFileToArchivePreserveCompression(IAaptContext* context, io::IFile* file return CopyFileToArchive(context, file, out_path, compression_flags, writer); } -bool CopyProtoToArchive(IAaptContext* context, ::google::protobuf::MessageLite* proto_msg, +bool CopyProtoToArchive(IAaptContext* context, ::google::protobuf::Message* proto_msg, const std::string& out_path, uint32_t compression_flags, IArchiveWriter* writer) { TRACE_CALL(); diff --git a/tools/aapt2/io/Util.h b/tools/aapt2/io/Util.h index 5f978a8e2c35..5cb8206db23c 100644 --- a/tools/aapt2/io/Util.h +++ b/tools/aapt2/io/Util.h @@ -19,7 +19,7 @@ #include <string> -#include "google/protobuf/message_lite.h" +#include "google/protobuf/message.h" #include "google/protobuf/io/coded_stream.h" #include "format/Archive.h" @@ -39,7 +39,7 @@ bool CopyFileToArchive(IAaptContext* context, IFile* file, const std::string& ou bool CopyFileToArchivePreserveCompression(IAaptContext* context, IFile* file, const std::string& out_path, IArchiveWriter* writer); -bool CopyProtoToArchive(IAaptContext* context, ::google::protobuf::MessageLite* proto_msg, +bool CopyProtoToArchive(IAaptContext* context, ::google::protobuf::Message* proto_msg, const std::string& out_path, uint32_t compression_flags, IArchiveWriter* writer); @@ -127,13 +127,13 @@ class ProtoInputStreamReader { public: explicit ProtoInputStreamReader(io::InputStream* in) : in_(in) { } - /** Deserializes a MessageLite proto from the current position in the input stream.*/ - template <typename T> bool ReadMessage(T *message_lite) { + /** Deserializes a Message proto from the current position in the input stream.*/ + template <typename T> bool ReadMessage(T *message) { ZeroCopyInputAdaptor adapter(in_); google::protobuf::io::CodedInputStream coded_stream(&adapter); coded_stream.SetTotalBytesLimit(std::numeric_limits<int32_t>::max(), coded_stream.BytesUntilTotalBytesLimit()); - return message_lite->ParseFromCodedStream(&coded_stream); + return message->ParseFromCodedStream(&coded_stream); } private: diff --git a/tools/aapt2/java/JavaClassGenerator.cpp b/tools/aapt2/java/JavaClassGenerator.cpp index 31d205e1b9c9..bb541fe2490b 100644 --- a/tools/aapt2/java/JavaClassGenerator.cpp +++ b/tools/aapt2/java/JavaClassGenerator.cpp @@ -304,9 +304,11 @@ void JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const Res auto documentation_remove_iter = std::remove_if(documentation_attrs.begin(), documentation_attrs.end(), [&](StyleableAttr entry) -> bool { - StringPiece attr_comment_line = entry.symbol.value().attribute->GetComment(); - return SkipSymbol(entry.symbol) || attr_comment_line.contains("@removed") - || attr_comment_line.contains("@hide"); + if (SkipSymbol(entry.symbol)) { + return true; + } + const StringPiece attr_comment_line = entry.symbol.value().attribute->GetComment(); + return attr_comment_line.contains("@removed") || attr_comment_line.contains("@hide"); }); documentation_attrs.erase(documentation_remove_iter, documentation_attrs.end()); @@ -428,7 +430,7 @@ void JavaClassGenerator::ProcessStyleable(const ResourceNameRef& name, const Res out_rewrite_method->AppendStatement( StringPrintf(" if ((styleable.%s[i] & 0xff000000) == 0) {", array_field_name.data())); out_rewrite_method->AppendStatement( - StringPrintf(" styleable.%s[i] = (styleable.%s[i] & 0x00ffffff) | (p << 24);", + StringPrintf(" styleable.%s[i] = (styleable.%s[i] & 0x00ffffff) | packageIdBits;", array_field_name.data(), array_field_name.data())); out_rewrite_method->AppendStatement(" }"); out_rewrite_method->AppendStatement("}"); @@ -487,9 +489,9 @@ void JavaClassGenerator::ProcessResource(const ResourceNameRef& name, const Reso if (out_rewrite_method != nullptr) { const StringPiece& type_str = to_string(name.type); - out_rewrite_method->AppendStatement(StringPrintf("%s.%s = (%s.%s & 0x00ffffff) | (p << 24);", - type_str.data(), field_name.data(), - type_str.data(), field_name.data())); + out_rewrite_method->AppendStatement( + StringPrintf("%s.%s = (%s.%s & 0x00ffffff) | packageIdBits;", type_str.data(), + field_name.data(), type_str.data(), field_name.data())); } } @@ -599,6 +601,7 @@ bool JavaClassGenerator::Generate(const StringPiece& package_name_to_generate, rewrite_method->AppendStatement( StringPrintf("%s.R.onResourcesLoaded(p);", package_to_callback.data())); } + rewrite_method->AppendStatement("final int packageIdBits = p << 24;"); } for (const auto& package : table_->packages) { diff --git a/tools/aapt2/java/JavaClassGenerator_test.cpp b/tools/aapt2/java/JavaClassGenerator_test.cpp index 4f51fc48c80e..1e1fe4740c6b 100644 --- a/tools/aapt2/java/JavaClassGenerator_test.cpp +++ b/tools/aapt2/java/JavaClassGenerator_test.cpp @@ -522,9 +522,15 @@ TEST(JavaClassGeneratorTest, GenerateOnResourcesLoadedCallbackForSharedLibrary) ASSERT_TRUE(generator.Generate("android", &out)); out.Flush(); - EXPECT_THAT(output, HasSubstr("void onResourcesLoaded")); - EXPECT_THAT(output, HasSubstr("com.foo.R.onResourcesLoaded")); - EXPECT_THAT(output, HasSubstr("com.boo.R.onResourcesLoaded")); + EXPECT_THAT(output, HasSubstr( + R"( public static void onResourcesLoaded(int p) { + com.foo.R.onResourcesLoaded(p); + com.boo.R.onResourcesLoaded(p); + final int packageIdBits = p << 24; + attr.foo = (attr.foo & 0x00ffffff) | packageIdBits; + id.foo = (id.foo & 0x00ffffff) | packageIdBits; + style.foo = (style.foo & 0x00ffffff) | packageIdBits; + })")); } TEST(JavaClassGeneratorTest, OnlyGenerateRText) { diff --git a/tools/aapt2/java/ProguardRules.cpp b/tools/aapt2/java/ProguardRules.cpp index ff7f5c12404c..d9a4caa34e0d 100644 --- a/tools/aapt2/java/ProguardRules.cpp +++ b/tools/aapt2/java/ProguardRules.cpp @@ -155,13 +155,19 @@ class MenuVisitor : public BaseVisitor { void Visit(xml::Element* node) override { if (node->namespace_uri.empty() && node->name == "item") { for (const auto& attr : node->attributes) { - if (attr.namespace_uri == xml::kSchemaAndroid) { - if ((attr.name == "actionViewClass" || attr.name == "actionProviderClass") && - util::IsJavaClassName(attr.value)) { - AddClass(node->line_number, attr.value, "android.content.Context"); - } else if (attr.name == "onClick") { - AddMethod(node->line_number, attr.value, "android.view.MenuItem"); - } + // AppCompat-v7 defines its own versions of Android attributes if + // they're defined after SDK 7 (the below are from 11 and 14, + // respectively), so don't bother checking the XML namespace. + // + // Given the names of the containing XML files and the attribute + // names, it's unlikely that keeping these classes would be wrong. + if ((attr.name == "actionViewClass" || attr.name == "actionProviderClass") && + util::IsJavaClassName(attr.value)) { + AddClass(node->line_number, attr.value, "android.content.Context"); + } + + if (attr.namespace_uri == xml::kSchemaAndroid && attr.name == "onClick") { + AddMethod(node->line_number, attr.value, "android.view.MenuItem"); } } } @@ -388,11 +394,15 @@ bool CollectProguardRules(IAaptContext* context_, xml::XmlResource* res, KeepSet return true; } -void WriteKeepSet(const KeepSet& keep_set, OutputStream* out, bool minimal_keep) { +void WriteKeepSet(const KeepSet& keep_set, OutputStream* out, bool minimal_keep, + bool no_location_reference) { + Printer printer(out); for (const auto& entry : keep_set.manifest_class_set_) { - for (const UsageLocation& location : entry.second) { - printer.Print("# Referenced at ").Println(location.source.to_string()); + if (!no_location_reference) { + for (const UsageLocation& location : entry.second) { + printer.Print("# Referenced at ").Println(location.source.to_string()); + } } printer.Print("-keep class ").Print(entry.first).Println(" { <init>(); }"); } @@ -409,7 +419,9 @@ void WriteKeepSet(const KeepSet& keep_set, OutputStream* out, bool minimal_keep) if (can_be_conditional) { for (const UsageLocation& location : locations) { - printer.Print("# Referenced at ").Println(location.source.to_string()); + if (!no_location_reference) { + printer.Print("# Referenced at ").Println(location.source.to_string()); + } printer.Print("-if class **.R$layout { int ") .Print(JavaClassGenerator::TransformToFieldName(location.name.entry)) .Println("; }"); @@ -419,8 +431,10 @@ void WriteKeepSet(const KeepSet& keep_set, OutputStream* out, bool minimal_keep) printer.Println("); }"); } } else { - for (const UsageLocation& location : entry.second) { - printer.Print("# Referenced at ").Println(location.source.to_string()); + if (!no_location_reference) { + for (const UsageLocation& location : entry.second) { + printer.Print("# Referenced at ").Println(location.source.to_string()); + } } printer.Print("-keep class ").Print(entry.first.name).Print(" { <init>("); @@ -431,8 +445,10 @@ void WriteKeepSet(const KeepSet& keep_set, OutputStream* out, bool minimal_keep) } for (const auto& entry : keep_set.method_set_) { - for (const UsageLocation& location : entry.second) { - printer.Print("# Referenced at ").Println(location.source.to_string()); + if (!no_location_reference) { + for (const UsageLocation& location : entry.second) { + printer.Print("# Referenced at ").Println(location.source.to_string()); + } } printer.Print("-keepclassmembers class * { *** ").Print(entry.first.name) .Print("(").Print(entry.first.signature).Println("); }"); diff --git a/tools/aapt2/java/ProguardRules.h b/tools/aapt2/java/ProguardRules.h index b15df59f56a6..a01b64d024d2 100644 --- a/tools/aapt2/java/ProguardRules.h +++ b/tools/aapt2/java/ProguardRules.h @@ -70,7 +70,8 @@ class KeepSet { } private: - friend void WriteKeepSet(const KeepSet& keep_set, io::OutputStream* out, bool minimal_keep); + friend void WriteKeepSet(const KeepSet& keep_set, io::OutputStream* out, bool minimal_keep, + bool no_location_reference); friend bool CollectLocations(const UsageLocation& location, const KeepSet& keep_set, std::set<UsageLocation>* locations); @@ -89,7 +90,8 @@ bool CollectProguardRules(IAaptContext* context, xml::XmlResource* res, KeepSet* bool CollectResourceReferences(IAaptContext* context, ResourceTable* table, KeepSet* keep_set); -void WriteKeepSet(const KeepSet& keep_set, io::OutputStream* out, bool minimal_keep); +void WriteKeepSet(const KeepSet& keep_set, io::OutputStream* out, bool minimal_keep, + bool no_location_reference); bool CollectLocations(const UsageLocation& location, const KeepSet& keep_set, std::set<UsageLocation>* locations); diff --git a/tools/aapt2/java/ProguardRules_test.cpp b/tools/aapt2/java/ProguardRules_test.cpp index 7c3cda089b40..c7ae0b6a75cc 100644 --- a/tools/aapt2/java/ProguardRules_test.cpp +++ b/tools/aapt2/java/ProguardRules_test.cpp @@ -30,7 +30,7 @@ namespace aapt { std::string GetKeepSetString(const proguard::KeepSet& set, bool minimal_rules) { std::string out; StringOutputStream sout(&out); - proguard::WriteKeepSet(set, &sout, minimal_rules); + proguard::WriteKeepSet(set, &sout, minimal_rules, false); sout.Flush(); return out; } @@ -381,6 +381,25 @@ TEST(ProguardRulesTest, MenuRulesAreEmitted) { EXPECT_THAT(actual, Not(HasSubstr("com.foo.Bat"))); } +TEST(ProguardRulesTest, MenuRulesAreEmittedForActionClasses) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + std::unique_ptr<xml::XmlResource> menu = test::BuildXmlDom(R"( + <menu xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto"> + <item android:id="@+id/my_item" + app:actionViewClass="com.foo.Bar" + app:actionProviderClass="com.foo.Baz" /> + </menu>)"); + menu->file.name = test::ParseNameOrDie("menu/foo"); + + proguard::KeepSet set; + ASSERT_TRUE(proguard::CollectProguardRules(context.get(), menu.get(), &set)); + + std::string actual = GetKeepSetString(set, /** minimal_rules */ false); + EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Bar")); + EXPECT_THAT(actual, HasSubstr("-keep class com.foo.Baz")); +} + TEST(ProguardRulesTest, TransitionPathMotionRulesAreEmitted) { std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); std::unique_ptr<xml::XmlResource> transition = test::BuildXmlDom(R"( diff --git a/tools/aapt2/link/ManifestFixer.cpp b/tools/aapt2/link/ManifestFixer.cpp index 49909f6e2b8e..5aa32f868104 100644 --- a/tools/aapt2/link/ManifestFixer.cpp +++ b/tools/aapt2/link/ManifestFixer.cpp @@ -214,6 +214,33 @@ static bool VerifyUsesFeature(xml::Element* el, SourcePathDiagnostics* diag) { return true; } +// Ensure that 'ns_decls' contains a declaration for 'uri', using 'prefix' as +// the xmlns prefix if possible. +static void EnsureNamespaceIsDeclared(const std::string& prefix, const std::string& uri, + std::vector<xml::NamespaceDecl>* ns_decls) { + if (std::find_if(ns_decls->begin(), ns_decls->end(), [&](const xml::NamespaceDecl& ns_decl) { + return ns_decl.uri == uri; + }) != ns_decls->end()) { + return; + } + + std::set<std::string> used_prefixes; + for (const auto& ns_decl : *ns_decls) { + used_prefixes.insert(ns_decl.prefix); + } + + // Make multiple attempts in the unlikely event that 'prefix' is already taken. + std::string disambiguator; + for (int i = 0; i < used_prefixes.size() + 1; i++) { + std::string attempted_prefix = prefix + disambiguator; + if (used_prefixes.find(attempted_prefix) == used_prefixes.end()) { + ns_decls->push_back(xml::NamespaceDecl{attempted_prefix, uri}); + return; + } + disambiguator = std::to_string(i); + } +} + bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, IDiagnostics* diag) { // First verify some options. @@ -262,6 +289,8 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, manifest_action.Action(VerifyManifest); manifest_action.Action(FixCoreAppAttribute); manifest_action.Action([&](xml::Element* el) -> bool { + EnsureNamespaceIsDeclared("android", xml::kSchemaAndroid, &el->namespace_decls); + if (options_.version_name_default) { if (options_.replace_version) { el->RemoveAttribute(xml::kSchemaAndroid, "versionName"); @@ -320,6 +349,7 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, } return true; }); + manifest_action["uses-sdk"]["extension-sdk"]; // Instrumentation actions. manifest_action["instrumentation"].Action(RequiredNameIsJavaClassName); @@ -336,6 +366,8 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, }); manifest_action["instrumentation"]["meta-data"] = meta_data_action; + manifest_action["feature"]; + manifest_action["feature"]["inherit-from"]; manifest_action["original-package"]; manifest_action["overlay"]; manifest_action["protected-broadcast"]; @@ -357,6 +389,10 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, manifest_action["package-verifier"]; manifest_action["meta-data"] = meta_data_action; manifest_action["uses-split"].Action(RequiredNameIsJavaPackage); + manifest_action["queries"]["package"].Action(RequiredNameIsJavaPackage); + manifest_action["queries"]["intent"] = intent_filter_action; + manifest_action["queries"]["provider"].Action(RequiredAndroidAttribute("authorities")); + // TODO: more complicated component name tag manifest_action["key-sets"]["key-set"]["public-key"]; manifest_action["key-sets"]["upgrade-key-set"]; @@ -393,6 +429,12 @@ bool ManifestFixer::BuildRules(xml::XmlActionExecutor* executor, application_action["meta-data"] = meta_data_action; + application_action["processes"]; + application_action["processes"]["deny-permission"]; + application_action["processes"]["allow-permission"]; + application_action["processes"]["process"]["deny-permission"]; + application_action["processes"]["process"]["allow-permission"]; + application_action["activity"] = component_action; application_action["activity"]["layout"]; diff --git a/tools/aapt2/link/ManifestFixer_test.cpp b/tools/aapt2/link/ManifestFixer_test.cpp index 3f1ee36dea4a..3af06f53d4f3 100644 --- a/tools/aapt2/link/ManifestFixer_test.cpp +++ b/tools/aapt2/link/ManifestFixer_test.cpp @@ -727,8 +727,7 @@ TEST_F(ManifestFixerTest, SupportKeySets) { } TEST_F(ManifestFixerTest, InsertCompileSdkVersions) { - std::string input = R"( - <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="android" />)"; + std::string input = R"(<manifest package="com.pkg" />)"; ManifestFixerOptions options; options.compile_sdk_version = {"28"}; options.compile_sdk_version_codename = {"P"}; @@ -736,6 +735,12 @@ TEST_F(ManifestFixerTest, InsertCompileSdkVersions) { std::unique_ptr<xml::XmlResource> manifest = VerifyWithOptions(input, options); ASSERT_THAT(manifest, NotNull()); + // There should be a declaration of kSchemaAndroid, even when the input + // didn't have one. + EXPECT_EQ(manifest->root->namespace_decls.size(), 1); + EXPECT_EQ(manifest->root->namespace_decls[0].prefix, "android"); + EXPECT_EQ(manifest->root->namespace_decls[0].uri, xml::kSchemaAndroid); + xml::Attribute* attr = manifest->root->FindAttribute(xml::kSchemaAndroid, "compileSdkVersion"); ASSERT_THAT(attr, NotNull()); EXPECT_THAT(attr->value, StrEq("28")); @@ -782,6 +787,27 @@ TEST_F(ManifestFixerTest, OverrideCompileSdkVersions) { EXPECT_THAT(attr->value, StrEq("P")); } +TEST_F(ManifestFixerTest, AndroidPrefixAlreadyUsed) { + std::string input = + R"(<manifest package="com.pkg" + xmlns:android="http://schemas.android.com/apk/prv/res/android" + android:private_attr="foo" />)"; + ManifestFixerOptions options; + options.compile_sdk_version = {"28"}; + options.compile_sdk_version_codename = {"P"}; + + std::unique_ptr<xml::XmlResource> manifest = VerifyWithOptions(input, options); + ASSERT_THAT(manifest, NotNull()); + + // Make sure that we don't redefine "android". + EXPECT_EQ(manifest->root->namespace_decls.size(), 2); + EXPECT_EQ(manifest->root->namespace_decls[0].prefix, "android"); + EXPECT_EQ(manifest->root->namespace_decls[0].uri, + "http://schemas.android.com/apk/prv/res/android"); + EXPECT_EQ(manifest->root->namespace_decls[1].prefix, "android0"); + EXPECT_EQ(manifest->root->namespace_decls[1].uri, xml::kSchemaAndroid); +} + TEST_F(ManifestFixerTest, UnexpectedElementsInManifest) { std::string input = R"( <manifest xmlns:android="http://schemas.android.com/apk/res/android" diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp index 28f09aa48365..8e49fabe6a5c 100644 --- a/tools/aapt2/link/ReferenceLinker.cpp +++ b/tools/aapt2/link/ReferenceLinker.cpp @@ -17,6 +17,7 @@ #include "link/ReferenceLinker.h" #include "android-base/logging.h" +#include "android-base/stringprintf.h" #include "androidfw/ResourceTypes.h" #include "Diagnostics.h" @@ -33,6 +34,7 @@ using ::aapt::ResourceUtils::StringBuilder; using ::android::StringPiece; +using ::android::base::StringPrintf; namespace aapt { @@ -81,7 +83,7 @@ class ReferenceLinkerVisitor : public DescendingValueVisitor { // Find the attribute in the symbol table and check if it is visible from this callsite. const SymbolTable::Symbol* symbol = ReferenceLinker::ResolveAttributeCheckVisibility( - transformed_reference, callsite_, symbols_, &err_str); + transformed_reference, callsite_, context_, symbols_, &err_str); if (symbol) { // Assign our style key the correct ID. The ID may not exist. entry.key.id = symbol->id; @@ -203,12 +205,35 @@ bool IsSymbolVisible(const SymbolTable::Symbol& symbol, const Reference& ref, const SymbolTable::Symbol* ReferenceLinker::ResolveSymbol(const Reference& reference, const CallSite& callsite, + IAaptContext* context, SymbolTable* symbols) { if (reference.name) { const ResourceName& name = reference.name.value(); if (name.package.empty()) { // Use the callsite's package name if no package name was defined. - return symbols->FindByName(ResourceName(callsite.package, name.type, name.entry)); + const SymbolTable::Symbol* symbol = symbols->FindByName( + ResourceName(callsite.package, name.type, name.entry)); + if (symbol) { + return symbol; + } + + // If the callsite package is the same as the current compilation package, + // check the feature split dependencies as well. Feature split resources + // can be referenced without a namespace, just like the base package. + // TODO: modify the package name of included splits instead of having the + // symbol table look up the resource in in every package. b/136105066 + if (callsite.package == context->GetCompilationPackage()) { + const auto& split_name_dependencies = context->GetSplitNameDependencies(); + for (const std::string& split_name : split_name_dependencies) { + std::string split_package = + StringPrintf("%s.%s", callsite.package.c_str(), split_name.c_str()); + symbol = symbols->FindByName(ResourceName(split_package, name.type, name.entry)); + if (symbol) { + return symbol; + } + } + } + return nullptr; } return symbols->FindByName(name); } else if (reference.id) { @@ -220,9 +245,10 @@ const SymbolTable::Symbol* ReferenceLinker::ResolveSymbol(const Reference& refer const SymbolTable::Symbol* ReferenceLinker::ResolveSymbolCheckVisibility(const Reference& reference, const CallSite& callsite, + IAaptContext* context, SymbolTable* symbols, std::string* out_error) { - const SymbolTable::Symbol* symbol = ResolveSymbol(reference, callsite, symbols); + const SymbolTable::Symbol* symbol = ResolveSymbol(reference, callsite, context, symbols); if (!symbol) { if (out_error) *out_error = "not found"; return nullptr; @@ -236,10 +262,10 @@ const SymbolTable::Symbol* ReferenceLinker::ResolveSymbolCheckVisibility(const R } const SymbolTable::Symbol* ReferenceLinker::ResolveAttributeCheckVisibility( - const Reference& reference, const CallSite& callsite, SymbolTable* symbols, - std::string* out_error) { + const Reference& reference, const CallSite& callsite, IAaptContext* context, + SymbolTable* symbols, std::string* out_error) { const SymbolTable::Symbol* symbol = - ResolveSymbolCheckVisibility(reference, callsite, symbols, out_error); + ResolveSymbolCheckVisibility(reference, callsite, context, symbols, out_error); if (!symbol) { return nullptr; } @@ -253,10 +279,11 @@ const SymbolTable::Symbol* ReferenceLinker::ResolveAttributeCheckVisibility( Maybe<xml::AaptAttribute> ReferenceLinker::CompileXmlAttribute(const Reference& reference, const CallSite& callsite, + IAaptContext* context, SymbolTable* symbols, std::string* out_error) { const SymbolTable::Symbol* symbol = - ResolveAttributeCheckVisibility(reference, callsite, symbols, out_error); + ResolveAttributeCheckVisibility(reference, callsite, context, symbols, out_error); if (!symbol) { return {}; } @@ -335,7 +362,7 @@ bool ReferenceLinker::LinkReference(const CallSite& callsite, Reference* referen std::string err_str; const SymbolTable::Symbol* s = - ResolveSymbolCheckVisibility(transformed_reference, callsite, symbols, &err_str); + ResolveSymbolCheckVisibility(transformed_reference, callsite, context, symbols, &err_str); if (s) { // The ID may not exist. This is fine because of the possibility of building // against libraries without assigned IDs. diff --git a/tools/aapt2/link/ReferenceLinker.h b/tools/aapt2/link/ReferenceLinker.h index b0b49457e5dd..1256709edbf4 100644 --- a/tools/aapt2/link/ReferenceLinker.h +++ b/tools/aapt2/link/ReferenceLinker.h @@ -39,13 +39,16 @@ class ReferenceLinker : public IResourceTableConsumer { // package if the reference has no package name defined (implicit). // Returns nullptr if the symbol was not found. static const SymbolTable::Symbol* ResolveSymbol(const Reference& reference, - const CallSite& callsite, SymbolTable* symbols); + const CallSite& callsite, + IAaptContext* context, + SymbolTable* symbols); // Performs name mangling and looks up the resource in the symbol table. If the symbol is not // visible by the reference at the callsite, nullptr is returned. // `out_error` holds the error message. static const SymbolTable::Symbol* ResolveSymbolCheckVisibility(const Reference& reference, const CallSite& callsite, + IAaptContext* context, SymbolTable* symbols, std::string* out_error); @@ -53,6 +56,7 @@ class ReferenceLinker : public IResourceTableConsumer { // That is, the return value will have a non-null value for ISymbolTable::Symbol::attribute. static const SymbolTable::Symbol* ResolveAttributeCheckVisibility(const Reference& reference, const CallSite& callsite, + IAaptContext* context, SymbolTable* symbols, std::string* out_error); @@ -60,6 +64,7 @@ class ReferenceLinker : public IResourceTableConsumer { // If resolution fails, outError holds the error message. static Maybe<xml::AaptAttribute> CompileXmlAttribute(const Reference& reference, const CallSite& callsite, + IAaptContext* context, SymbolTable* symbols, std::string* out_error); diff --git a/tools/aapt2/link/ReferenceLinker_test.cpp b/tools/aapt2/link/ReferenceLinker_test.cpp index be38b967c986..a31ce9496d0c 100644 --- a/tools/aapt2/link/ReferenceLinker_test.cpp +++ b/tools/aapt2/link/ReferenceLinker_test.cpp @@ -266,8 +266,13 @@ TEST(ReferenceLinkerTest, AppsWithSamePackageButDifferentIdAreVisibleNonPublic) std::string error; const CallSite call_site{"com.app.test"}; + std::unique_ptr<IAaptContext> context = + test::ContextBuilder() + .SetCompilationPackage("com.app.test") + .SetPackageId(0x7f) + .Build(); const SymbolTable::Symbol* symbol = ReferenceLinker::ResolveSymbolCheckVisibility( - *test::BuildReference("com.app.test:string/foo"), call_site, &table, &error); + *test::BuildReference("com.app.test:string/foo"), call_site, context.get(), &table, &error); ASSERT_THAT(symbol, NotNull()); EXPECT_TRUE(error.empty()); } @@ -281,17 +286,23 @@ TEST(ReferenceLinkerTest, AppsWithDifferentPackageCanNotUseEachOthersAttribute) .AddPublicSymbol("com.app.test:attr/public_foo", ResourceId(0x7f010001), test::AttributeBuilder().Build()) .Build()); + std::unique_ptr<IAaptContext> context = + test::ContextBuilder() + .SetCompilationPackage("com.app.ext") + .SetPackageId(0x7f) + .Build(); std::string error; const CallSite call_site{"com.app.ext"}; EXPECT_FALSE(ReferenceLinker::CompileXmlAttribute( - *test::BuildReference("com.app.test:attr/foo"), call_site, &table, &error)); + *test::BuildReference("com.app.test:attr/foo"), call_site, context.get(), &table, &error)); EXPECT_FALSE(error.empty()); error = ""; ASSERT_TRUE(ReferenceLinker::CompileXmlAttribute( - *test::BuildReference("com.app.test:attr/public_foo"), call_site, &table, &error)); + *test::BuildReference("com.app.test:attr/public_foo"), call_site, context.get(), &table, + &error)); EXPECT_TRUE(error.empty()); } @@ -302,20 +313,62 @@ TEST(ReferenceLinkerTest, ReferenceWithNoPackageUsesCallSitePackage) { .AddSymbol("com.app.test:string/foo", ResourceId(0x7f010000)) .AddSymbol("com.app.lib:string/foo", ResourceId(0x7f010001)) .Build()); + std::unique_ptr<IAaptContext> context = + test::ContextBuilder() + .SetCompilationPackage("com.app.test") + .SetPackageId(0x7f) + .Build(); const SymbolTable::Symbol* s = ReferenceLinker::ResolveSymbol(*test::BuildReference("string/foo"), - CallSite{"com.app.test"}, &table); + CallSite{"com.app.test"}, + context.get(), &table); ASSERT_THAT(s, NotNull()); EXPECT_THAT(s->id, Eq(make_value<ResourceId>(0x7f010000))); s = ReferenceLinker::ResolveSymbol(*test::BuildReference("string/foo"), CallSite{"com.app.lib"}, - &table); + context.get(), &table); ASSERT_THAT(s, NotNull()); EXPECT_THAT(s->id, Eq(make_value<ResourceId>(0x7f010001))); EXPECT_THAT(ReferenceLinker::ResolveSymbol(*test::BuildReference("string/foo"), - CallSite{"com.app.bad"}, &table), + CallSite{"com.app.bad"}, context.get(), &table), IsNull()); } +TEST(ReferenceLinkerTest, ReferenceSymbolFromOtherSplit) { + NameMangler mangler(NameManglerPolicy{"com.app.test"}); + SymbolTable table(&mangler); + table.AppendSource(test::StaticSymbolSourceBuilder() + .AddSymbol("com.app.test.feature:string/bar", ResourceId(0x80010000)) + .Build()); + std::set<std::string> split_name_dependencies; + split_name_dependencies.insert("feature"); + std::unique_ptr<IAaptContext> context = + test::ContextBuilder() + .SetCompilationPackage("com.app.test") + .SetPackageId(0x81) + .SetSplitNameDependencies(split_name_dependencies) + .Build(); + + const SymbolTable::Symbol* s = ReferenceLinker::ResolveSymbol(*test::BuildReference("string/bar"), + CallSite{"com.app.test"}, + context.get(), &table); + ASSERT_THAT(s, NotNull()); + EXPECT_THAT(s->id, Eq(make_value<ResourceId>(0x80010000))); + + s = ReferenceLinker::ResolveSymbol(*test::BuildReference("string/foo"), CallSite{"com.app.lib"}, + context.get(), &table); + EXPECT_THAT(s, IsNull()); + + context = + test::ContextBuilder() + .SetCompilationPackage("com.app.test") + .SetPackageId(0x81) + .Build(); + s = ReferenceLinker::ResolveSymbol(*test::BuildReference("string/bar"),CallSite{"com.app.test"}, + context.get(), &table); + + EXPECT_THAT(s, IsNull()); +} + } // namespace aapt diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp index 3f65e868505d..c25e4503a208 100644 --- a/tools/aapt2/link/TableMerger.cpp +++ b/tools/aapt2/link/TableMerger.cpp @@ -172,28 +172,32 @@ static bool MergeEntry(IAaptContext* context, const Source& src, // // Styleables and Styles don't simply overlay each other, their definitions merge and accumulate. // If both values are Styleables/Styles, we just merge them into the existing value. -static ResourceTable::CollisionResult ResolveMergeCollision(Value* existing, Value* incoming, - StringPool* pool) { +static ResourceTable::CollisionResult ResolveMergeCollision( + bool override_styles_instead_of_overlaying, Value* existing, Value* incoming, + StringPool* pool) { if (Styleable* existing_styleable = ValueCast<Styleable>(existing)) { if (Styleable* incoming_styleable = ValueCast<Styleable>(incoming)) { // Styleables get merged. existing_styleable->MergeWith(incoming_styleable); return ResourceTable::CollisionResult::kKeepOriginal; } - } else if (Style* existing_style = ValueCast<Style>(existing)) { - if (Style* incoming_style = ValueCast<Style>(incoming)) { - // Styles get merged. - existing_style->MergeWith(incoming_style, pool); - return ResourceTable::CollisionResult::kKeepOriginal; + } else if (!override_styles_instead_of_overlaying) { + if (Style* existing_style = ValueCast<Style>(existing)) { + if (Style* incoming_style = ValueCast<Style>(incoming)) { + // Styles get merged. + existing_style->MergeWith(incoming_style, pool); + return ResourceTable::CollisionResult::kKeepOriginal; + } } } // Delegate to the default handler. - return ResourceTable::ResolveValueCollision(existing, incoming, true /* overlay */); + return ResourceTable::ResolveValueCollision(existing, incoming); } static ResourceTable::CollisionResult MergeConfigValue(IAaptContext* context, const ResourceNameRef& res_name, bool overlay, + bool override_styles_instead_of_overlaying, ResourceConfigValue* dst_config_value, ResourceConfigValue* src_config_value, StringPool* pool) { @@ -204,13 +208,18 @@ static ResourceTable::CollisionResult MergeConfigValue(IAaptContext* context, CollisionResult collision_result; if (overlay) { - collision_result = ResolveMergeCollision(dst_value, src_value, pool); + collision_result = + ResolveMergeCollision(override_styles_instead_of_overlaying, dst_value, src_value, pool); } else { - collision_result = ResourceTable::ResolveValueCollision(dst_value, src_value, - false /* overlay */); + collision_result = ResourceTable::ResolveValueCollision(dst_value, src_value); } if (collision_result == CollisionResult::kConflict) { + if (overlay) { + return CollisionResult::kTakeNew; + } + + // Error! context->GetDiagnostics()->Error(DiagMessage(src_value->GetSource()) << "resource '" << res_name << "' has a conflicting value for " << "configuration (" << src_config_value->config << ")"); @@ -268,9 +277,9 @@ bool TableMerger::DoMerge(const Source& src, ResourceTablePackage* src_package, ResourceConfigValue* dst_config_value = dst_entry->FindValue( src_config_value->config, src_config_value->product); if (dst_config_value) { - CollisionResult collision_result = - MergeConfigValue(context_, res_name, overlay, dst_config_value, - src_config_value.get(), &master_table_->string_pool); + CollisionResult collision_result = MergeConfigValue( + context_, res_name, overlay, options_.override_styles_instead_of_overlaying, + dst_config_value, src_config_value.get(), &master_table_->string_pool); if (collision_result == CollisionResult::kConflict) { error = true; continue; diff --git a/tools/aapt2/link/TableMerger.h b/tools/aapt2/link/TableMerger.h index 51305cfcdd25..a35a134a887d 100644 --- a/tools/aapt2/link/TableMerger.h +++ b/tools/aapt2/link/TableMerger.h @@ -37,6 +37,8 @@ struct TableMergerOptions { bool auto_add_overlay = false; // If true, resource overlays with conflicting visibility are not allowed. bool strict_visibility = false; + // If true, styles specified via "aapt2 link -R" completely replace any previously-seen resources. + bool override_styles_instead_of_overlaying = false; }; // TableMerger takes resource tables and merges all packages within the tables that have the same diff --git a/tools/aapt2/link/TableMerger_test.cpp b/tools/aapt2/link/TableMerger_test.cpp index 78d42a160e21..69cf5ee7002b 100644 --- a/tools/aapt2/link/TableMerger_test.cpp +++ b/tools/aapt2/link/TableMerger_test.cpp @@ -29,6 +29,8 @@ using ::testing::Pointee; using ::testing::StrEq; using ::testing::UnorderedElementsAreArray; +using PolicyFlags = android::ResTable_overlayable_policy_header::PolicyFlags; + namespace aapt { struct TableMergerTest : public ::testing::Test { @@ -352,62 +354,6 @@ TEST_F(TableMergerTest, MergeAddResourceFromOverlayWithAutoAddOverlay) { ASSERT_TRUE(merger.Merge({}, table_b.get(), false /*overlay*/)); } -TEST_F(TableMergerTest, OverrideAttributeSameFormatsWithOverlay) { - std::unique_ptr<ResourceTable> base = - test::ResourceTableBuilder() - .SetPackageId("", 0x7f) - .AddValue("attr/foo", test::AttributeBuilder() - .SetTypeMask(android::ResTable_map::TYPE_STRING) - .SetWeak(false) - .Build()) - .Build(); - - std::unique_ptr<ResourceTable> overlay = - test::ResourceTableBuilder() - .SetPackageId("", 0x7f) - .AddValue("attr/foo", test::AttributeBuilder() - .SetTypeMask(android::ResTable_map::TYPE_STRING) - .SetWeak(false) - .Build()) - .Build(); - - ResourceTable final_table; - TableMergerOptions options; - options.auto_add_overlay = false; - TableMerger merger(context_.get(), &final_table, options); - - ASSERT_TRUE(merger.Merge({}, base.get(), false /*overlay*/)); - ASSERT_TRUE(merger.Merge({}, overlay.get(), true /*overlay*/)); -} - -TEST_F(TableMergerTest, FailToOverrideConflictingAttributeFormatsWithOverlay) { - std::unique_ptr<ResourceTable> base = - test::ResourceTableBuilder() - .SetPackageId("", 0x7f) - .AddValue("attr/foo", test::AttributeBuilder() - .SetTypeMask(android::ResTable_map::TYPE_ANY) - .SetWeak(false) - .Build()) - .Build(); - - std::unique_ptr<ResourceTable> overlay = - test::ResourceTableBuilder() - .SetPackageId("", 0x7f) - .AddValue("attr/foo", test::AttributeBuilder() - .SetTypeMask(android::ResTable_map::TYPE_STRING) - .SetWeak(false) - .Build()) - .Build(); - - ResourceTable final_table; - TableMergerOptions options; - options.auto_add_overlay = false; - TableMerger merger(context_.get(), &final_table, options); - - ASSERT_TRUE(merger.Merge({}, base.get(), false /*overlay*/)); - ASSERT_FALSE(merger.Merge({}, overlay.get(), true /*overlay*/)); -} - TEST_F(TableMergerTest, FailToMergeNewResourceWithoutAutoAddOverlay) { std::unique_ptr<ResourceTable> table_a = test::ResourceTableBuilder().SetPackageId("", 0x7f).Build(); @@ -492,12 +438,59 @@ TEST_F(TableMergerTest, OverlaidStyleablesAndStylesShouldBeMerged) { Eq(make_value(Reference(test::ParseNameOrDie("com.app.a:style/OverlayParent"))))); } +TEST_F(TableMergerTest, OverrideStyleInsteadOfOverlaying) { + std::unique_ptr<ResourceTable> table_a = + test::ResourceTableBuilder() + .SetPackageId("com.app.a", 0x7f) + .AddValue( + "com.app.a:styleable/MyWidget", + test::StyleableBuilder().AddItem("com.app.a:attr/foo", ResourceId(0x1234)).Build()) + .AddValue("com.app.a:style/Theme", + test::StyleBuilder() + .AddItem("com.app.a:attr/foo", ResourceUtils::MakeBool(false)) + .Build()) + .Build(); + std::unique_ptr<ResourceTable> table_b = + test::ResourceTableBuilder() + .SetPackageId("com.app.a", 0x7f) + .AddValue( + "com.app.a:styleable/MyWidget", + test::StyleableBuilder().AddItem("com.app.a:attr/bar", ResourceId(0x5678)).Build()) + .AddValue( + "com.app.a:style/Theme", + test::StyleBuilder().AddItem("com.app.a:attr/bat", util::make_unique<Id>()).Build()) + .Build(); + + ResourceTable final_table; + TableMergerOptions options; + options.auto_add_overlay = true; + options.override_styles_instead_of_overlaying = true; + TableMerger merger(context_.get(), &final_table, options); + ASSERT_TRUE(merger.Merge({}, table_a.get(), false /*overlay*/)); + ASSERT_TRUE(merger.Merge({}, table_b.get(), true /*overlay*/)); + + // Styleables are always overlaid + std::unique_ptr<Styleable> expected_styleable = test::StyleableBuilder() + // The merged Styleable has its entries ordered by name. + .AddItem("com.app.a:attr/bar", ResourceId(0x5678)) + .AddItem("com.app.a:attr/foo", ResourceId(0x1234)) + .Build(); + const Styleable* actual_styleable = + test::GetValue<Styleable>(&final_table, "com.app.a:styleable/MyWidget"); + ASSERT_NE(actual_styleable, nullptr); + EXPECT_TRUE(actual_styleable->Equals(expected_styleable.get())); + // Style should be overridden + const Style* actual_style = test::GetValue<Style>(&final_table, "com.app.a:style/Theme"); + ASSERT_NE(actual_style, nullptr); + EXPECT_TRUE(actual_style->Equals(test::GetValue<Style>(table_b.get(), "com.app.a:style/Theme"))); +} + TEST_F(TableMergerTest, SetOverlayable) { auto overlayable = std::make_shared<Overlayable>("CustomizableResources", "overlay://customization"); OverlayableItem overlayable_item(overlayable); - overlayable_item.policies |= OverlayableItem::Policy::kProduct; - overlayable_item.policies |= OverlayableItem::Policy::kVendor; + overlayable_item.policies |= PolicyFlags::PRODUCT_PARTITION; + overlayable_item.policies |= PolicyFlags::VENDOR_PARTITION; std::unique_ptr<ResourceTable> table_a = test::ResourceTableBuilder() @@ -525,8 +518,8 @@ TEST_F(TableMergerTest, SetOverlayable) { OverlayableItem& result_overlayable_item = search_result.value().entry->overlayable_item.value(); EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("CustomizableResources")); EXPECT_THAT(result_overlayable_item.overlayable->actor, Eq("overlay://customization")); - EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kProduct - | OverlayableItem::Policy::kVendor)); + EXPECT_THAT(result_overlayable_item.policies, Eq(PolicyFlags::PRODUCT_PARTITION + | PolicyFlags::VENDOR_PARTITION)); } TEST_F(TableMergerTest, SetOverlayableLater) { @@ -539,8 +532,8 @@ TEST_F(TableMergerTest, SetOverlayableLater) { .Build(); OverlayableItem overlayable_item(overlayable); - overlayable_item.policies |= OverlayableItem::Policy::kPublic; - overlayable_item.policies |= OverlayableItem::Policy::kSystem; + overlayable_item.policies |= PolicyFlags::PUBLIC; + overlayable_item.policies |= PolicyFlags::SYSTEM_PARTITION; std::unique_ptr<ResourceTable> table_b = test::ResourceTableBuilder() .SetPackageId("com.app.a", 0x7f) @@ -561,15 +554,15 @@ TEST_F(TableMergerTest, SetOverlayableLater) { OverlayableItem& result_overlayable_item = search_result.value().entry->overlayable_item.value(); EXPECT_THAT(result_overlayable_item.overlayable->name, Eq("CustomizableResources")); EXPECT_THAT(result_overlayable_item.overlayable->actor, Eq("overlay://customization")); - EXPECT_THAT(result_overlayable_item.policies, Eq(OverlayableItem::Policy::kPublic - | OverlayableItem::Policy::kSystem)); + EXPECT_THAT(result_overlayable_item.policies, Eq(PolicyFlags::PUBLIC + | PolicyFlags::SYSTEM_PARTITION)); } TEST_F(TableMergerTest, SameResourceDifferentNameFail) { auto overlayable_first = std::make_shared<Overlayable>("CustomizableResources", "overlay://customization"); OverlayableItem overlayable_item_first(overlayable_first); - overlayable_item_first.policies |= OverlayableItem::Policy::kProduct; + overlayable_item_first.policies |= PolicyFlags::PRODUCT_PARTITION; std::unique_ptr<ResourceTable> table_a = test::ResourceTableBuilder() .SetPackageId("com.app.a", 0x7f) @@ -579,7 +572,7 @@ TEST_F(TableMergerTest, SameResourceDifferentNameFail) { auto overlayable_second = std::make_shared<Overlayable>("ThemeResources", "overlay://customization"); OverlayableItem overlayable_item_second(overlayable_second); - overlayable_item_second.policies |= OverlayableItem::Policy::kProduct; + overlayable_item_second.policies |= PolicyFlags::PRODUCT_PARTITION; std::unique_ptr<ResourceTable> table_b = test::ResourceTableBuilder() .SetPackageId("com.app.a", 0x7f) @@ -598,7 +591,7 @@ TEST_F(TableMergerTest, SameResourceDifferentActorFail) { auto overlayable_first = std::make_shared<Overlayable>("CustomizableResources", "overlay://customization"); OverlayableItem overlayable_item_first(overlayable_first); - overlayable_item_first.policies |= OverlayableItem::Policy::kProduct; + overlayable_item_first.policies |= PolicyFlags::PRODUCT_PARTITION; std::unique_ptr<ResourceTable> table_a = test::ResourceTableBuilder() .SetPackageId("com.app.a", 0x7f) @@ -608,7 +601,7 @@ TEST_F(TableMergerTest, SameResourceDifferentActorFail) { auto overlayable_second = std::make_shared<Overlayable>("CustomizableResources", "overlay://theme"); OverlayableItem overlayable_item_second(overlayable_second); - overlayable_item_second.policies |= OverlayableItem::Policy::kProduct; + overlayable_item_second.policies |= PolicyFlags::PRODUCT_PARTITION; std::unique_ptr<ResourceTable> table_b = test::ResourceTableBuilder() .SetPackageId("com.app.a", 0x7f) @@ -627,7 +620,7 @@ TEST_F(TableMergerTest, SameResourceDifferentPoliciesFail) { auto overlayable_first = std::make_shared<Overlayable>("CustomizableResources", "overlay://customization"); OverlayableItem overlayable_item_first(overlayable_first); - overlayable_item_first.policies |= OverlayableItem::Policy::kProduct; + overlayable_item_first.policies |= PolicyFlags::PRODUCT_PARTITION; std::unique_ptr<ResourceTable> table_a = test::ResourceTableBuilder() .SetPackageId("com.app.a", 0x7f) @@ -637,7 +630,7 @@ TEST_F(TableMergerTest, SameResourceDifferentPoliciesFail) { auto overlayable_second = std::make_shared<Overlayable>("CustomizableResources", "overlay://customization"); OverlayableItem overlayable_item_second(overlayable_second); - overlayable_item_second.policies |= OverlayableItem::Policy::kSignature; + overlayable_item_second.policies |= PolicyFlags::SIGNATURE; std::unique_ptr<ResourceTable> table_b = test::ResourceTableBuilder() .SetPackageId("com.app.a", 0x7f) @@ -657,7 +650,7 @@ TEST_F(TableMergerTest, SameResourceSameOverlayable) { "overlay://customization"); OverlayableItem overlayable_item_first(overlayable); - overlayable_item_first.policies |= OverlayableItem::Policy::kProduct; + overlayable_item_first.policies |= PolicyFlags::PRODUCT_PARTITION; std::unique_ptr<ResourceTable> table_a = test::ResourceTableBuilder() .SetPackageId("com.app.a", 0x7f) @@ -665,7 +658,7 @@ TEST_F(TableMergerTest, SameResourceSameOverlayable) { .Build(); OverlayableItem overlayable_item_second(overlayable); - overlayable_item_second.policies |= OverlayableItem::Policy::kProduct; + overlayable_item_second.policies |= PolicyFlags::PRODUCT_PARTITION; std::unique_ptr<ResourceTable> table_b = test::ResourceTableBuilder() .SetPackageId("com.app.a", 0x7f) diff --git a/tools/aapt2/link/XmlReferenceLinker.cpp b/tools/aapt2/link/XmlReferenceLinker.cpp index d68f7dd44c9f..c3c16b92f712 100644 --- a/tools/aapt2/link/XmlReferenceLinker.cpp +++ b/tools/aapt2/link/XmlReferenceLinker.cpp @@ -83,6 +83,15 @@ class XmlVisitor : public xml::PackageAwareVisitor { Attribute default_attribute(android::ResTable_map::TYPE_ANY); default_attribute.SetWeak(true); + // The default orientation of gradients in android Q is different than previous android + // versions. Set the android:angle attribute to "0" to ensure that the default gradient + // orientation will remain left-to-right in android Q. + if (el->name == "gradient" && context_->GetMinSdkVersion() <= SDK_Q) { + if (!el->FindAttribute(xml::kSchemaAndroid, "angle")) { + el->attributes.push_back(xml::Attribute{xml::kSchemaAndroid, "angle", "0"}); + } + } + const Source source = source_.WithLine(el->line_number); for (xml::Attribute& attr : el->attributes) { // If the attribute has no namespace, interpret values as if @@ -99,7 +108,7 @@ class XmlVisitor : public xml::PackageAwareVisitor { std::string err_str; attr.compiled_attribute = - ReferenceLinker::CompileXmlAttribute(attr_ref, callsite_, symbols_, &err_str); + ReferenceLinker::CompileXmlAttribute(attr_ref, callsite_, context_, symbols_, &err_str); if (!attr.compiled_attribute) { DiagMessage error_msg(source); diff --git a/tools/aapt2/link/XmlReferenceLinker_test.cpp b/tools/aapt2/link/XmlReferenceLinker_test.cpp index ef99355e5b5f..0ce2e50d6e44 100644 --- a/tools/aapt2/link/XmlReferenceLinker_test.cpp +++ b/tools/aapt2/link/XmlReferenceLinker_test.cpp @@ -47,6 +47,8 @@ class XmlReferenceLinkerTest : public ::testing::Test { test::AttributeBuilder() .SetTypeMask(android::ResTable_map::TYPE_STRING) .Build()) + .AddPublicSymbol("android:attr/angle", ResourceId(0x01010004), + test::AttributeBuilder().Build()) // Add one real symbol that was introduces in v21 .AddPublicSymbol("android:attr/colorAccent", ResourceId(0x01010435), @@ -75,7 +77,7 @@ class XmlReferenceLinkerTest : public ::testing::Test { } protected: - std::unique_ptr<IAaptContext> context_; + std::unique_ptr<test::Context> context_; }; TEST_F(XmlReferenceLinkerTest, LinkBasicAttributes) { @@ -254,4 +256,63 @@ TEST_F(XmlReferenceLinkerTest, LinkViewWithLocalPackageAndAliasOfTheSameName) { EXPECT_EQ(make_value(ResourceId(0x7f030000)), ref->id); } + +TEST_F(XmlReferenceLinkerTest, AddAngleOnGradientForAndroidQ) { + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"( + <gradient />)"); + + XmlReferenceLinker linker; + ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); + + xml::Element* gradient_el = doc->root.get(); + ASSERT_THAT(gradient_el, NotNull()); + + xml::Attribute* xml_attr = gradient_el->FindAttribute(xml::kSchemaAndroid, "angle"); + ASSERT_THAT(xml_attr, NotNull()); + ASSERT_TRUE(xml_attr->compiled_attribute); + EXPECT_EQ(make_value(ResourceId(0x01010004)), xml_attr->compiled_attribute.value().id); + + BinaryPrimitive* value = ValueCast<BinaryPrimitive>(xml_attr->compiled_value.get()); + ASSERT_THAT(value, NotNull()); + EXPECT_EQ(value->value.dataType, android::Res_value::TYPE_INT_DEC); + EXPECT_EQ(value->value.data, 0U); +} + +TEST_F(XmlReferenceLinkerTest, DoNotOverwriteAngleOnGradientForAndroidQ) { + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"( + <gradient xmlns:android="http://schemas.android.com/apk/res/android" + android:angle="90"/>)"); + + XmlReferenceLinker linker; + ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); + + xml::Element* gradient_el = doc->root.get(); + ASSERT_THAT(gradient_el, NotNull()); + + xml::Attribute* xml_attr = gradient_el->FindAttribute(xml::kSchemaAndroid, "angle"); + ASSERT_THAT(xml_attr, NotNull()); + ASSERT_TRUE(xml_attr->compiled_attribute); + EXPECT_EQ(make_value(ResourceId(0x01010004)), xml_attr->compiled_attribute.value().id); + + BinaryPrimitive* value = ValueCast<BinaryPrimitive>(xml_attr->compiled_value.get()); + ASSERT_THAT(value, NotNull()); + EXPECT_EQ(value->value.dataType, android::Res_value::TYPE_INT_DEC); + EXPECT_EQ(value->value.data, 90U); +} + +TEST_F(XmlReferenceLinkerTest, DoNotOverwriteAngleOnGradientForPostAndroidQ) { + std::unique_ptr<xml::XmlResource> doc = test::BuildXmlDomForPackageName(context_.get(), R"( + <gradient xmlns:android="http://schemas.android.com/apk/res/android" />)"); + context_->SetMinSdkVersion(30); + + XmlReferenceLinker linker; + ASSERT_TRUE(linker.Consume(context_.get(), doc.get())); + + xml::Element* gradient_el = doc->root.get(); + ASSERT_THAT(gradient_el, NotNull()); + + xml::Attribute* xml_attr = gradient_el->FindAttribute(xml::kSchemaAndroid, "angle"); + ASSERT_THAT(xml_attr, IsNull()); +} + } // namespace aapt diff --git a/tools/aapt2/optimize/MultiApkGenerator.cpp b/tools/aapt2/optimize/MultiApkGenerator.cpp index 8c9c43409569..c686a10a3fa9 100644 --- a/tools/aapt2/optimize/MultiApkGenerator.cpp +++ b/tools/aapt2/optimize/MultiApkGenerator.cpp @@ -101,6 +101,10 @@ class ContextWrapper : public IAaptContext { util::make_unique<SourcePathDiagnostics>(Source{source}, context_->GetDiagnostics()); } + const std::set<std::string>& GetSplitNameDependencies() override { + return context_->GetSplitNameDependencies(); + } + private: IAaptContext* context_; std::unique_ptr<SourcePathDiagnostics> source_diag_; diff --git a/tools/aapt2/optimize/ResourceDeduper.cpp b/tools/aapt2/optimize/ResourceDeduper.cpp index 78ebcb97b811..0278b439cfae 100644 --- a/tools/aapt2/optimize/ResourceDeduper.cpp +++ b/tools/aapt2/optimize/ResourceDeduper.cpp @@ -63,13 +63,14 @@ class DominatedKeyValueRemover : public DominatorTree::BottomUpVisitor { // Compare compatible configs for this entry and ensure the values are // equivalent. const ConfigDescription& node_configuration = node_value->config; - for (const auto& sibling : entry_->values) { - if (!sibling->value) { + for (const auto& sibling : parent->children()) { + ResourceConfigValue* sibling_value = sibling->value(); + if (!sibling_value->value) { // Sibling was already removed. continue; } - if (node_configuration.IsCompatibleWith(sibling->config) && - !node_value->value->Equals(sibling->value.get())) { + if (node_configuration.IsCompatibleWith(sibling_value->config) && + !node_value->value->Equals(sibling_value->value.get())) { // The configurations are compatible, but the value is // different, so we can't remove this value. return; diff --git a/tools/aapt2/optimize/ResourceDeduper_test.cpp b/tools/aapt2/optimize/ResourceDeduper_test.cpp index 2e098aec4f8d..048e318d2802 100644 --- a/tools/aapt2/optimize/ResourceDeduper_test.cpp +++ b/tools/aapt2/optimize/ResourceDeduper_test.cpp @@ -80,11 +80,58 @@ TEST(ResourceDeduperTest, DifferentValuesAreKept) { .Build(); ASSERT_TRUE(ResourceDeduper().Consume(context.get(), table.get())); + EXPECT_THAT(table, HasValue("android:string/keep", default_config)); EXPECT_THAT(table, HasValue("android:string/keep", ldrtl_config)); EXPECT_THAT(table, HasValue("android:string/keep", ldrtl_v21_config)); EXPECT_THAT(table, HasValue("android:string/keep", land_config)); } +TEST(ResourceDeduperTest, SameValuesAreDedupedIncompatibleSiblings) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + const ConfigDescription default_config = {}; + const ConfigDescription ldrtl_config = test::ParseConfigOrDie("ldrtl"); + const ConfigDescription ldrtl_night_config = test::ParseConfigOrDie("ldrtl-night"); + // Chosen because this configuration is not compatible with ldrtl-night. + const ConfigDescription ldrtl_notnight_config = test::ParseConfigOrDie("ldrtl-notnight"); + + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .AddString("android:string/keep", ResourceId{}, default_config, "keep") + .AddString("android:string/keep", ResourceId{}, ldrtl_config, "dedupe") + .AddString("android:string/keep", ResourceId{}, ldrtl_night_config, "dedupe") + .AddString("android:string/keep", ResourceId{}, ldrtl_notnight_config, "keep2") + .Build(); + + ASSERT_TRUE(ResourceDeduper().Consume(context.get(), table.get())); + EXPECT_THAT(table, HasValue("android:string/keep", default_config)); + EXPECT_THAT(table, HasValue("android:string/keep", ldrtl_config)); + EXPECT_THAT(table, Not(HasValue("android:string/keep", ldrtl_night_config))); + EXPECT_THAT(table, HasValue("android:string/keep", ldrtl_notnight_config)); +} + +TEST(ResourceDeduperTest, SameValuesAreDedupedCompatibleNonSiblings) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + const ConfigDescription default_config = {}; + const ConfigDescription ldrtl_config = test::ParseConfigOrDie("ldrtl"); + const ConfigDescription ldrtl_night_config = test::ParseConfigOrDie("ldrtl-night"); + // Chosen because this configuration is compatible with ldrtl. + const ConfigDescription land_config = test::ParseConfigOrDie("land"); + + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .AddString("android:string/keep", ResourceId{}, default_config, "keep") + .AddString("android:string/keep", ResourceId{}, ldrtl_config, "dedupe") + .AddString("android:string/keep", ResourceId{}, ldrtl_night_config, "dedupe") + .AddString("android:string/keep", ResourceId{}, land_config, "keep2") + .Build(); + + ASSERT_TRUE(ResourceDeduper().Consume(context.get(), table.get())); + EXPECT_THAT(table, HasValue("android:string/keep", default_config)); + EXPECT_THAT(table, HasValue("android:string/keep", ldrtl_config)); + EXPECT_THAT(table, Not(HasValue("android:string/keep", ldrtl_night_config))); + EXPECT_THAT(table, HasValue("android:string/keep", land_config)); +} + TEST(ResourceDeduperTest, LocalesValuesAreKept) { std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); const ConfigDescription default_config = {}; diff --git a/tools/aapt2/optimize/ResourcePathShortener.cpp b/tools/aapt2/optimize/ResourcePathShortener.cpp index c5df3dd00db9..7ff9bf5aa8df 100644 --- a/tools/aapt2/optimize/ResourcePathShortener.cpp +++ b/tools/aapt2/optimize/ResourcePathShortener.cpp @@ -16,13 +16,14 @@ #include "optimize/ResourcePathShortener.h" -#include <math.h> +#include <set> #include <unordered_set> #include "androidfw/StringPiece.h" #include "ResourceTable.h" #include "ValueVisitor.h" +#include "util/Util.h" static const std::string base64_chars = @@ -50,18 +51,15 @@ std::string ShortenFileName(const android::StringPiece& file_path, int output_le } -// Calculate the optimal hash length such that an average of 10% of resources -// collide in their shortened path. +// Return the optimal hash length such that at most 10% of resources collide in +// their shortened path. // Reference: http://matt.might.net/articles/counting-hash-collisions/ int OptimalShortenedLength(int num_resources) { - int num_chars = 2; - double N = 64*64; // hash space when hash is 2 chars long - double max_collisions = num_resources * 0.1; - while (num_resources - N + N * pow((N - 1) / N, num_resources) > max_collisions) { - N *= 64; - num_chars++; + if (num_resources > 4000) { + return 3; + } else { + return 2; } - return num_chars; } std::string GetShortenedPath(const android::StringPiece& shortened_filename, @@ -74,10 +72,19 @@ std::string GetShortenedPath(const android::StringPiece& shortened_filename, return shortened_path; } +// implement custom comparator of FileReference pointers so as to use the +// underlying filepath as key rather than the integer address. This is to ensure +// determinism of output for colliding files. +struct PathComparator { + bool operator() (const FileReference* lhs, const FileReference* rhs) const { + return lhs->path->compare(*rhs->path); + } +}; + bool ResourcePathShortener::Consume(IAaptContext* context, ResourceTable* table) { // used to detect collisions std::unordered_set<std::string> shortened_paths; - std::unordered_set<FileReference*> file_refs; + std::set<FileReference*, PathComparator> file_refs; for (auto& package : table->packages) { for (auto& type : package->types) { for (auto& entry : type->entries) { @@ -95,6 +102,10 @@ bool ResourcePathShortener::Consume(IAaptContext* context, ResourceTable* table) android::StringPiece res_subdir, actual_filename, extension; util::ExtractResFilePathParts(*file_ref->path, &res_subdir, &actual_filename, &extension); + // Android detects ColorStateLists via pathname, skip res/color* + if (util::StartsWith(res_subdir, "res/color")) + continue; + std::string shortened_filename = ShortenFileName(*file_ref->path, num_chars); int collision_count = 0; std::string shortened_path = GetShortenedPath(shortened_filename, extension, collision_count); diff --git a/tools/aapt2/optimize/ResourcePathShortener_test.cpp b/tools/aapt2/optimize/ResourcePathShortener_test.cpp index 88cadc76c336..f5a02be0ea5e 100644 --- a/tools/aapt2/optimize/ResourcePathShortener_test.cpp +++ b/tools/aapt2/optimize/ResourcePathShortener_test.cpp @@ -24,6 +24,19 @@ using ::testing::Not; using ::testing::NotNull; using ::testing::Eq; +android::StringPiece GetExtension(android::StringPiece path) { + auto iter = std::find(path.begin(), path.end(), '.'); + return android::StringPiece(iter, path.end() - iter); +} + +void FillTable(aapt::test::ResourceTableBuilder& builder, int start, int end) { + for (int i=start; i<end; i++) { + builder.AddFileReference( + "android:drawable/xmlfile" + std::to_string(i), + "res/drawable/xmlfile" + std::to_string(i) + ".xml"); + } +} + namespace aapt { TEST(ResourcePathShortenerTest, FileRefPathsChangedInResourceTable) { @@ -64,4 +77,90 @@ TEST(ResourcePathShortenerTest, FileRefPathsChangedInResourceTable) { EXPECT_THAT(path_map.find("res/should/still/be/the/same.png"), Eq(path_map.end())); } +TEST(ResourcePathShortenerTest, SkipColorFileRefPaths) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .AddFileReference("android:color/colorlist", "res/color/colorlist.xml") + .AddFileReference("android:color/colorlist", + "res/color-mdp-v21/colorlist.xml", + test::ParseConfigOrDie("mdp-v21")) + .Build(); + + std::map<std::string, std::string> path_map; + ASSERT_TRUE(ResourcePathShortener(path_map).Consume(context.get(), table.get())); + + // Expect that the path map to not contain the ColorStateList + ASSERT_THAT(path_map.find("res/color/colorlist.xml"), Eq(path_map.end())); + ASSERT_THAT(path_map.find("res/color-mdp-v21/colorlist.xml"), Eq(path_map.end())); +} + +TEST(ResourcePathShortenerTest, KeepExtensions) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + + std::string original_xml_path = "res/drawable/xmlfile.xml"; + std::string original_png_path = "res/drawable/pngfile.png"; + + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .AddFileReference("android:color/xmlfile", original_xml_path) + .AddFileReference("android:color/pngfile", original_png_path) + .Build(); + + std::map<std::string, std::string> path_map; + ASSERT_TRUE(ResourcePathShortener(path_map).Consume(context.get(), table.get())); + + // Expect that the path map is populated + ASSERT_THAT(path_map.find("res/drawable/xmlfile.xml"), Not(Eq(path_map.end()))); + ASSERT_THAT(path_map.find("res/drawable/pngfile.png"), Not(Eq(path_map.end()))); + + auto shortend_xml_path = path_map[original_xml_path]; + auto shortend_png_path = path_map[original_png_path]; + + EXPECT_THAT(GetExtension(path_map[original_xml_path]), Eq(android::StringPiece(".xml"))); + EXPECT_THAT(GetExtension(path_map[original_png_path]), Eq(android::StringPiece(".png"))); +} + +TEST(ResourcePathShortenerTest, DeterministicallyHandleCollisions) { + std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build(); + + // 4000 resources is the limit at which the hash space is expanded to 3 + // letters to reduce collisions, we want as many collisions as possible thus + // N-1. + const auto kNumResources = 3999; + const auto kNumTries = 5; + + test::ResourceTableBuilder builder1; + FillTable(builder1, 0, kNumResources); + std::unique_ptr<ResourceTable> table1 = builder1.Build(); + std::map<std::string, std::string> expected_mapping; + ASSERT_TRUE(ResourcePathShortener(expected_mapping).Consume(context.get(), table1.get())); + + // We are trying to ensure lack of non-determinism, it is not simple to prove + // a negative, thus we must try the test a few times so that the test itself + // is non-flaky. Basically create the pathmap 5 times from the same set of + // resources but a different order of addition and then ensure they are always + // mapped to the same short path. + for (int i=0; i<kNumTries; i++) { + test::ResourceTableBuilder builder2; + // This loop adds resources to the resource table in the range of + // [0:kNumResources). Adding the file references in different order makes + // non-determinism more likely to surface. Thus we add resources + // [start_index:kNumResources) first then [0:start_index). We also use a + // different start_index each run. + int start_index = (kNumResources/kNumTries)*i; + FillTable(builder2, start_index, kNumResources); + FillTable(builder2, 0, start_index); + std::unique_ptr<ResourceTable> table2 = builder2.Build(); + + std::map<std::string, std::string> actual_mapping; + ASSERT_TRUE(ResourcePathShortener(actual_mapping).Consume(context.get(), table2.get())); + + for (auto& item : actual_mapping) { + ASSERT_THAT(expected_mapping[item.first], Eq(item.second)); + } + } +} + } // namespace aapt diff --git a/tools/aapt2/process/IResourceTableConsumer.h b/tools/aapt2/process/IResourceTableConsumer.h index 30dad8025900..9c4b323db433 100644 --- a/tools/aapt2/process/IResourceTableConsumer.h +++ b/tools/aapt2/process/IResourceTableConsumer.h @@ -19,6 +19,7 @@ #include <iostream> #include <list> +#include <set> #include <sstream> #include "Diagnostics.h" @@ -50,6 +51,7 @@ struct IAaptContext { virtual NameMangler* GetNameMangler() = 0; virtual bool IsVerbose() = 0; virtual int GetMinSdkVersion() = 0; + virtual const std::set<std::string>& GetSplitNameDependencies() = 0; }; struct IResourceTableConsumer { diff --git a/tools/aapt2/process/SymbolTable.cpp b/tools/aapt2/process/SymbolTable.cpp index 61a8fbbb7f52..897fa80ffedb 100644 --- a/tools/aapt2/process/SymbolTable.cpp +++ b/tools/aapt2/process/SymbolTable.cpp @@ -245,7 +245,8 @@ std::map<size_t, std::string> AssetManagerSymbolSource::GetAssignedPackageIds() return package_map; } -bool AssetManagerSymbolSource::IsPackageDynamic(uint32_t packageId) const { +bool AssetManagerSymbolSource::IsPackageDynamic(uint32_t packageId, + const std::string& package_name) const { if (packageId == 0) { return true; } @@ -253,7 +254,7 @@ bool AssetManagerSymbolSource::IsPackageDynamic(uint32_t packageId) const { for (const std::unique_ptr<const ApkAssets>& assets : apk_assets_) { for (const std::unique_ptr<const android::LoadedPackage>& loaded_package : assets->GetLoadedArsc()->GetPackages()) { - if (packageId == loaded_package->GetPackageId() && loaded_package->IsDynamic()) { + if (package_name == loaded_package->GetPackageName() && loaded_package->IsDynamic()) { return true; } } @@ -313,6 +314,7 @@ static std::unique_ptr<SymbolTable::Symbol> LookupAttributeInTable( symbol.symbol.name = parsed_name.value(); symbol.symbol.id = ResourceId(map_entry.key); symbol.value = map_entry.value.data; + symbol.type = map_entry.value.dataType; s->attribute->symbols.push_back(std::move(symbol)); } } @@ -327,19 +329,19 @@ std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindByName( bool found = false; ResourceId res_id = 0; uint32_t type_spec_flags; + ResourceName real_name; // There can be mangled resources embedded within other packages. Here we will // look into each package and look-up the mangled name until we find the resource. asset_manager_.ForEachPackage([&](const std::string& package_name, uint8_t id) -> bool { - ResourceName real_name(name.package, name.type, name.entry); - + real_name = ResourceName(name.package, name.type, name.entry); if (package_name != name.package) { real_name.entry = mangled_entry; real_name.package = package_name; } res_id = asset_manager_.GetResourceId(real_name.to_string()); - if (res_id.is_valid() && asset_manager_.GetResourceFlags(res_id.id, &type_spec_flags)) { + if (res_id.is_valid_static() && asset_manager_.GetResourceFlags(res_id.id, &type_spec_flags)) { found = true; return false; } @@ -352,12 +354,12 @@ std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindByName( } std::unique_ptr<SymbolTable::Symbol> s; - if (name.type == ResourceType::kAttr) { + if (real_name.type == ResourceType::kAttr) { s = LookupAttributeInTable(asset_manager_, res_id); } else { s = util::make_unique<SymbolTable::Symbol>(); s->id = res_id; - s->is_dynamic = IsPackageDynamic(ResourceId(res_id).package_id()); + s->is_dynamic = IsPackageDynamic(ResourceId(res_id).package_id(), real_name.package); } if (s) { @@ -378,7 +380,7 @@ static Maybe<ResourceName> GetResourceName(android::AssetManager2& am, std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindById( ResourceId id) { - if (!id.is_valid()) { + if (!id.is_valid_static()) { // Exit early and avoid the error logs from AssetManager. return {}; } @@ -405,7 +407,7 @@ std::unique_ptr<SymbolTable::Symbol> AssetManagerSymbolSource::FindById( } else { s = util::make_unique<SymbolTable::Symbol>(); s->id = id; - s->is_dynamic = IsPackageDynamic(ResourceId(id).package_id()); + s->is_dynamic = IsPackageDynamic(ResourceId(id).package_id(), name.package); } if (s) { diff --git a/tools/aapt2/process/SymbolTable.h b/tools/aapt2/process/SymbolTable.h index 6997cd6714a8..06eaf63ad442 100644 --- a/tools/aapt2/process/SymbolTable.h +++ b/tools/aapt2/process/SymbolTable.h @@ -194,7 +194,7 @@ class AssetManagerSymbolSource : public ISymbolSource { bool AddAssetPath(const android::StringPiece& path); std::map<size_t, std::string> GetAssignedPackageIds() const; - bool IsPackageDynamic(uint32_t packageId) const; + bool IsPackageDynamic(uint32_t packageId, const std::string& package_name) const; std::unique_ptr<SymbolTable::Symbol> FindByName( const ResourceName& name) override; diff --git a/tools/aapt2/test/Context.h b/tools/aapt2/test/Context.h index 0564db063b9a..553c43e6c469 100644 --- a/tools/aapt2/test/Context.h +++ b/tools/aapt2/test/Context.h @@ -81,6 +81,14 @@ class Context : public IAaptContext { return min_sdk_version_; } + void SetMinSdkVersion(int min_sdk_version) { + min_sdk_version_ = min_sdk_version; + } + + const std::set<std::string>& GetSplitNameDependencies() override { + return split_name_dependencies_; + } + private: DISALLOW_COPY_AND_ASSIGN(Context); @@ -93,6 +101,7 @@ class Context : public IAaptContext { NameMangler name_mangler_; SymbolTable symbols_; int min_sdk_version_; + std::set<std::string> split_name_dependencies_; }; class ContextBuilder { @@ -127,6 +136,11 @@ class ContextBuilder { return *this; } + ContextBuilder& SetSplitNameDependencies(const std::set<std::string>& split_name_dependencies) { + context_->split_name_dependencies_ = split_name_dependencies; + return *this; + } + std::unique_ptr<Context> Build() { return std::move(context_); } private: diff --git a/tools/aapt2/test/Fixture.cpp b/tools/aapt2/test/Fixture.cpp index a51b4a4649f1..5386802dbc8e 100644 --- a/tools/aapt2/test/Fixture.cpp +++ b/tools/aapt2/test/Fixture.cpp @@ -80,7 +80,7 @@ void TestDirectoryFixture::TearDown() { ClearDirectory(temp_dir_); } -bool TestDirectoryFixture::WriteFile(const std::string& path, const std::string& contents) { +void TestDirectoryFixture::WriteFile(const std::string& path, const std::string& contents) { CHECK(util::StartsWith(path, temp_dir_)) << "Attempting to create a file outside of test temporary directory."; @@ -91,16 +91,31 @@ bool TestDirectoryFixture::WriteFile(const std::string& path, const std::string& file::mkdirs(dirs); } - return android::base::WriteStringToFile(contents, path); + CHECK(android::base::WriteStringToFile(contents, path)); } bool CommandTestFixture::CompileFile(const std::string& path, const std::string& contents, const android::StringPiece& out_dir, IDiagnostics* diag) { - CHECK(WriteFile(path, contents)); + WriteFile(path, contents); CHECK(file::mkdirs(out_dir.data())); return CompileCommand(diag).Execute({path, "-o", out_dir, "-v"}, &std::cerr) == 0; } +bool CommandTestFixture::Link(const std::vector<std::string>& args, IDiagnostics* diag) { + std::vector<android::StringPiece> link_args; + for(const std::string& arg : args) { + link_args.emplace_back(arg); + } + + // Link against the android SDK + std::string android_sdk = file::BuildPath({android::base::GetExecutableDirectory(), + "integration-tests", "CommandTests", + "android-28.jar"}); + link_args.insert(link_args.end(), {"-I", android_sdk}); + + return LinkCommand(diag).Execute(link_args, &std::cerr) == 0; +} + bool CommandTestFixture::Link(const std::vector<std::string>& args, const android::StringPiece& flat_dir, IDiagnostics* diag) { std::vector<android::StringPiece> link_args; @@ -128,10 +143,10 @@ bool CommandTestFixture::Link(const std::vector<std::string>& args, std::string CommandTestFixture::GetDefaultManifest(const char* package_name) { const std::string manifest_file = GetTestPath("AndroidManifest.xml"); - CHECK(WriteFile(manifest_file, android::base::StringPrintf(R"( + WriteFile(manifest_file, android::base::StringPrintf(R"( <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="%s"> - </manifest>)", package_name))); + </manifest>)", package_name)); return manifest_file; } diff --git a/tools/aapt2/test/Fixture.h b/tools/aapt2/test/Fixture.h index fce2aebfecaa..457d65e30b65 100644 --- a/tools/aapt2/test/Fixture.h +++ b/tools/aapt2/test/Fixture.h @@ -58,7 +58,7 @@ class TestDirectoryFixture : public ::testing::Test { // Creates a file with the specified contents, creates any intermediate directories in the // process. The file path must be an absolute path within the test directory. - bool WriteFile(const std::string& path, const std::string& contents); + void WriteFile(const std::string& path, const std::string& contents); private: std::string temp_dir_; @@ -75,6 +75,9 @@ class CommandTestFixture : public TestDirectoryFixture { bool CompileFile(const std::string& path, const std::string& contents, const android::StringPiece& flat_out_dir, IDiagnostics* diag); + // Executes the link command with the specified arguments. + bool Link(const std::vector<std::string>& args, IDiagnostics* diag); + // Executes the link command with the specified arguments. The flattened files residing in the // flat directory will be added to the link command as file arguments. bool Link(const std::vector<std::string>& args, const android::StringPiece& flat_dir, |