diff options
19 files changed, 626 insertions, 58 deletions
diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp index 68d216d286cf..c20c720eadbb 100644 --- a/libs/androidfw/LoadedArsc.cpp +++ b/libs/androidfw/LoadedArsc.cpp @@ -583,7 +583,65 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk, loaded_package->dynamic_package_map_.emplace_back(std::move(package_name), dtohl(entry_iter->packageId)); } + } break; + + case RES_TABLE_OVERLAYABLE_TYPE: { + const ResTable_overlayable_header* header = + child_chunk.header<ResTable_overlayable_header>(); + if (header == nullptr) { + LOG(ERROR) << "RES_TABLE_OVERLAYABLE_TYPE too small."; + return {}; + } + + // Iterate over the overlayable policy chunks + ChunkIterator overlayable_iter(child_chunk.data_ptr(), child_chunk.data_size()); + while (overlayable_iter.HasNext()) { + const Chunk overlayable_child_chunk = overlayable_iter.Next(); + + switch (overlayable_child_chunk.type()) { + case RES_TABLE_OVERLAYABLE_POLICY_TYPE: { + const ResTable_overlayable_policy_header* policy_header = + overlayable_child_chunk.header<ResTable_overlayable_policy_header>(); + if (policy_header == nullptr) { + LOG(ERROR) << "RES_TABLE_OVERLAYABLE_POLICY_TYPE too small."; + return {}; + } + + if ((overlayable_child_chunk.data_size() / sizeof(ResTable_ref)) + < dtohl(policy_header->entry_count)) { + LOG(ERROR) << "RES_TABLE_OVERLAYABLE_POLICY_TYPE too small to hold entries."; + return {}; + } + + // Retrieve all the ids belonging to this policy + std::unordered_set<uint32_t> ids; + const auto ids_begin = + reinterpret_cast<const ResTable_ref*>(overlayable_child_chunk.data_ptr()); + const auto ids_end = ids_begin + dtohl(policy_header->entry_count); + for (auto id_iter = ids_begin; id_iter != ids_end; ++id_iter) { + ids.insert(dtohl(id_iter->ident)); + } + + // Add the pairing of overlayable properties to resource ids to the package + OverlayableInfo overlayable_info; + overlayable_info.policy_flags = policy_header->policy_flags; + loaded_package->overlayable_infos_.push_back(std::make_pair(overlayable_info, ids)); + break; + } + + default: + LOG(WARNING) << StringPrintf("Unknown chunk type '%02x'.", chunk.type()); + break; + } + } + if (overlayable_iter.HadError()) { + LOG(ERROR) << StringPrintf("Error parsing RES_TABLE_OVERLAYABLE_POLICY_TYPE: %s", + overlayable_iter.GetLastError().c_str()); + if (overlayable_iter.HadFatalError()) { + return {}; + } + } } break; default: diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp index 2fe98b00f3a2..63b25270f116 100644 --- a/libs/androidfw/ResourceTypes.cpp +++ b/libs/androidfw/ResourceTypes.cpp @@ -7076,7 +7076,7 @@ public: } } - const auto& getTypeMapping() const { + const std::map<uint8_t, std::set<std::pair<uint32_t, uint32_t>>>& getTypeMapping() const { return mTypeMapping->mData; } @@ -7137,9 +7137,6 @@ status_t ResTable::createIdmap(const ResTable& targetResTable, const PackageGroup* packageGroup = mPackageGroups[0]; - // the number of resources overlaid that were not explicitly marked overlayable - size_t forcedOverlayCount = 0u; - // find the resources that exist in both packages auto typeMapping = std::make_unique<IdmapTypeMapping>(); for (size_t typeIndex = 0; typeIndex < packageGroup->types.size(); ++typeIndex) { @@ -7170,11 +7167,6 @@ status_t ResTable::createIdmap(const ResTable& targetResTable, continue; } - if ((dtohl(typeConfigs->typeSpecFlags[entryIndex]) & - ResTable_typeSpec::SPEC_OVERLAYABLE) == 0) { - ++forcedOverlayCount; - } - typeMapping->add(target_resid, overlay_resid); } } @@ -7243,10 +7235,6 @@ status_t ResTable::createIdmap(const ResTable& targetResTable, typeData += entryCount * 2; } - if (forcedOverlayCount > 0) { - ALOGW("idmap: overlaid %zu resources not marked overlayable", forcedOverlayCount); - } - return NO_ERROR; } diff --git a/libs/androidfw/include/androidfw/Chunk.h b/libs/androidfw/include/androidfw/Chunk.h index 99a52dc9244e..a0f23433c676 100644 --- a/libs/androidfw/include/androidfw/Chunk.h +++ b/libs/androidfw/include/androidfw/Chunk.h @@ -89,7 +89,9 @@ class ChunkIterator { len_(len), last_error_(nullptr) { CHECK(next_chunk_ != nullptr) << "data can't be nullptr"; - VerifyNextChunk(); + if (len_ != 0) { + VerifyNextChunk(); + } } Chunk Next(); diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h index 349b379778a6..8c5c3b7d3858 100644 --- a/libs/androidfw/include/androidfw/LoadedArsc.h +++ b/libs/androidfw/include/androidfw/LoadedArsc.h @@ -20,6 +20,7 @@ #include <memory> #include <set> #include <vector> +#include <unordered_set> #include "android-base/macros.h" @@ -76,6 +77,10 @@ struct TypeSpec { // TypeSpecPtr is a managed pointer that knows how to delete itself. using TypeSpecPtr = util::unique_cptr<TypeSpec>; +struct OverlayableInfo { + uint32_t policy_flags; +}; + class LoadedPackage { public: class iterator { @@ -216,6 +221,18 @@ class LoadedPackage { } } + // Retrieve the overlayable properties of the specified resource. If the resource is not + // overlayable, this will return a null pointer. + const OverlayableInfo* GetOverlayableInfo(uint32_t resid) const { + for (const std::pair<OverlayableInfo, std::unordered_set<uint32_t>>& overlayable_info_ids + : overlayable_infos_) { + if (overlayable_info_ids.second.find(resid) != overlayable_info_ids.second.end()) { + return &overlayable_info_ids.first; + } + } + return nullptr; + } + private: DISALLOW_COPY_AND_ASSIGN(LoadedPackage); @@ -233,6 +250,7 @@ class LoadedPackage { ByteBucketArray<TypeSpecPtr> type_specs_; ByteBucketArray<uint32_t> resource_ids_; std::vector<DynamicPackageEntry> dynamic_package_map_; + std::vector<const std::pair<OverlayableInfo, std::unordered_set<uint32_t>>> overlayable_infos_; }; // Read-only view into a resource table. This class validates all data diff --git a/libs/androidfw/include/androidfw/ResourceTypes.h b/libs/androidfw/include/androidfw/ResourceTypes.h index ad33fcfa2429..91261aa3e4f9 100644 --- a/libs/androidfw/include/androidfw/ResourceTypes.h +++ b/libs/androidfw/include/androidfw/ResourceTypes.h @@ -234,7 +234,9 @@ enum { RES_TABLE_PACKAGE_TYPE = 0x0200, RES_TABLE_TYPE_TYPE = 0x0201, RES_TABLE_TYPE_SPEC_TYPE = 0x0202, - RES_TABLE_LIBRARY_TYPE = 0x0203 + RES_TABLE_LIBRARY_TYPE = 0x0203, + RES_TABLE_OVERLAYABLE_TYPE = 0x0204, + RES_TABLE_OVERLAYABLE_POLICY_TYPE = 0x0205, }; /** @@ -1354,10 +1356,6 @@ struct ResTable_typeSpec enum : uint32_t { // Additional flag indicating an entry is public. SPEC_PUBLIC = 0x40000000u, - - // Additional flag indicating an entry is overlayable at runtime. - // Added in Android-P. - SPEC_OVERLAYABLE = 0x80000000u, }; }; @@ -1607,6 +1605,49 @@ struct ResTable_lib_entry uint16_t packageName[128]; }; +/** + * Specifies the set of resources that are explicitly allowed to be overlaid by RROs. + */ +struct ResTable_overlayable_header +{ + struct ResChunk_header header; +}; + +/** + * Holds a list of resource ids that are protected from being overlaid by a set of policies. If + * the overlay fulfils at least one of the policies, then the overlay can overlay the list of + * resources. + */ +struct ResTable_overlayable_policy_header +{ + struct ResChunk_header header; + + enum PolicyFlags { + // Any overlay can overlay these resources. + POLICY_PUBLIC = 0x00000001, + + // The overlay must reside of the system partition or must have existed on the system partition + // before an upgrade to overlay these resources. + POLICY_SYSTEM_PARTITION = 0x00000002, + + // The overlay must reside of the vendor partition or must have existed on the vendor partition + // before an upgrade to overlay these resources. + POLICY_VENDOR_PARTITION = 0x00000004, + + // The overlay must reside of the product partition or must have existed on the product + // partition before an upgrade to overlay these resources. + POLICY_PRODUCT_PARTITION = 0x00000008, + + // The overlay must reside of the product services partition or must have existed on the product + // services partition before an upgrade to overlay these resources. + POLICY_PRODUCT_SERVICES_PARTITION = 0x00000010, + }; + uint32_t policy_flags; + + // The number of ResTable_ref that follow this header. + uint32_t entry_count; +}; + struct alignas(uint32_t) Idmap_header { // Always 0x504D4449 ('IDMP') uint32_t magic; diff --git a/libs/androidfw/tests/LoadedArsc_test.cpp b/libs/androidfw/tests/LoadedArsc_test.cpp index ffa48367c252..441356b95d36 100644 --- a/libs/androidfw/tests/LoadedArsc_test.cpp +++ b/libs/androidfw/tests/LoadedArsc_test.cpp @@ -22,12 +22,14 @@ #include "TestHelpers.h" #include "data/basic/R.h" #include "data/libclient/R.h" +#include "data/overlayable/R.h" #include "data/sparse/R.h" #include "data/styles/R.h" namespace app = com::android::app; namespace basic = com::android::basic; namespace libclient = com::android::libclient; +namespace overlayable = com::android::overlayable; namespace sparse = com::android::sparse; using ::android::base::ReadFileToString; @@ -273,10 +275,44 @@ TEST(LoadedArscTest, LoadOverlay) { ASSERT_THAT(LoadedPackage::GetEntry(type_spec->types[0], 0x0000), NotNull()); } -// structs with size fields (like Res_value, ResTable_entry) should be -// backwards and forwards compatible (aka checking the size field against -// sizeof(Res_value) might not be backwards compatible. -TEST(LoadedArscTest, LoadingShouldBeForwardsAndBackwardsCompatible) { ASSERT_TRUE(false); } +TEST(LoadedArscTest, LoadOverlayable) { + std::string contents; + ASSERT_TRUE(ReadFileFromZipToString(GetTestDataPath() + "/overlayable/overlayable.apk", + "resources.arsc", &contents)); + + std::unique_ptr<const LoadedArsc> loaded_arsc = + LoadedArsc::Load(StringPiece(contents), nullptr /*loaded_idmap*/, false /*system*/, + false /*load_as_shared_library*/); + + ASSERT_THAT(loaded_arsc, NotNull()); + const LoadedPackage* package = loaded_arsc->GetPackageById( + get_package_id(overlayable::R::string::not_overlayable)); + + const OverlayableInfo* info = package->GetOverlayableInfo( + overlayable::R::string::not_overlayable); + ASSERT_THAT(info, IsNull()); + + info = package->GetOverlayableInfo(overlayable::R::string::overlayable1); + ASSERT_THAT(info, NotNull()); + EXPECT_THAT(info->policy_flags, Eq(ResTable_overlayable_policy_header::POLICY_PUBLIC)); + + info = package->GetOverlayableInfo(overlayable::R::string::overlayable2); + ASSERT_THAT(info, NotNull()); + EXPECT_THAT(info->policy_flags, + Eq(ResTable_overlayable_policy_header::POLICY_SYSTEM_PARTITION + | ResTable_overlayable_policy_header::POLICY_PRODUCT_PARTITION)); + + info = package->GetOverlayableInfo(overlayable::R::string::overlayable3); + ASSERT_THAT(info, NotNull()); + EXPECT_THAT(info->policy_flags, + Eq(ResTable_overlayable_policy_header::POLICY_VENDOR_PARTITION + | ResTable_overlayable_policy_header::POLICY_PRODUCT_SERVICES_PARTITION + | ResTable_overlayable_policy_header::POLICY_PRODUCT_PARTITION)); + + info = package->GetOverlayableInfo(overlayable::R::string::overlayable4); + ASSERT_THAT(info, NotNull()); + EXPECT_THAT(info->policy_flags, Eq(ResTable_overlayable_policy_header::POLICY_PUBLIC)); +} TEST(LoadedArscTest, ResourceIdentifierIterator) { std::string contents; @@ -326,4 +362,9 @@ TEST(LoadedArscTest, ResourceIdentifierIterator) { ASSERT_EQ(end, iter); } +// structs with size fields (like Res_value, ResTable_entry) should be +// backwards and forwards compatible (aka checking the size field against +// sizeof(Res_value) might not be backwards compatible. +TEST(LoadedArscTest, LoadingShouldBeForwardsAndBackwardsCompatible) { ASSERT_TRUE(false); } + } // namespace android diff --git a/libs/androidfw/tests/data/overlay/overlay.apk b/libs/androidfw/tests/data/overlay/overlay.apk Binary files differindex 33f961117c44..d37874dcbb40 100644 --- a/libs/androidfw/tests/data/overlay/overlay.apk +++ b/libs/androidfw/tests/data/overlay/overlay.apk diff --git a/libs/androidfw/tests/data/overlayable/AndroidManifest.xml b/libs/androidfw/tests/data/overlayable/AndroidManifest.xml new file mode 100644 index 000000000000..abc2a454e845 --- /dev/null +++ b/libs/androidfw/tests/data/overlayable/AndroidManifest.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2018 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.overlayable"> + <application> + </application> +</manifest> diff --git a/libs/androidfw/tests/data/overlayable/R.h b/libs/androidfw/tests/data/overlayable/R.h new file mode 100644 index 000000000000..e46e264da318 --- /dev/null +++ b/libs/androidfw/tests/data/overlayable/R.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef TESTS_DATA_OVERLAYABLE_R_H_ +#define TESTS_DATA_OVERLAYABLE_R_H_ + +#include <cstdint> + +namespace com { +namespace android { +namespace overlayable { + +struct R { + struct string { + enum : uint32_t { + not_overlayable = 0x7f010000, + overlayable1 = 0x7f010001, + overlayable2 = 0x7f010002, + overlayable3 = 0x7f010003, + overlayable4 = 0x7f010004, + }; + }; +}; + +} // namespace overlayable +} // namespace android +} // namespace com + +#endif /* TESTS_DATA_OVERLAYABLE_R_H_ */ diff --git a/libs/androidfw/tests/data/overlayable/build b/libs/androidfw/tests/data/overlayable/build new file mode 100755 index 000000000000..98fdc5101160 --- /dev/null +++ b/libs/androidfw/tests/data/overlayable/build @@ -0,0 +1,22 @@ +#!/bin/bash +# +# Copyright (C) 2018 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. +# + +set -e + +aapt2 compile --dir res -o compiled.flata +aapt2 link --manifest AndroidManifest.xml -o overlayable.apk compiled.flata +rm compiled.flata diff --git a/libs/androidfw/tests/data/overlayable/overlayable.apk b/libs/androidfw/tests/data/overlayable/overlayable.apk Binary files differnew file mode 100644 index 000000000000..85ab4be7a2e5 --- /dev/null +++ b/libs/androidfw/tests/data/overlayable/overlayable.apk diff --git a/libs/androidfw/tests/data/overlayable/res/values/overlayable.xml b/libs/androidfw/tests/data/overlayable/res/values/overlayable.xml new file mode 100644 index 000000000000..11aa7354901d --- /dev/null +++ b/libs/androidfw/tests/data/overlayable/res/values/overlayable.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2018 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> +<overlayable> + <!-- Any overlay can overlay the value of @string/overlayable1 --> + <item type="string" name="overlayable1" /> + + <!-- Any overlay on the product or system partition can overlay the value of + @string/overlayable2 --> + <policy type="product|system"> + <item type="string" name="overlayable2" /> + </policy> + + <!-- Any overlay can overlay the value of @string/overlayable4 --> + <policy type="public"> + <item type="string" name="overlayable4" /> + </policy> +</overlayable> + +<overlayable> + <!-- Any overlay on the product_services, vendor, or product partition can overlay the value of + @string/overlayable3 --> + <policy type="product_services|vendor|product"> + <item type="string" name="overlayable3" /> + </policy> +</overlayable> +</resources>
\ No newline at end of file diff --git a/libs/androidfw/tests/data/overlayable/res/values/public.xml b/libs/androidfw/tests/data/overlayable/res/values/public.xml new file mode 100644 index 000000000000..5676d7cc64c9 --- /dev/null +++ b/libs/androidfw/tests/data/overlayable/res/values/public.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2018 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> + <public type="string" name="not_overlayable" id="0x7f010000" /> + <public type="string" name="overlayable1" id="0x7f010001" /> + <public type="string" name="overlayable2" id="0x7f010002" /> + <public type="string" name="overlayable3" id="0x7f010003" /> + <public type="string" name="overlayable4" id="0x7f010004" /> +</resources>
\ No newline at end of file diff --git a/libs/androidfw/tests/data/overlayable/res/values/values.xml b/libs/androidfw/tests/data/overlayable/res/values/values.xml new file mode 100644 index 000000000000..a86b31282bc9 --- /dev/null +++ b/libs/androidfw/tests/data/overlayable/res/values/values.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2018 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="not_overlayable">Not overlayable</string> + <string name="overlayable1">Overlayable One</string> + <string name="overlayable2">Overlayable Two</string> + <string name="overlayable3">Overlayable Three</string> + <string name="overlayable4">Overlayable Four</string> +</resources> diff --git a/tools/aapt2/format/binary/BinaryResourceParser.cpp b/tools/aapt2/format/binary/BinaryResourceParser.cpp index ed70fb3c57d6..df0daebe8453 100644 --- a/tools/aapt2/format/binary/BinaryResourceParser.cpp +++ b/tools/aapt2/format/binary/BinaryResourceParser.cpp @@ -240,6 +240,12 @@ bool BinaryResourceParser::ParsePackage(const ResChunk_header* chunk) { } break; + case android::RES_TABLE_OVERLAYABLE_TYPE: + if (!ParseOverlayable(parser.chunk())) { + return false; + } + break; + default: diag_->Warn(DiagMessage(source_) << "unexpected chunk type " @@ -383,24 +389,12 @@ bool BinaryResourceParser::ParseType(const ResourceTablePackage* package, return false; } - const uint32_t type_spec_flags = entry_type_spec_flags_[res_id]; - if ((entry->flags & ResTable_entry::FLAG_PUBLIC) != 0 || - (type_spec_flags & ResTable_typeSpec::SPEC_OVERLAYABLE) != 0) { - if (entry->flags & ResTable_entry::FLAG_PUBLIC) { - Visibility visibility; - visibility.level = Visibility::Level::kPublic; - visibility.source = source_.WithLine(0); - if (!table_->SetVisibilityWithIdMangled(name, visibility, res_id, diag_)) { - return false; - } - } - - if (type_spec_flags & ResTable_typeSpec::SPEC_OVERLAYABLE) { - Overlayable overlayable; - overlayable.source = source_.WithLine(0); - if (!table_->AddOverlayableMangled(name, overlayable, diag_)) { - return false; - } + if (entry->flags & ResTable_entry::FLAG_PUBLIC) { + Visibility visibility; + visibility.level = Visibility::Level::kPublic; + visibility.source = source_.WithLine(0); + if (!table_->SetVisibilityWithIdMangled(name, visibility, res_id, diag_)) { + return false; } // Erase the ID from the map once processed, so that we don't mark the same symbol more than @@ -433,6 +427,72 @@ bool BinaryResourceParser::ParseLibrary(const ResChunk_header* chunk) { return true; } +bool BinaryResourceParser::ParseOverlayable(const ResChunk_header* chunk) { + const ResTable_overlayable_header* header = ConvertTo<ResTable_overlayable_header>(chunk); + if (!header) { + diag_->Error(DiagMessage(source_) << "corrupt ResTable_category_header chunk"); + return false; + } + + ResChunkPullParser parser(GetChunkData(chunk), + GetChunkDataLen(chunk)); + while (ResChunkPullParser::IsGoodEvent(parser.Next())) { + if (util::DeviceToHost16(parser.chunk()->type) == android::RES_TABLE_OVERLAYABLE_POLICY_TYPE) { + const ResTable_overlayable_policy_header* policy_header = + ConvertTo<ResTable_overlayable_policy_header>(parser.chunk()); + + std::vector<Overlayable::Policy> policies; + if (policy_header->policy_flags & ResTable_overlayable_policy_header::POLICY_PUBLIC) { + policies.push_back(Overlayable::Policy::kPublic); + } + if (policy_header->policy_flags + & ResTable_overlayable_policy_header::POLICY_SYSTEM_PARTITION) { + policies.push_back(Overlayable::Policy::kSystem); + } + if (policy_header->policy_flags + & ResTable_overlayable_policy_header::POLICY_VENDOR_PARTITION) { + policies.push_back(Overlayable::Policy::kVendor); + } + if (policy_header->policy_flags + & ResTable_overlayable_policy_header::POLICY_PRODUCT_PARTITION) { + policies.push_back(Overlayable::Policy::kProduct); + } + if (policy_header->policy_flags + & ResTable_overlayable_policy_header::POLICY_PRODUCT_SERVICES_PARTITION) { + policies.push_back(Overlayable::Policy::kProductServices); + } + + 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 + + util::DeviceToHost32(policy_header->entry_count); + for (auto ref_iter = ref_begin; ref_iter != ref_end; ++ref_iter) { + ResourceId res_id(util::DeviceToHost32(ref_iter->ident)); + const auto iter = id_index_.find(res_id); + + // If the overlayable chunk comes before the type chunks, the resource ids and resource name + // pairing will not exist at this point. + if (iter == id_index_.cend()) { + diag_->Error(DiagMessage(source_) << "failed to find resource name for overlayable" + << " resource " << res_id); + return false; + } + + for (Overlayable::Policy policy : policies) { + Overlayable overlayable; + overlayable.source = source_.WithLine(0); + overlayable.policy = policy; + if (!table_->AddOverlayable(iter->second, overlayable, diag_)) { + return false; + } + } + } + } + } + + return true; +} + std::unique_ptr<Item> BinaryResourceParser::ParseValue(const ResourceNameRef& name, const ConfigDescription& config, const android::Res_value& value) { diff --git a/tools/aapt2/format/binary/BinaryResourceParser.h b/tools/aapt2/format/binary/BinaryResourceParser.h index 2bdc051f4e29..a2eee5006964 100644 --- a/tools/aapt2/format/binary/BinaryResourceParser.h +++ b/tools/aapt2/format/binary/BinaryResourceParser.h @@ -54,6 +54,7 @@ class BinaryResourceParser { bool ParseTypeSpec(const ResourceTablePackage* package, const android::ResChunk_header* chunk); bool ParseType(const ResourceTablePackage* package, const android::ResChunk_header* chunk); bool ParseLibrary(const android::ResChunk_header* chunk); + bool ParseOverlayable(const android::ResChunk_header* chunk); std::unique_ptr<Item> ParseValue(const ResourceNameRef& name, const android::ConfigDescription& config, diff --git a/tools/aapt2/format/binary/TableFlattener.cpp b/tools/aapt2/format/binary/TableFlattener.cpp index 6c1a9ba2cbad..976c3288bfca 100644 --- a/tools/aapt2/format/binary/TableFlattener.cpp +++ b/tools/aapt2/format/binary/TableFlattener.cpp @@ -24,6 +24,7 @@ #include "android-base/logging.h" #include "android-base/macros.h" #include "android-base/stringprintf.h" +#include "androidfw/ResourceUtils.h" #include "ResourceTable.h" #include "ResourceValues.h" @@ -216,6 +217,11 @@ class MapFlattenVisitor : public ValueVisitor { size_t entry_count_ = 0; }; +struct PolicyChunk { + uint32_t policy_flags; + std::set<ResourceId> ids; +}; + class PackageFlattener { public: PackageFlattener(IAaptContext* context, ResourceTablePackage* package, @@ -267,6 +273,8 @@ class PackageFlattener { FlattenLibrarySpec(buffer); } + FlattenOverlayable(buffer); + pkg_writer.Finish(); return true; } @@ -413,6 +421,97 @@ class PackageFlattener { return sorted_entries; } + void FlattenOverlayable(BigBuffer* buffer) { + std::vector<PolicyChunk> policies; + + CHECK(bool(package_->id)) << "package must have an ID set when flattening <overlayable>"; + for (auto& type : package_->types) { + CHECK(bool(type->id)) << "type must have an ID set when flattening <overlayable>"; + for (auto& entry : type->entries) { + CHECK(bool(type->id)) << "entry must have an ID set when flattening <overlayable>"; + + // TODO(b/120298168): Convert the policies vector to a policy set or bitmask + if (!entry->overlayable_declarations.empty()) { + uint16_t policy_flags = 0; + for (Overlayable overlayable : entry->overlayable_declarations) { + if (overlayable.policy) { + switch (overlayable.policy.value()) { + case Overlayable::Policy::kPublic: + policy_flags |= ResTable_overlayable_policy_header::POLICY_PUBLIC; + break; + case Overlayable::Policy::kSystem: + policy_flags |= ResTable_overlayable_policy_header::POLICY_SYSTEM_PARTITION; + break; + case Overlayable::Policy::kVendor: + policy_flags |= ResTable_overlayable_policy_header::POLICY_VENDOR_PARTITION; + break; + case Overlayable::Policy::kProduct: + policy_flags |= ResTable_overlayable_policy_header::POLICY_PRODUCT_PARTITION; + break; + case Overlayable::Policy::kProductServices: + policy_flags |= + ResTable_overlayable_policy_header::POLICY_PRODUCT_SERVICES_PARTITION; + break; + } + } else { + // Encode overlayable entries defined without a policy as publicly overlayable + policy_flags |= ResTable_overlayable_policy_header::POLICY_PUBLIC; + } + } + + // Find the overlayable policy chunk with the same policies as the entry + PolicyChunk* policy_chunk = nullptr; + for (PolicyChunk& policy : policies) { + if (policy.policy_flags == policy_flags) { + policy_chunk = &policy; + break; + } + } + + // Create a new policy chunk if an existing one with the same policy cannot be found + if (policy_chunk == nullptr) { + PolicyChunk p; + p.policy_flags = policy_flags; + policies.push_back(p); + policy_chunk = &policies.back(); + } + + policy_chunk->ids.insert(android::make_resid(package_->id.value(), type->id.value(), + entry->id.value())); + } + } + } + + if (policies.empty()) { + // Only write the overlayable chunk if the APK has overlayable entries + return; + } + + ChunkWriter writer(buffer); + writer.StartChunk<ResTable_overlayable_header>(RES_TABLE_OVERLAYABLE_TYPE); + + // Write each policy block for the overlayable + for (PolicyChunk& policy : policies) { + ChunkWriter policy_writer(buffer); + ResTable_overlayable_policy_header* policy_type = + policy_writer.StartChunk<ResTable_overlayable_policy_header>( + RES_TABLE_OVERLAYABLE_POLICY_TYPE); + policy_type->policy_flags = util::HostToDevice32(policy.policy_flags); + policy_type->entry_count = util::HostToDevice32(static_cast<uint32_t>(policy.ids.size())); + + // Write the ids after the policy header + ResTable_ref* id_block = policy_writer.NextBlock<ResTable_ref>(policy.ids.size()); + for (const ResourceId& id : policy.ids) { + id_block->ident = util::HostToDevice32(id.id); + id_block++; + } + + policy_writer.Finish(); + } + + writer.Finish(); + } + bool FlattenTypeSpec(ResourceTableType* type, std::vector<ResourceEntry*>* sorted_entries, BigBuffer* buffer) { ChunkWriter type_spec_writer(buffer); @@ -446,11 +545,6 @@ class PackageFlattener { config_masks[entry->id.value()] |= util::HostToDevice32(ResTable_typeSpec::SPEC_PUBLIC); } - if (!entry->overlayable_declarations.empty()) { - config_masks[entry->id.value()] |= - util::HostToDevice32(ResTable_typeSpec::SPEC_OVERLAYABLE); - } - const size_t config_count = entry->values.size(); for (size_t i = 0; i < config_count; i++) { const ConfigDescription& config = entry->values[i]->config; diff --git a/tools/aapt2/format/binary/TableFlattener_test.cpp b/tools/aapt2/format/binary/TableFlattener_test.cpp index cd1414c7e628..410efbe83b1b 100644 --- a/tools/aapt2/format/binary/TableFlattener_test.cpp +++ b/tools/aapt2/format/binary/TableFlattener_test.cpp @@ -628,24 +628,108 @@ TEST_F(TableFlattenerTest, ObfuscatingResourceNamesWithWhitelistSucceeds) { } TEST_F(TableFlattenerTest, FlattenOverlayable) { + std::string name = "com.app.test:integer/overlayable"; std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder() .SetPackageId("com.app.test", 0x7f) - .AddSimple("com.app.test:integer/overlayable", ResourceId(0x7f020000)) + .AddSimple(name, ResourceId(0x7f020000)) + .AddOverlayable(name, Overlayable::Policy::kProduct) + .AddOverlayable(name, Overlayable::Policy::kSystem) + .AddOverlayable(name, Overlayable::Policy::kVendor) .Build(); - ASSERT_TRUE(table->AddOverlayable(test::ParseNameOrDie("com.app.test:integer/overlayable"), - Overlayable{}, test::GetDiagnostics())); + ResourceTable output_table; + ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &output_table)); - ResTable res_table; - ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &res_table)); + auto search_result = output_table.FindResource(test::ParseNameOrDie(name)); + ASSERT_TRUE(search_result); + ASSERT_THAT(search_result.value().entry, NotNull()); + EXPECT_EQ(search_result.value().entry->overlayable_declarations.size(), 3); + EXPECT_TRUE(search_result.value().entry->overlayable_declarations[0].policy); + EXPECT_EQ(search_result.value().entry->overlayable_declarations[0].policy, + Overlayable::Policy::kSystem); + EXPECT_TRUE(search_result.value().entry->overlayable_declarations[1].policy); + EXPECT_EQ(search_result.value().entry->overlayable_declarations[1].policy, + Overlayable::Policy::kVendor); + EXPECT_TRUE(search_result.value().entry->overlayable_declarations[2].policy); + EXPECT_EQ(search_result.value().entry->overlayable_declarations[2].policy, + Overlayable::Policy::kProduct); +} + +TEST_F(TableFlattenerTest, FlattenMultipleOverlayablePolicies) { + std::string name_zero = "com.app.test:integer/overlayable_zero"; + std::string name_one = "com.app.test:integer/overlayable_one"; + std::string name_two = "com.app.test:integer/overlayable_two"; + std::string name_three = "com.app.test:integer/overlayable_three"; + std::unique_ptr<ResourceTable> table = + test::ResourceTableBuilder() + .SetPackageId("com.app.test", 0x7f) + .AddSimple(name_zero, ResourceId(0x7f020000)) + .AddOverlayable(name_zero, Overlayable::Policy::kProduct) + .AddOverlayable(name_zero, Overlayable::Policy::kSystem) + .AddOverlayable(name_zero, Overlayable::Policy::kProductServices) + .AddSimple(name_one, ResourceId(0x7f020001)) + .AddOverlayable(name_one, Overlayable::Policy::kPublic) + .AddOverlayable(name_one, Overlayable::Policy::kSystem) + .AddSimple(name_two, ResourceId(0x7f020002)) + .AddOverlayable(name_two, Overlayable::Policy::kProduct) + .AddOverlayable(name_two, Overlayable::Policy::kSystem) + .AddOverlayable(name_two, Overlayable::Policy::kProductServices) + .AddSimple(name_three, ResourceId(0x7f020003)) + .AddOverlayable(name_three, {}) + .Build(); + + ResourceTable output_table; + ASSERT_TRUE(Flatten(context_.get(), {}, table.get(), &output_table)); + + auto search_result = output_table.FindResource(test::ParseNameOrDie(name_zero)); + ASSERT_TRUE(search_result); + ASSERT_THAT(search_result.value().entry, NotNull()); + EXPECT_EQ(search_result.value().entry->overlayable_declarations.size(), 3); + EXPECT_TRUE(search_result.value().entry->overlayable_declarations[0].policy); + EXPECT_EQ(search_result.value().entry->overlayable_declarations[0].policy, + Overlayable::Policy::kSystem); + EXPECT_TRUE(search_result.value().entry->overlayable_declarations[1].policy); + EXPECT_EQ(search_result.value().entry->overlayable_declarations[1].policy, + Overlayable::Policy::kProduct); + EXPECT_TRUE(search_result.value().entry->overlayable_declarations[2].policy); + EXPECT_EQ(search_result.value().entry->overlayable_declarations[2].policy, + Overlayable::Policy::kProductServices); + + search_result = output_table.FindResource(test::ParseNameOrDie(name_one)); + ASSERT_TRUE(search_result); + ASSERT_THAT(search_result.value().entry, NotNull()); + EXPECT_EQ(search_result.value().entry->overlayable_declarations.size(), 2); + EXPECT_TRUE(search_result.value().entry->overlayable_declarations[0].policy); + EXPECT_EQ(search_result.value().entry->overlayable_declarations[0].policy, + Overlayable::Policy::kPublic); + EXPECT_TRUE(search_result.value().entry->overlayable_declarations[1].policy); + EXPECT_EQ(search_result.value().entry->overlayable_declarations[1].policy, + Overlayable::Policy::kSystem); + + search_result = output_table.FindResource(test::ParseNameOrDie(name_two)); + ASSERT_TRUE(search_result); + ASSERT_THAT(search_result.value().entry, NotNull()); + EXPECT_EQ(search_result.value().entry->overlayable_declarations.size(), 3); + EXPECT_TRUE(search_result.value().entry->overlayable_declarations[0].policy); + EXPECT_EQ(search_result.value().entry->overlayable_declarations[0].policy, + Overlayable::Policy::kSystem); + EXPECT_TRUE(search_result.value().entry->overlayable_declarations[1].policy); + EXPECT_EQ(search_result.value().entry->overlayable_declarations[1].policy, + Overlayable::Policy::kProduct); + EXPECT_TRUE(search_result.value().entry->overlayable_declarations[2].policy); + EXPECT_EQ(search_result.value().entry->overlayable_declarations[2].policy, + Overlayable::Policy::kProductServices); + + search_result = output_table.FindResource(test::ParseNameOrDie(name_three)); + ASSERT_TRUE(search_result); + ASSERT_THAT(search_result.value().entry, NotNull()); + EXPECT_EQ(search_result.value().entry->overlayable_declarations.size(), 1); + EXPECT_TRUE(search_result.value().entry->overlayable_declarations[0].policy); + EXPECT_EQ(search_result.value().entry->overlayable_declarations[0].policy, + Overlayable::Policy::kPublic); - const StringPiece16 overlayable_name(u"com.app.test:integer/overlayable"); - uint32_t spec_flags = 0u; - ASSERT_THAT(res_table.identifierForName(overlayable_name.data(), overlayable_name.size(), nullptr, - 0u, nullptr, 0u, &spec_flags), - Gt(0u)); - EXPECT_TRUE(spec_flags & android::ResTable_typeSpec::SPEC_OVERLAYABLE); } + } // namespace aapt diff --git a/tools/aapt2/link/ReferenceLinker.cpp b/tools/aapt2/link/ReferenceLinker.cpp index 3a5d5858254d..1b6626a8dfe9 100644 --- a/tools/aapt2/link/ReferenceLinker.cpp +++ b/tools/aapt2/link/ReferenceLinker.cpp @@ -368,7 +368,16 @@ bool ReferenceLinker::Consume(IAaptContext* context, ResourceTable* table) { // Symbol state information may be lost if there is no value for the resource. if (entry->visibility.level != Visibility::Level::kUndefined && entry->values.empty()) { context->GetDiagnostics()->Error(DiagMessage(entry->visibility.source) - << "no definition for declared symbol '" << name << "'"); + << "no definition for declared symbol '" << name + << "'"); + error = true; + } + + // Ensure that definitions for values declared as overlayable exist + if (!entry->overlayable_declarations.empty() && entry->values.empty()) { + context->GetDiagnostics()->Error(DiagMessage(entry->overlayable_declarations[0].source) + << "no definition for overlayable symbol '" + << name << "'"); error = true; } |