Add <staging-public-group-final> to aapt2

To allow apps that compiled against a pre-release SDK to continue
working for a period of time after API finalization, a new tag,
<staging-public-group-final>, has been added to aapt2.

When finalizing the framework resource API, converting
<staging-public-group> tags to <staging-public-group-final> will
cause aapt2 to generate the resource table so that there is a resource
entry for the old non-finalized (staged) resource ID and another entry
for the finalized resource ID of newly finalized resources. This allows
an application that compiled against the pre-release SDK to continue
resolving resources using pre-release resource IDs.

All references to pre-release resource IDs will be rewritten to their
finalized resource IDs through the information stored in the new staged
alias chunk. This allows applications compiled against
<staging-public-group> resources to use the newly finalized
resource ID without re-compilation.

When an application is re-compiled against the SDK with
<staging-public-group-final> tags, the application will use the
finalized resource IDs.

This change limits the use of the alias chunk to the framework for S.

Bug: 183411356
Test: aapt2_test
Change-Id: Iba1c3033c3c2f32de8e4a19b58d3921c971092c4
This commit is contained in:
Ryan Mitchell 2021-04-23 07:47:38 -07:00
parent 969f4ec61b
commit 2fedba9a32
22 changed files with 717 additions and 203 deletions

View File

@ -221,6 +221,16 @@ void AssetManager2::BuildDynamicRefTable() {
for (auto iter2 = package_groups_.begin(); iter2 != package_groups_end; ++iter2) {
iter2->dynamic_ref_table->addMapping(String16(package_name.c_str(), package_name.size()),
iter->dynamic_ref_table->mAssignedPackageId);
// Add the alias resources to the dynamic reference table of every package group. Since
// staging aliases can only be defined by the framework package (which is not a shared
// library), the compile-time package id of the framework is the same across all packages
// that compile against the framework.
for (const auto& package : iter2->packages_) {
for (const auto& entry : package.loaded_package_->GetAliasResourceIdMap()) {
iter->dynamic_ref_table->addAlias(entry.first, entry.second);
}
}
}
}
}

View File

@ -41,6 +41,7 @@ using android::base::StringPrintf;
namespace android {
constexpr const static int kFrameworkPackageId = 0x01;
constexpr const static int kAppPackageId = 0x7f;
namespace {
@ -675,6 +676,42 @@ std::unique_ptr<const LoadedPackage> LoadedPackage::Load(const Chunk& chunk,
}
} break;
case RES_TABLE_STAGED_ALIAS_TYPE: {
if (loaded_package->package_id_ != kFrameworkPackageId) {
LOG(WARNING) << "Alias chunk ignored for non-framework package '"
<< loaded_package->package_name_ << "'";
break;
}
std::unordered_set<uint32_t> finalized_ids;
const auto lib_alias = child_chunk.header<ResTable_staged_alias_header>();
if (!lib_alias) {
return {};
}
const auto entry_begin = child_chunk.data_ptr().convert<ResTable_staged_alias_entry>();
const auto entry_end = entry_begin + dtohl(lib_alias->count);
for (auto entry_iter = entry_begin; entry_iter != entry_end; ++entry_iter) {
if (!entry_iter) {
return {};
}
auto finalized_id = dtohl(entry_iter->finalizedResId);
if (!finalized_ids.insert(finalized_id).second) {
LOG(ERROR) << StringPrintf("Repeated finalized resource id '%08x' in staged aliases.",
finalized_id);
return {};
}
auto staged_id = dtohl(entry_iter->stagedResId);
auto [_, success] = loaded_package->alias_id_map_.insert(std::make_pair(staged_id,
finalized_id));
if (!success) {
LOG(ERROR) << StringPrintf("Repeated staged resource id '%08x' in staged aliases.",
staged_id);
return {};
}
}
} break;
default:
LOG(WARNING) << StringPrintf("Unknown chunk type '%02x'.", chunk.type());
break;

View File

@ -7079,6 +7079,10 @@ void DynamicRefTable::addMapping(uint8_t buildPackageId, uint8_t runtimePackageI
mLookupTable[buildPackageId] = runtimePackageId;
}
void DynamicRefTable::addAlias(uint32_t stagedId, uint32_t finalizedId) {
mAliasId[stagedId] = finalizedId;
}
status_t DynamicRefTable::lookupResourceId(uint32_t* resId) const {
uint32_t res = *resId;
size_t packageId = Res_GETPACKAGE(res) + 1;
@ -7088,8 +7092,16 @@ status_t DynamicRefTable::lookupResourceId(uint32_t* resId) const {
return NO_ERROR;
}
if (packageId == APP_PACKAGE_ID && !mAppAsLib) {
// No lookup needs to be done, app package IDs are absolute.
auto alias_id = mAliasId.find(res);
if (alias_id != mAliasId.end()) {
// Rewrite the resource id to its alias resource id. Since the alias resource id is a
// compile-time id, it still needs to be resolved further.
res = alias_id->second;
}
if (packageId == SYS_PACKAGE_ID || (packageId == APP_PACKAGE_ID && !mAppAsLib)) {
// No lookup needs to be done, app and framework package IDs are absolute.
*resId = res;
return NO_ERROR;
}

View File

@ -17,6 +17,7 @@
#ifndef LOADEDARSC_H_
#define LOADEDARSC_H_
#include <map>
#include <memory>
#include <set>
#include <vector>
@ -171,51 +172,51 @@ class LoadedPackage {
incfs::verified_map_ptr<ResTable_type> type_chunk, uint32_t offset);
// Returns the string pool where type names are stored.
inline const ResStringPool* GetTypeStringPool() const {
const ResStringPool* GetTypeStringPool() const {
return &type_string_pool_;
}
// Returns the string pool where the names of resource entries are stored.
inline const ResStringPool* GetKeyStringPool() const {
const ResStringPool* GetKeyStringPool() const {
return &key_string_pool_;
}
inline const std::string& GetPackageName() const {
const std::string& GetPackageName() const {
return package_name_;
}
inline int GetPackageId() const {
int GetPackageId() const {
return package_id_;
}
// Returns true if this package is dynamic (shared library) and needs to have an ID assigned.
inline bool IsDynamic() const {
bool IsDynamic() const {
return (property_flags_ & PROPERTY_DYNAMIC) != 0;
}
// Returns true if this package is a Runtime Resource Overlay.
inline bool IsOverlay() const {
bool IsOverlay() const {
return (property_flags_ & PROPERTY_OVERLAY) != 0;
}
// Returns true if this package originates from a system provided resource.
inline bool IsSystem() const {
bool IsSystem() const {
return (property_flags_ & PROPERTY_SYSTEM) != 0;
}
// Returns true if this package is a custom loader and should behave like an overlay.
inline bool IsCustomLoader() const {
bool IsCustomLoader() const {
return (property_flags_ & PROPERTY_LOADER) != 0;
}
inline package_property_t GetPropertyFlags() const {
package_property_t GetPropertyFlags() const {
return property_flags_;
}
// Returns the map of package name to package ID used in this LoadedPackage. At runtime, a
// package could have been assigned a different package ID than what this LoadedPackage was
// compiled with. AssetManager rewrites the package IDs so that they are compatible at runtime.
inline const std::vector<DynamicPackageEntry>& GetDynamicPackageMap() const {
const std::vector<DynamicPackageEntry>& GetDynamicPackageMap() const {
return dynamic_package_map_;
}
@ -270,6 +271,10 @@ class LoadedPackage {
return overlayable_map_;
}
const std::map<uint32_t, uint32_t>& GetAliasResourceIdMap() const {
return alias_id_map_;
}
private:
DISALLOW_COPY_AND_ASSIGN(LoadedPackage);
@ -287,6 +292,7 @@ class LoadedPackage {
ByteBucketArray<uint32_t> resource_ids_;
std::vector<DynamicPackageEntry> dynamic_package_map_;
std::vector<const std::pair<OverlayableInfo, std::unordered_set<uint32_t>>> overlayable_infos_;
std::map<uint32_t, uint32_t> alias_id_map_;
// A map of overlayable name to actor
std::unordered_map<std::string, std::string> overlayable_map_;

View File

@ -39,6 +39,7 @@
#include <android/configuration.h>
#include <array>
#include <map>
#include <memory>
namespace android {
@ -229,30 +230,31 @@ struct ResChunk_header
};
enum {
RES_NULL_TYPE = 0x0000,
RES_STRING_POOL_TYPE = 0x0001,
RES_TABLE_TYPE = 0x0002,
RES_XML_TYPE = 0x0003,
RES_NULL_TYPE = 0x0000,
RES_STRING_POOL_TYPE = 0x0001,
RES_TABLE_TYPE = 0x0002,
RES_XML_TYPE = 0x0003,
// Chunk types in RES_XML_TYPE
RES_XML_FIRST_CHUNK_TYPE = 0x0100,
RES_XML_START_NAMESPACE_TYPE= 0x0100,
RES_XML_END_NAMESPACE_TYPE = 0x0101,
RES_XML_START_ELEMENT_TYPE = 0x0102,
RES_XML_END_ELEMENT_TYPE = 0x0103,
RES_XML_CDATA_TYPE = 0x0104,
RES_XML_LAST_CHUNK_TYPE = 0x017f,
RES_XML_FIRST_CHUNK_TYPE = 0x0100,
RES_XML_START_NAMESPACE_TYPE = 0x0100,
RES_XML_END_NAMESPACE_TYPE = 0x0101,
RES_XML_START_ELEMENT_TYPE = 0x0102,
RES_XML_END_ELEMENT_TYPE = 0x0103,
RES_XML_CDATA_TYPE = 0x0104,
RES_XML_LAST_CHUNK_TYPE = 0x017f,
// This contains a uint32_t array mapping strings in the string
// pool back to resource identifiers. It is optional.
RES_XML_RESOURCE_MAP_TYPE = 0x0180,
RES_XML_RESOURCE_MAP_TYPE = 0x0180,
// Chunk types in RES_TABLE_TYPE
RES_TABLE_PACKAGE_TYPE = 0x0200,
RES_TABLE_TYPE_TYPE = 0x0201,
RES_TABLE_TYPE_SPEC_TYPE = 0x0202,
RES_TABLE_LIBRARY_TYPE = 0x0203,
RES_TABLE_OVERLAYABLE_TYPE = 0x0204,
RES_TABLE_PACKAGE_TYPE = 0x0200,
RES_TABLE_TYPE_TYPE = 0x0201,
RES_TABLE_TYPE_SPEC_TYPE = 0x0202,
RES_TABLE_LIBRARY_TYPE = 0x0203,
RES_TABLE_OVERLAYABLE_TYPE = 0x0204,
RES_TABLE_OVERLAYABLE_POLICY_TYPE = 0x0205,
RES_TABLE_STAGED_ALIAS_TYPE = 0x0206,
};
/**
@ -1638,6 +1640,29 @@ struct ResTable_lib_entry
uint16_t packageName[128];
};
/**
* A map that allows rewriting staged (non-finalized) resource ids to their finalized counterparts.
*/
struct ResTable_staged_alias_header
{
struct ResChunk_header header;
// The number of ResTable_staged_alias_entry that follow this header.
uint32_t count;
};
/**
* Maps the staged (non-finalized) resource id to its finalized resource id.
*/
struct ResTable_staged_alias_entry
{
// The compile-time staged resource id to rewrite.
uint32_t stagedResId;
// The compile-time finalized resource id to which the staged resource id should be rewritten.
uint32_t finalizedResId;
};
/**
* Specifies the set of resources that are explicitly allowed to be overlaid by RROs.
*/
@ -1751,6 +1776,8 @@ public:
void addMapping(uint8_t buildPackageId, uint8_t runtimePackageId);
void addAlias(uint32_t stagedId, uint32_t finalizedId);
// Returns whether or not the value must be looked up.
bool requiresLookup(const Res_value* value) const;
@ -1768,6 +1795,7 @@ private:
uint8_t mLookupTable[256];
KeyedVector<String16, uint8_t> mEntries;
bool mAppAsLib;
std::map<uint32_t, uint32_t> mAliasId;
};
bool U16StringToInt(const char16_t* s, size_t len, Res_value* outValue);

View File

@ -278,17 +278,19 @@ void Debug::PrintTable(const ResourceTable& table, const DebugPrintTableOptions&
printer->Println(StringPrintf(" entryCount=%zd", type.entries.size()));
printer->Indent();
for (const ResourceEntry* entry : type.entries) {
for (const ResourceTableEntryView& entry : type.entries) {
printer->Print("resource ");
printer->Print(entry->id.value_or_default(0).to_string());
printer->Print(ResourceId(package.id.value_or_default(0), type.id.value_or_default(0),
entry.id.value_or_default(0))
.to_string());
printer->Print(" ");
// Write the name without the package (this is obvious and too verbose).
printer->Print(to_string(type.type));
printer->Print("/");
printer->Print(entry->name);
printer->Print(entry.name);
switch (entry->visibility.level) {
switch (entry.visibility.level) {
case Visibility::Level::kPublic:
printer->Print(" PUBLIC");
break;
@ -300,19 +302,24 @@ void Debug::PrintTable(const ResourceTable& table, const DebugPrintTableOptions&
break;
}
if (entry->visibility.staged_api) {
if (entry.visibility.staged_api) {
printer->Print(" STAGED");
}
if (entry->overlayable_item) {
if (entry.overlayable_item) {
printer->Print(" OVERLAYABLE");
}
if (entry.staged_id) {
printer->Print(" STAGED_ID=");
printer->Print(entry.staged_id.value().id.to_string());
}
printer->Println();
if (options.show_values) {
printer->Indent();
for (const auto& value : entry->values) {
for (const auto& value : entry.values) {
printer->Print("(");
printer->Print(value->config.to_string());
printer->Print(") ");

View File

@ -45,6 +45,7 @@ namespace aapt {
namespace {
constexpr const char* kPublicGroupTag = "public-group";
constexpr const char* kStagingPublicGroupTag = "staging-public-group";
constexpr const char* kStagingPublicGroupFinalTag = "staging-public-group-final";
} // namespace
constexpr const char* sXliffNamespaceUri = "urn:oasis:names:tc:xliff:document:1.2";
@ -109,6 +110,7 @@ struct ParsedResource {
bool staged_api = false;
bool allow_new = false;
Maybe<OverlayableItem> overlayable_item;
Maybe<StagedId> staged_alias;
std::string comment;
std::unique_ptr<Value> value;
@ -155,6 +157,10 @@ static bool AddResourcesToTable(ResourceTable* table, IDiagnostics* diag, Parsed
res_builder.SetValue(std::move(res->value), res->config, res->product);
}
if (res->staged_alias) {
res_builder.SetStagedId(res->staged_alias.value());
}
bool error = false;
if (!res->name.entry.empty()) {
if (!table->AddResource(res_builder.Build(), diag)) {
@ -532,6 +538,7 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser,
{"public", std::mem_fn(&ResourceParser::ParsePublic)},
{"public-group", std::mem_fn(&ResourceParser::ParsePublicGroup)},
{"staging-public-group", std::mem_fn(&ResourceParser::ParseStagingPublicGroup)},
{"staging-public-group-final", std::mem_fn(&ResourceParser::ParseStagingPublicGroupFinal)},
{"string-array", std::mem_fn(&ResourceParser::ParseStringArray)},
{"style", std::bind(&ResourceParser::ParseStyle, std::placeholders::_1, ResourceType::kStyle,
std::placeholders::_2, std::placeholders::_3)},
@ -671,7 +678,7 @@ bool ResourceParser::ParseResource(xml::XmlPullParser* parser,
if (bag_iter != elToBagMap.end()) {
// Ensure we have a name (unless this is a <public-group> or <overlayable>).
if (resource_type != kPublicGroupTag && resource_type != kStagingPublicGroupTag &&
resource_type != "overlayable") {
resource_type != kStagingPublicGroupFinalTag && resource_type != "overlayable") {
if (!maybe_name) {
diag_->Error(DiagMessage(out_resource->source)
<< "<" << parser->element_name() << "> missing 'name' attribute");
@ -1034,7 +1041,6 @@ bool static ParseGroupImpl(xml::XmlPullParser* parser, ParsedResource* out_resou
ParsedResource& entry_res = out_resource->child_resources.emplace_back(ParsedResource{
.name = ResourceName{{}, *parsed_type, maybe_name.value().to_string()},
.source = item_source,
.id = next_id,
.comment = std::move(comment),
});
@ -1060,6 +1066,14 @@ bool ResourceParser::ParseStagingPublicGroup(xml::XmlPullParser* parser,
});
}
bool ResourceParser::ParseStagingPublicGroupFinal(xml::XmlPullParser* parser,
ParsedResource* out_resource) {
return ParseGroupImpl(parser, out_resource, kStagingPublicGroupFinalTag, diag_,
[](ParsedResource& parsed_entry, ResourceId id) {
parsed_entry.staged_alias = StagedId{id, parsed_entry.source};
});
}
bool ResourceParser::ParsePublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource) {
if (options_.visibility) {
diag_->Error(DiagMessage(out_resource->source)

View File

@ -115,6 +115,7 @@ class ResourceParser {
bool ParsePublic(xml::XmlPullParser* parser, ParsedResource* out_resource);
bool ParsePublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource);
bool ParseStagingPublicGroup(xml::XmlPullParser* parser, ParsedResource* out_resource);
bool ParseStagingPublicGroupFinal(xml::XmlPullParser* parser, ParsedResource* out_resource);
bool ParseSymbolImpl(xml::XmlPullParser* parser, ParsedResource* out_resource);
bool ParseSymbol(xml::XmlPullParser* parser, ParsedResource* out_resource);
bool ParseOverlayable(xml::XmlPullParser* parser, ParsedResource* out_resource);

View File

@ -83,6 +83,20 @@ T* FindElementsRunAction(const android::StringPiece& name, Elements& entries, Fu
return action(found, iter);
}
struct ConfigKey {
const ConfigDescription* config;
const StringPiece& product;
};
template <typename T>
bool lt_config_key_ref(const T& lhs, const ConfigKey& rhs) {
int cmp = lhs->config.compare(*rhs.config);
if (cmp == 0) {
cmp = StringPiece(lhs->product).compare(rhs.product);
}
return cmp < 0;
}
} // namespace
ResourceTable::ResourceTable(ResourceTable::Validation validation) : validation_(validation) {
@ -134,23 +148,10 @@ ResourceEntry* ResourceTableType::FindOrCreateEntry(const android::StringPiece&
});
}
struct ConfigKey {
const ConfigDescription* config;
const StringPiece& product;
};
bool lt_config_key_ref(const std::unique_ptr<ResourceConfigValue>& lhs, const ConfigKey& rhs) {
int cmp = lhs->config.compare(*rhs.config);
if (cmp == 0) {
cmp = StringPiece(lhs->product).compare(rhs.product);
}
return cmp < 0;
}
ResourceConfigValue* ResourceEntry::FindValue(const ConfigDescription& config,
android::StringPiece product) {
auto iter = std::lower_bound(values.begin(), values.end(), ConfigKey{&config, product},
lt_config_key_ref);
lt_config_key_ref<std::unique_ptr<ResourceConfigValue>>);
if (iter != values.end()) {
ResourceConfigValue* value = iter->get();
if (value->config == config && StringPiece(value->product) == product) {
@ -163,7 +164,7 @@ ResourceConfigValue* ResourceEntry::FindValue(const ConfigDescription& config,
const ResourceConfigValue* ResourceEntry::FindValue(const android::ConfigDescription& config,
android::StringPiece product) const {
auto iter = std::lower_bound(values.begin(), values.end(), ConfigKey{&config, product},
lt_config_key_ref);
lt_config_key_ref<std::unique_ptr<ResourceConfigValue>>);
if (iter != values.end()) {
ResourceConfigValue* value = iter->get();
if (value->config == config && StringPiece(value->product) == product) {
@ -176,7 +177,7 @@ const ResourceConfigValue* ResourceEntry::FindValue(const android::ConfigDescrip
ResourceConfigValue* ResourceEntry::FindOrCreateValue(const ConfigDescription& config,
const StringPiece& product) {
auto iter = std::lower_bound(values.begin(), values.end(), ConfigKey{&config, product},
lt_config_key_ref);
lt_config_key_ref<std::unique_ptr<ResourceConfigValue>>);
if (iter != values.end()) {
ResourceConfigValue* value = iter->get();
if (value->config == config && StringPiece(value->product) == product) {
@ -296,6 +297,7 @@ ResourceTable::CollisionResult ResourceTable::ResolveValueCollision(Value* exist
return CollisionResult::kConflict;
}
namespace {
template <typename T, typename Comparer>
struct SortedVectorInserter : public Comparer {
std::pair<bool, typename std::vector<T>::iterator> LowerBound(std::vector<T>& el,
@ -313,7 +315,7 @@ struct SortedVectorInserter : public Comparer {
if (found) {
return &*it;
}
return &*el.insert(it, std::move(value));
return &*el.insert(it, std::forward<T>(value));
}
};
@ -331,35 +333,77 @@ struct TypeViewComparer {
};
struct EntryViewComparer {
bool operator()(const ResourceEntry* lhs, const ResourceEntry* rhs) {
return less_than_struct_with_name_and_id<ResourceEntry, ResourceId>(
*lhs, std::make_pair(rhs->name, rhs->id));
bool operator()(const ResourceTableEntryView& lhs, const ResourceTableEntryView& rhs) {
return less_than_struct_with_name_and_id<ResourceTableEntryView, uint16_t>(
lhs, std::make_pair(rhs.name, rhs.id));
}
};
ResourceTableView ResourceTable::GetPartitionedView() const {
ResourceTableView view;
void InsertEntryIntoTableView(ResourceTableView& table, const ResourceTablePackage* package,
const ResourceTableType* type, const std::string& entry_name,
const Maybe<ResourceId>& id, const Visibility& visibility,
const Maybe<AllowNew>& allow_new,
const Maybe<OverlayableItem>& overlayable_item,
const Maybe<StagedId>& staged_id,
const std::vector<std::unique_ptr<ResourceConfigValue>>& values) {
SortedVectorInserter<ResourceTablePackageView, PackageViewComparer> package_inserter;
SortedVectorInserter<ResourceTableTypeView, TypeViewComparer> type_inserter;
SortedVectorInserter<const ResourceEntry*, EntryViewComparer> entry_inserter;
SortedVectorInserter<ResourceTableEntryView, EntryViewComparer> entry_inserter;
ResourceTablePackageView new_package{package->name,
id ? id.value().package_id() : Maybe<uint8_t>{}};
auto view_package = package_inserter.Insert(table.packages, std::move(new_package));
ResourceTableTypeView new_type{type->type, id ? id.value().type_id() : Maybe<uint8_t>{}};
auto view_type = type_inserter.Insert(view_package->types, std::move(new_type));
if (visibility.level == Visibility::Level::kPublic) {
// Only mark the type visibility level as public, it doesn't care about being private.
view_type->visibility_level = Visibility::Level::kPublic;
}
ResourceTableEntryView new_entry{.name = entry_name,
.id = id ? id.value().entry_id() : Maybe<uint16_t>{},
.visibility = visibility,
.allow_new = allow_new,
.overlayable_item = overlayable_item,
.staged_id = staged_id};
for (auto& value : values) {
new_entry.values.emplace_back(value.get());
}
entry_inserter.Insert(view_type->entries, std::move(new_entry));
}
} // namespace
const ResourceConfigValue* ResourceTableEntryView::FindValue(const ConfigDescription& config,
android::StringPiece product) const {
auto iter = std::lower_bound(values.begin(), values.end(), ConfigKey{&config, product},
lt_config_key_ref<const ResourceConfigValue*>);
if (iter != values.end()) {
const ResourceConfigValue* value = *iter;
if (value->config == config && StringPiece(value->product) == product) {
return value;
}
}
return nullptr;
}
ResourceTableView ResourceTable::GetPartitionedView(const ResourceTableViewOptions& options) const {
ResourceTableView view;
for (const auto& package : packages) {
for (const auto& type : package->types) {
for (const auto& entry : type->entries) {
ResourceTablePackageView new_package{
package->name, entry->id ? entry->id.value().package_id() : Maybe<uint8_t>{}};
auto view_package = package_inserter.Insert(view.packages, std::move(new_package));
InsertEntryIntoTableView(view, package.get(), type.get(), entry->name, entry->id,
entry->visibility, entry->allow_new, entry->overlayable_item,
entry->staged_id, entry->values);
ResourceTableTypeView new_type{type->type,
entry->id ? entry->id.value().type_id() : Maybe<uint8_t>{}};
auto view_type = type_inserter.Insert(view_package->types, std::move(new_type));
if (entry->visibility.level == Visibility::Level::kPublic) {
// Only mark the type visibility level as public, it doesn't care about being private.
view_type->visibility_level = Visibility::Level::kPublic;
if (options.create_alias_entries && entry->staged_id) {
auto alias_id = entry->staged_id.value().id;
InsertEntryIntoTableView(view, package.get(), type.get(), entry->name, alias_id,
entry->visibility, entry->allow_new, entry->overlayable_item, {},
entry->values);
}
entry_inserter.Insert(view_type->entries, entry.get());
}
}
}
@ -368,6 +412,8 @@ ResourceTableView ResourceTable::GetPartitionedView() const {
// for the same resource type within the same package. For this reason, if there are types with
// multiple type ids, each type needs to exist in its own package in order to be queried by name.
std::vector<ResourceTablePackageView> new_packages;
SortedVectorInserter<ResourceTablePackageView, PackageViewComparer> package_inserter;
SortedVectorInserter<ResourceTableTypeView, TypeViewComparer> type_inserter;
for (auto& package : view.packages) {
// If a new package was already created for a different type within this package, then
// we can reuse those packages for other types that need to be extracted from this package.
@ -498,6 +544,10 @@ bool ResourceTable::AddResource(NewResource&& res, IDiagnostics* diag) {
entry->allow_new = res.allow_new.value();
}
if (res.staged_id.has_value()) {
entry->staged_id = res.staged_id.value();
}
if (res.value != nullptr) {
auto config_value = entry->FindOrCreateValue(res.config, res.product);
if (!config_value->value) {
@ -575,6 +625,28 @@ Maybe<ResourceTable::SearchResult> ResourceTable::FindResource(const ResourceNam
return {};
}
bool ResourceTable::RemoveResource(const ResourceNameRef& name, ResourceId id) const {
ResourceTablePackage* package = FindPackage(name.package);
if (package == nullptr) {
return {};
}
ResourceTableType* type = package->FindType(name.type);
if (type == nullptr) {
return {};
}
auto entry_it = std::equal_range(type->entries.begin(), type->entries.end(), name.entry,
NameEqualRange<ResourceEntry>{});
for (auto it = entry_it.first; it != entry_it.second; ++it) {
if ((*it)->id == id) {
type->entries.erase(it);
return true;
}
}
return false;
}
std::unique_ptr<ResourceTable> ResourceTable::Clone() const {
std::unique_ptr<ResourceTable> new_table = util::make_unique<ResourceTable>();
CloningValueTransformer cloner(&new_table->string_pool);
@ -640,6 +712,11 @@ NewResourceBuilder& NewResourceBuilder::SetAllowNew(AllowNew allow_new) {
return *this;
}
NewResourceBuilder& NewResourceBuilder::SetStagedId(StagedId staged_alias) {
res_.staged_id = std::move(staged_alias);
return *this;
}
NewResourceBuilder& NewResourceBuilder::SetAllowMangled(bool allow_mangled) {
res_.allow_mangled = allow_mangled;
return *this;

View File

@ -64,6 +64,12 @@ struct AllowNew {
std::string comment;
};
// Represents the staged resource id of a finalized resource.
struct StagedId {
ResourceId id;
Source source;
};
struct Overlayable {
Overlayable() = default;
Overlayable(const android::StringPiece& name, const android::StringPiece& actor)
@ -124,6 +130,9 @@ class ResourceEntry {
// The declarations of this resource as overlayable for RROs
Maybe<OverlayableItem> overlayable_item;
// The staged resource id for a finalized resource.
Maybe<StagedId> staged_id;
// The resource's values for each configuration.
std::vector<std::unique_ptr<ResourceConfigValue>> values;
@ -194,14 +203,27 @@ class ResourceTablePackage {
DISALLOW_COPY_AND_ASSIGN(ResourceTablePackage);
};
struct ResourceTableEntryView {
std::string name;
Maybe<uint16_t> id;
Visibility visibility;
Maybe<AllowNew> allow_new;
Maybe<OverlayableItem> overlayable_item;
Maybe<StagedId> staged_id;
std::vector<const ResourceConfigValue*> values;
const ResourceConfigValue* FindValue(const android::ConfigDescription& config,
android::StringPiece product = {}) const;
};
struct ResourceTableTypeView {
ResourceType type;
Maybe<uint8_t> id;
Visibility::Level visibility_level = Visibility::Level::kUndefined;
// Entries sorted in ascending entry id order. If ids have not been assigned, the entries are
// // sorted lexicographically.
std::vector<const ResourceEntry*> entries;
// sorted lexicographically.
std::vector<ResourceTableEntryView> entries;
};
struct ResourceTablePackageView {
@ -212,6 +234,10 @@ struct ResourceTablePackageView {
std::vector<ResourceTableTypeView> types;
};
struct ResourceTableViewOptions {
bool create_alias_entries = false;
};
struct ResourceTableView {
// Packages sorted in ascending package id order. If ids have not been assigned, the packages are
// sorted lexicographically.
@ -237,6 +263,7 @@ struct NewResource {
std::optional<Visibility> visibility;
std::optional<OverlayableItem> overlayable;
std::optional<AllowNew> allow_new;
std::optional<StagedId> staged_id;
bool allow_mangled = false;
};
@ -249,6 +276,7 @@ struct NewResourceBuilder {
NewResourceBuilder& SetVisibility(Visibility id);
NewResourceBuilder& SetOverlayable(OverlayableItem overlayable);
NewResourceBuilder& SetAllowNew(AllowNew allow_new);
NewResourceBuilder& SetStagedId(StagedId id);
NewResourceBuilder& SetAllowMangled(bool allow_mangled);
NewResource Build();
@ -273,7 +301,7 @@ class ResourceTable {
// Retrieves a sorted a view of the packages, types, and entries sorted in ascending resource id
// order.
ResourceTableView GetPartitionedView() const;
ResourceTableView GetPartitionedView(const ResourceTableViewOptions& options = {}) const;
struct SearchResult {
ResourceTablePackage* package;
@ -283,6 +311,7 @@ class ResourceTable {
Maybe<SearchResult> FindResource(const ResourceNameRef& name) const;
Maybe<SearchResult> FindResource(const ResourceNameRef& name, ResourceId id) const;
bool RemoveResource(const ResourceNameRef& name, ResourceId id) const;
// Returns the package struct with the given name, or nullptr if such a package does not
// exist. The empty string is a valid package and typically is used to represent the

View File

@ -190,6 +190,12 @@ message OverlayableItem {
uint32 overlayable_idx = 4;
}
// The staged resource ID definition of a finalized resource.
message StagedId {
Source source = 1;
uint32 staged_id = 2;
}
// An entry ID in the range [0x0000, 0xffff].
message EntryId {
uint32 id = 1;
@ -222,6 +228,9 @@ message Entry {
// The set of values defined for this entry, each corresponding to a different
// configuration/variant.
repeated ConfigValue config_value = 6;
// The staged resource ID of this finalized resource.
StagedId staged_id = 7;
}
// A Configuration/Value pair.

View File

@ -97,15 +97,15 @@ static bool IsIdDiff(const Visibility::Level& level_a, const Maybe<Id>& id_a,
static bool EmitResourceConfigValueDiff(
IAaptContext* context, LoadedApk* apk_a, const ResourceTablePackageView& pkg_a,
const ResourceTableTypeView& type_a, const ResourceEntry* entry_a,
const ResourceTableTypeView& type_a, const ResourceTableEntryView& entry_a,
const ResourceConfigValue* config_value_a, LoadedApk* apk_b,
const ResourceTablePackageView& pkg_b, const ResourceTableTypeView& type_b,
const ResourceEntry* entry_b, const ResourceConfigValue* config_value_b) {
const ResourceTableEntryView& entry_b, const ResourceConfigValue* config_value_b) {
Value* value_a = config_value_a->value.get();
Value* value_b = config_value_b->value.get();
if (!value_a->Equals(value_b)) {
std::stringstream str_stream;
str_stream << "value " << pkg_a.name << ":" << type_a.type << "/" << entry_a->name
str_stream << "value " << pkg_a.name << ":" << type_a.type << "/" << entry_a.name
<< " config=" << config_value_a->config << " does not match:\n";
value_a->Print(&str_stream);
str_stream << "\n vs \n";
@ -118,32 +118,32 @@ static bool EmitResourceConfigValueDiff(
static bool EmitResourceEntryDiff(IAaptContext* context, LoadedApk* apk_a,
const ResourceTablePackageView& pkg_a,
const ResourceTableTypeView& type_a, const ResourceEntry* entry_a,
LoadedApk* apk_b, const ResourceTablePackageView& pkg_b,
const ResourceTableTypeView& type_a,
const ResourceTableEntryView& entry_a, LoadedApk* apk_b,
const ResourceTablePackageView& pkg_b,
const ResourceTableTypeView& type_b,
const ResourceEntry* entry_b) {
const ResourceTableEntryView& entry_b) {
bool diff = false;
for (const std::unique_ptr<ResourceConfigValue>& config_value_a : entry_a->values) {
auto config_value_b = entry_b->FindValue(config_value_a->config);
for (const ResourceConfigValue* config_value_a : entry_a.values) {
auto config_value_b = entry_b.FindValue(config_value_a->config);
if (!config_value_b) {
std::stringstream str_stream;
str_stream << "missing " << pkg_a.name << ":" << type_a.type << "/" << entry_a->name
str_stream << "missing " << pkg_a.name << ":" << type_a.type << "/" << entry_a.name
<< " config=" << config_value_a->config;
EmitDiffLine(apk_b->GetSource(), str_stream.str());
diff = true;
} else {
diff |=
EmitResourceConfigValueDiff(context, apk_a, pkg_a, type_a, entry_a, config_value_a.get(),
apk_b, pkg_b, type_b, entry_b, config_value_b);
diff |= EmitResourceConfigValueDiff(context, apk_a, pkg_a, type_a, entry_a, config_value_a,
apk_b, pkg_b, type_b, entry_b, config_value_b);
}
}
// Check for any newly added config values.
for (const std::unique_ptr<ResourceConfigValue>& config_value_b : entry_b->values) {
auto config_value_a = entry_a->FindValue(config_value_b->config);
for (const ResourceConfigValue* config_value_b : entry_b.values) {
auto config_value_a = entry_a.FindValue(config_value_b->config);
if (!config_value_a) {
std::stringstream str_stream;
str_stream << "new config " << pkg_b.name << ":" << type_b.type << "/" << entry_b->name
str_stream << "new config " << pkg_b.name << ":" << type_b.type << "/" << entry_b.name
<< " config=" << config_value_b->config;
EmitDiffLine(apk_b->GetSource(), str_stream.str());
diff = true;
@ -164,36 +164,35 @@ static bool EmitResourceTypeDiff(IAaptContext* context, LoadedApk* apk_a,
if (entry_b_iter == type_b.entries.end()) {
// Type A contains a type that type B does not have.
std::stringstream str_stream;
str_stream << "missing " << pkg_a.name << ":" << type_a.type << "/" << (*entry_a_iter)->name;
str_stream << "missing " << pkg_a.name << ":" << type_a.type << "/" << entry_a_iter->name;
EmitDiffLine(apk_a->GetSource(), str_stream.str());
diff = true;
} else if (entry_a_iter == type_a.entries.end()) {
// Type B contains a type that type A does not have.
std::stringstream str_stream;
str_stream << "new entry " << pkg_b.name << ":" << type_b.type << "/"
<< (*entry_b_iter)->name;
str_stream << "new entry " << pkg_b.name << ":" << type_b.type << "/" << entry_b_iter->name;
EmitDiffLine(apk_b->GetSource(), str_stream.str());
diff = true;
} else {
const auto& entry_a = *entry_a_iter;
const auto& entry_b = *entry_b_iter;
if (IsSymbolVisibilityDifferent(entry_a->visibility, entry_b->visibility)) {
if (IsSymbolVisibilityDifferent(entry_a.visibility, entry_b.visibility)) {
std::stringstream str_stream;
str_stream << pkg_a.name << ":" << type_a.type << "/" << entry_a->name
str_stream << pkg_a.name << ":" << type_a.type << "/" << entry_a.name
<< " has different visibility (";
if (entry_b->visibility.staged_api) {
if (entry_b.visibility.staged_api) {
str_stream << "STAGED ";
}
if (entry_b->visibility.level == Visibility::Level::kPublic) {
if (entry_b.visibility.level == Visibility::Level::kPublic) {
str_stream << "PUBLIC";
} else {
str_stream << "PRIVATE";
}
str_stream << " vs ";
if (entry_a->visibility.staged_api) {
if (entry_a.visibility.staged_api) {
str_stream << "STAGED ";
}
if (entry_a->visibility.level == Visibility::Level::kPublic) {
if (entry_a.visibility.level == Visibility::Level::kPublic) {
str_stream << "PUBLIC";
} else {
str_stream << "PRIVATE";
@ -201,19 +200,19 @@ static bool EmitResourceTypeDiff(IAaptContext* context, LoadedApk* apk_a,
str_stream << ")";
EmitDiffLine(apk_b->GetSource(), str_stream.str());
diff = true;
} else if (IsIdDiff(entry_a->visibility.level, entry_a->id, entry_b->visibility.level,
entry_b->id)) {
} else if (IsIdDiff(entry_a.visibility.level, entry_a.id, entry_b.visibility.level,
entry_b.id)) {
std::stringstream str_stream;
str_stream << pkg_a.name << ":" << type_a.type << "/" << entry_a->name
str_stream << pkg_a.name << ":" << type_a.type << "/" << entry_a.name
<< " has different public ID (";
if (entry_b->id) {
str_stream << "0x" << std::hex << entry_b->id.value();
if (entry_b.id) {
str_stream << "0x" << std::hex << entry_b.id.value();
} else {
str_stream << "none";
}
str_stream << " vs ";
if (entry_a->id) {
str_stream << "0x " << std::hex << entry_a->id.value();
if (entry_a.id) {
str_stream << "0x " << std::hex << entry_a.id.value();
} else {
str_stream << "none";
}

View File

@ -402,8 +402,39 @@ TEST_F(LinkTest, SharedLibraryAttributeRJava) {
EXPECT_THAT(client_r_contents, HasSubstr(" com.example.lib.R.attr.foo, 0x7f010000"));
}
TEST_F(LinkTest, StagedAndroidApi) {
StdErrDiagnostics diag;
struct SourceXML {
std::string res_file_path;
std::string file_contents;
};
static void BuildApk(const std::vector<SourceXML>& source_files, const std::string& apk_path,
LinkCommandBuilder&& link_args, CommandTestFixture* fixture,
IDiagnostics* diag) {
TemporaryDir res_dir;
TemporaryDir compiled_res_dir;
for (auto& source_file : source_files) {
ASSERT_TRUE(fixture->CompileFile(res_dir.path + source_file.res_file_path,
source_file.file_contents, compiled_res_dir.path, diag));
}
ASSERT_TRUE(fixture->Link(
link_args.AddCompiledResDir(compiled_res_dir.path, diag).Build(apk_path), diag));
}
static void BuildSDK(const std::vector<SourceXML>& source_files, const std::string& apk_path,
const std::string& java_root_path, CommandTestFixture* fixture,
IDiagnostics* diag) {
auto android_manifest = ManifestBuilder(fixture).SetPackageName("android").Build();
auto android_link_args = LinkCommandBuilder(fixture)
.SetManifestFile(android_manifest)
.AddParameter("--private-symbols", "com.android.internal")
.AddParameter("--java", java_root_path);
BuildApk(source_files, apk_path, std::move(android_link_args), fixture, diag);
}
static void BuildNonFinalizedSDK(const std::string& apk_path, const std::string& java_path,
CommandTestFixture* fixture, IDiagnostics* diag) {
const std::string android_values =
R"(<resources>
<public type="attr" name="finalized_res" id="0x01010001"/>
@ -413,6 +444,10 @@ TEST_F(LinkTest, StagedAndroidApi) {
<public name="staged_s_res" />
</staging-public-group>
<staging-public-group type="string" first-id="0x01fd0080">
<public name="staged_s_string" />
</staging-public-group>
<!-- SV2 staged attributes (support staged resources in a separate type id) -->
<staging-public-group type="attr" first-id="0x01ff0049">
<public name="staged_s2_res" />
@ -423,46 +458,90 @@ TEST_F(LinkTest, StagedAndroidApi) {
<public name="staged_t_res" />
</staging-public-group>
<staging-public-group type="string" first-id="0x01fd0072">
<public name="staged_t_string" />
<attr name="finalized_res" />
<attr name="staged_s_res" />
<attr name="staged_s2_res" />
<attr name="staged_t_res" />
<string name="staged_s_string">Hello</string>
</resources>)";
SourceXML source_xml{.res_file_path = "/res/values/values.xml", .file_contents = android_values};
BuildSDK({source_xml}, apk_path, java_path, fixture, diag);
}
static void BuildFinalizedSDK(const std::string& apk_path, const std::string& java_path,
CommandTestFixture* fixture, IDiagnostics* diag) {
const std::string android_values =
R"(<resources>
<public type="attr" name="finalized_res" id="0x01010001"/>
<public type="attr" name="staged_s_res" id="0x01010002"/>
<public type="attr" name="staged_s2_res" id="0x01010003"/>
<public type="string" name="staged_s_string" id="0x01020000"/>
<!-- S staged attributes (support staged resources in the same type id) -->
<staging-public-group-final type="attr" first-id="0x01010050">
<public name="staged_s_res" />
</staging-public-group-final>
<staging-public-group-final type="string" first-id="0x01fd0080">
<public name="staged_s_string" />
</staging-public-group-final>
<!-- SV2 staged attributes (support staged resources in a separate type id) -->
<staging-public-group-final type="attr" first-id="0x01ff0049">
<public name="staged_s2_res" />
</staging-public-group-final>
<!-- T staged attributes (support staged resources in multiple separate type ids) -->
<staging-public-group type="attr" first-id="0x01fe0063">
<public name="staged_t_res" />
</staging-public-group>
<attr name="finalized_res" />
<attr name="staged_s_res" />
<attr name="staged_s2_res" />
<attr name="staged_t_res" />
<string name="staged_t_string">Hello</string>
<string name="staged_s_string">Hello</string>
</resources>)";
SourceXML source_xml{.res_file_path = "/res/values/values.xml", .file_contents = android_values};
BuildSDK({source_xml}, apk_path, java_path, fixture, diag);
}
static void BuildAppAgainstSDK(const std::string& apk_path, const std::string& java_path,
const std::string& sdk_path, CommandTestFixture* fixture,
IDiagnostics* diag) {
const std::string app_values =
R"(<resources xmlns:android="http://schemas.android.com/apk/res/android">
<attr name="bar" />
<style name="MyStyle">
<item name="android:staged_s_res">@android:string/staged_s_string</item>
</style>
<declare-styleable name="ClientStyleable">
<attr name="android:finalized_res" />
<attr name="android:staged_s_res" />
<attr name="bar" />
</declare-styleable>
<public name="MyStyle" type="style" id="0x7f020000" />
</resources>)";
const std::string android_res = GetTestPath("android-res");
ASSERT_TRUE(
CompileFile(GetTestPath("res/values/values.xml"), android_values, android_res, &diag));
SourceXML source_xml{.res_file_path = "/res/values/values.xml", .file_contents = app_values};
auto app_manifest = ManifestBuilder(fixture).SetPackageName("com.example.app").Build();
auto app_link_args = LinkCommandBuilder(fixture)
.SetManifestFile(app_manifest)
.AddParameter("--java", java_path)
.AddParameter("-I", sdk_path);
BuildApk({source_xml}, apk_path, std::move(app_link_args), fixture, diag);
}
TEST_F(LinkTest, StagedAndroidApi) {
StdErrDiagnostics diag;
const std::string android_apk = GetTestPath("android.apk");
const std::string android_java = GetTestPath("android_java");
// clang-format off
auto android_manifest = ManifestBuilder(this)
.SetPackageName("android")
.Build();
auto android_link_args = LinkCommandBuilder(this)
.SetManifestFile(android_manifest)
.AddParameter("--private-symbols", "com.android.internal")
.AddParameter("--java", android_java)
.AddCompiledResDir(android_res, &diag)
.Build(android_apk);
// clang-format on
ASSERT_TRUE(Link(android_link_args, &diag));
const std::string android_java = GetTestPath("android-java");
BuildNonFinalizedSDK(android_apk, android_java, this, &diag);
const std::string android_r_java = android_java + "/android/R.java";
std::string android_r_contents;
@ -471,35 +550,19 @@ TEST_F(LinkTest, StagedAndroidApi) {
EXPECT_THAT(
android_r_contents,
HasSubstr("public static final int staged_s_res; static { staged_s_res=0x01010050; }"));
EXPECT_THAT(
android_r_contents,
HasSubstr("public static final int staged_s_string; static { staged_s_string=0x01fd0080; }"));
EXPECT_THAT(
android_r_contents,
HasSubstr("public static final int staged_s2_res; static { staged_s2_res=0x01ff0049; }"));
EXPECT_THAT(
android_r_contents,
HasSubstr("public static final int staged_t_res; static { staged_t_res=0x01fe0063; }"));
EXPECT_THAT(
android_r_contents,
HasSubstr("public static final int staged_t_string; static { staged_t_string=0x01fd0072; }"));
// Build an app that uses the framework attribute in a declare-styleable
const std::string client_res = GetTestPath("app-res");
ASSERT_TRUE(CompileFile(GetTestPath("res/values/values.xml"), app_values, client_res, &diag));
const std::string app_apk = GetTestPath("app.apk");
const std::string app_java = GetTestPath("app_java");
// clang-format off
auto app_manifest = ManifestBuilder(this)
.SetPackageName("com.example.app")
.Build();
auto app_link_args = LinkCommandBuilder(this)
.SetManifestFile(app_manifest)
.AddParameter("--java", app_java)
.AddParameter("-I", android_apk)
.AddCompiledResDir(client_res, &diag)
.Build(app_apk);
// clang-format on
ASSERT_TRUE(Link(app_link_args, &diag));
const std::string app_java = GetTestPath("app-java");
BuildAppAgainstSDK(app_apk, app_java, android_apk, this, &diag);
const std::string client_r_java = app_java + "/com/example/app/R.java";
std::string client_r_contents;
@ -520,6 +583,10 @@ TEST_F(LinkTest, StagedAndroidApi) {
ASSERT_TRUE(result.has_value());
EXPECT_THAT(*result, Eq(0x01010050));
result = am.GetResourceId("android:string/staged_s_string");
ASSERT_TRUE(result.has_value());
EXPECT_THAT(*result, Eq(0x01fd0080));
result = am.GetResourceId("android:attr/staged_s2_res");
ASSERT_TRUE(result.has_value());
EXPECT_THAT(*result, Eq(0x01ff0049));
@ -527,10 +594,88 @@ TEST_F(LinkTest, StagedAndroidApi) {
result = am.GetResourceId("android:attr/staged_t_res");
ASSERT_TRUE(result.has_value());
EXPECT_THAT(*result, Eq(0x01fe0063));
}
result = am.GetResourceId("android:string/staged_t_string");
TEST_F(LinkTest, FinalizedAndroidApi) {
StdErrDiagnostics diag;
const std::string android_apk = GetTestPath("android.apk");
const std::string android_java = GetTestPath("android-java");
BuildFinalizedSDK(android_apk, android_java, this, &diag);
const std::string android_r_java = android_java + "/android/R.java";
std::string android_r_contents;
ASSERT_TRUE(android::base::ReadFileToString(android_r_java, &android_r_contents));
EXPECT_THAT(android_r_contents, HasSubstr("public static final int finalized_res=0x01010001;"));
EXPECT_THAT(android_r_contents, HasSubstr("public static final int staged_s_res=0x01010002;"));
EXPECT_THAT(android_r_contents, HasSubstr("public static final int staged_s_string=0x01020000;"));
EXPECT_THAT(android_r_contents, HasSubstr("public static final int staged_s2_res=0x01010003;"));
EXPECT_THAT(
android_r_contents,
HasSubstr("public static final int staged_t_res; static { staged_t_res=0x01fe0063; }"));
;
// Build an application against the non-finalized SDK and then load it into an AssetManager with
// the finalized SDK.
const std::string non_finalized_android_apk = GetTestPath("non-finalized-android.apk");
const std::string non_finalized_android_java = GetTestPath("non-finalized-android-java");
BuildNonFinalizedSDK(non_finalized_android_apk, non_finalized_android_java, this, &diag);
const std::string app_apk = GetTestPath("app.apk");
const std::string app_java = GetTestPath("app-java");
BuildAppAgainstSDK(app_apk, app_java, non_finalized_android_apk, this, &diag);
android::AssetManager2 am;
auto android_asset = android::ApkAssets::Load(android_apk);
auto app_against_non_final = android::ApkAssets::Load(app_apk);
ASSERT_THAT(android_asset, NotNull());
ASSERT_THAT(app_against_non_final, NotNull());
ASSERT_TRUE(am.SetApkAssets({android_asset.get(), app_against_non_final.get()}));
auto result = am.GetResourceId("android:attr/finalized_res");
ASSERT_TRUE(result.has_value());
EXPECT_THAT(*result, Eq(0x01fd0072));
EXPECT_THAT(*result, Eq(0x01010001));
result = am.GetResourceId("android:attr/staged_s_res");
ASSERT_TRUE(result.has_value());
EXPECT_THAT(*result, Eq(0x01010002));
result = am.GetResourceId("android:string/staged_s_string");
ASSERT_TRUE(result.has_value());
EXPECT_THAT(*result, Eq(0x01020000));
result = am.GetResourceId("android:attr/staged_s2_res");
ASSERT_TRUE(result.has_value());
EXPECT_THAT(*result, Eq(0x01010003));
{
auto style = am.GetBag(0x7f020000);
ASSERT_TRUE(style.has_value());
auto& entry = (*style)->entries[0];
EXPECT_THAT(entry.key, Eq(0x01010002));
EXPECT_THAT(entry.value.dataType, Eq(android::Res_value::TYPE_REFERENCE));
EXPECT_THAT(entry.value.data, Eq(0x01020000));
}
// Re-compile the application against the finalized SDK and then load it into an AssetManager with
// the finalized SDK.
const std::string app_apk_respin = GetTestPath("app-respin.apk");
const std::string app_java_respin = GetTestPath("app-respin-java");
BuildAppAgainstSDK(app_apk_respin, app_java_respin, android_apk, this, &diag);
auto app_against_final = android::ApkAssets::Load(app_apk_respin);
ASSERT_THAT(app_against_final, NotNull());
ASSERT_TRUE(am.SetApkAssets({android_asset.get(), app_against_final.get()}));
{
auto style = am.GetBag(0x7f020000);
ASSERT_TRUE(style.has_value());
auto& entry = (*style)->entries[0];
EXPECT_THAT(entry.key, Eq(0x01010002));
EXPECT_THAT(entry.value.dataType, Eq(android::Res_value::TYPE_REFERENCE));
EXPECT_THAT(entry.value.data, Eq(0x01020000));
}
}
TEST_F(LinkTest, MacroSubstitution) {

View File

@ -129,11 +129,16 @@ bool IdAssigner::Consume(IAaptContext* context, ResourceTable* table) {
for (auto& type : package->types) {
for (auto& entry : type->entries) {
const ResourceName name(package->name, type->type, entry->name);
if (entry->id) {
if (!assigned_ids.ReserveId(name, entry->id.value(), entry->visibility,
context->GetDiagnostics())) {
return false;
}
if (entry->id && !assigned_ids.ReserveId(name, entry->id.value(), entry->visibility,
context->GetDiagnostics())) {
return false;
}
auto v = entry->visibility;
v.staged_api = true;
if (entry->staged_id && !assigned_ids.ReserveId(name, entry->staged_id.value().id, v,
context->GetDiagnostics())) {
return false;
}
if (assigned_id_map_) {
@ -237,7 +242,7 @@ Result<std::monostate> TypeGroup::ReserveId(const ResourceName& name, ResourceId
if (type_id_ != id.type_id()) {
// Currently there cannot be multiple type ids for a single type.
std::stringstream error;
error << "type '" << name.type << "' already has ID " << std::hex << (int)id.type_id();
error << "type '" << name.type << "' already has ID " << std::hex << (int)type_id_;
return unexpected(error.str());
}

View File

@ -254,6 +254,12 @@ bool BinaryResourceParser::ParsePackage(const ResChunk_header* chunk) {
}
break;
case android::RES_TABLE_STAGED_ALIAS_TYPE:
if (!ParseOverlayable(parser.chunk())) {
return false;
}
break;
default:
diag_->Warn(DiagMessage(source_)
<< "unexpected chunk type "
@ -489,6 +495,52 @@ bool BinaryResourceParser::ParseOverlayable(const ResChunk_header* chunk) {
return true;
}
bool BinaryResourceParser::ParseStagedAliases(const ResChunk_header* chunk) {
auto header = ConvertTo<ResTable_staged_alias_header>(chunk);
if (!header) {
diag_->Error(DiagMessage(source_) << "corrupt ResTable_staged_alias_header chunk");
return false;
}
const auto ref_begin = reinterpret_cast<const ResTable_staged_alias_entry*>(
((uint8_t*)header) + util::DeviceToHost32(header->header.headerSize));
const auto ref_end = ref_begin + util::DeviceToHost32(header->count);
for (auto ref_iter = ref_begin; ref_iter != ref_end; ++ref_iter) {
const auto staged_id = ResourceId(util::DeviceToHost32(ref_iter->stagedResId));
const auto finalized_id = ResourceId(util::DeviceToHost32(ref_iter->finalizedResId));
// If the staged alias chunk comes before the type chunks, the resource ids and resource name
// pairing will not exist at this point.
const auto iter = id_index_.find(finalized_id);
if (iter == id_index_.cend()) {
diag_->Error(DiagMessage(source_) << "failed to find resource name for finalized"
<< " resource ID " << finalized_id);
return false;
}
// Set the staged if of the finalized resource.
const auto& resource_name = iter->second;
const StagedId staged_id_def{.id = staged_id};
if (!table_->AddResource(NewResourceBuilder(resource_name)
.SetId(finalized_id, OnIdConflict::CREATE_ENTRY)
.SetStagedId(staged_id_def)
.SetAllowMangled(true)
.Build(),
diag_)) {
return false;
}
// Since a the finalized resource entry is cloned and added to the resource table under the
// staged resource id, remove the cloned resource entry from the table.
if (!table_->RemoveResource(resource_name, staged_id)) {
diag_->Error(DiagMessage(source_) << "failed to find resource entry for staged "
<< " resource ID " << staged_id);
return false;
}
}
return true;
}
std::unique_ptr<Item> BinaryResourceParser::ParseValue(const ResourceNameRef& name,
const ConfigDescription& config,
const android::Res_value& value) {

View File

@ -57,6 +57,7 @@ class BinaryResourceParser {
uint8_t package_id);
bool ParseLibrary(const android::ResChunk_header* chunk);
bool ParseOverlayable(const android::ResChunk_header* chunk);
bool ParseStagedAliases(const android::ResChunk_header* chunk);
std::unique_ptr<Item> ParseValue(const ResourceNameRef& name,
const android::ConfigDescription& config,

View File

@ -72,7 +72,7 @@ static bool cmp_style_entries(const Style::Entry* a, const Style::Entry* b) {
}
struct FlatEntry {
const ResourceEntry* entry;
const ResourceTableEntryView* entry;
const Value* value;
// The entry string pool index to the entry's name.
@ -286,6 +286,10 @@ class PackageFlattener {
return false;
}
if (!FlattenAliases(buffer)) {
return false;
}
pkg_writer.Finish();
return true;
}
@ -351,8 +355,8 @@ class PackageFlattener {
BigBuffer values_buffer(512);
for (FlatEntry& flat_entry : *entries) {
CHECK(static_cast<size_t>(flat_entry.entry->id.value().entry_id()) < num_total_entries);
offsets[flat_entry.entry->id.value().entry_id()] = values_buffer.size();
CHECK(static_cast<size_t>(flat_entry.entry->id.value()) < num_total_entries);
offsets[flat_entry.entry->id.value()] = values_buffer.size();
if (!FlattenValue(&flat_entry, &values_buffer)) {
diag_->Error(DiagMessage()
<< "failed to flatten resource '"
@ -404,6 +408,26 @@ class PackageFlattener {
return true;
}
bool FlattenAliases(BigBuffer* buffer) {
if (aliases_.empty()) {
return true;
}
ChunkWriter alias_writer(buffer);
auto header =
alias_writer.StartChunk<ResTable_staged_alias_header>(RES_TABLE_STAGED_ALIAS_TYPE);
header->count = util::HostToDevice32(aliases_.size());
auto mapping = alias_writer.NextBlock<ResTable_staged_alias_entry>(aliases_.size());
for (auto& p : aliases_) {
mapping->stagedResId = util::HostToDevice32(p.first);
mapping->finalizedResId = util::HostToDevice32(p.second);
++mapping;
}
alias_writer.Finish();
return true;
}
bool FlattenOverlayable(BigBuffer* buffer) {
std::set<ResourceId> seen_ids;
std::map<std::string, OverlayableChunk> overlayable_chunks;
@ -413,18 +437,17 @@ class PackageFlattener {
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>";
if (!entry->overlayable_item) {
if (!entry.overlayable_item) {
continue;
}
const OverlayableItem& item = entry->overlayable_item.value();
const OverlayableItem& item = entry.overlayable_item.value();
// Resource ids should only appear once in the resource table
ResourceId id =
android::make_resid(package_.id.value(), type.id.value(), entry->id.value().entry_id());
ResourceId id = android::make_resid(package_.id.value(), type.id.value(), entry.id.value());
CHECK(seen_ids.find(id) == seen_ids.end())
<< "multiple overlayable definitions found for resource "
<< ResourceName(package_.name, type.type, entry->name).to_string();
<< ResourceName(package_.name, type.type, entry.name).to_string();
seen_ids.insert(id);
// Find the overlayable chunk with the specified name
@ -452,9 +475,8 @@ class PackageFlattener {
if (item.policies == 0) {
context_->GetDiagnostics()->Error(DiagMessage(item.overlayable->source)
<< "overlayable "
<< entry->name
<< " does not specify policy");
<< "overlayable " << entry.name
<< " does not specify policy");
return false;
}
@ -520,7 +542,8 @@ class PackageFlattener {
}
bool FlattenTypeSpec(const ResourceTableTypeView& type,
const std::vector<const ResourceEntry*>& sorted_entries, BigBuffer* buffer) {
const std::vector<ResourceTableEntryView>& sorted_entries,
BigBuffer* buffer) {
ChunkWriter type_spec_writer(buffer);
ResTable_typeSpec* spec_header =
type_spec_writer.StartChunk<ResTable_typeSpec>(RES_TABLE_TYPE_SPEC_TYPE);
@ -534,7 +557,7 @@ class PackageFlattener {
// We can't just take the size of the vector. There may be holes in the
// entry ID space.
// Since the entries are sorted by ID, the last one will be the biggest.
const size_t num_entries = sorted_entries.back()->id.value().entry_id() + 1;
const size_t num_entries = sorted_entries.back().id.value() + 1;
spec_header->entryCount = util::HostToDevice32(num_entries);
@ -542,23 +565,23 @@ class PackageFlattener {
// show for which configuration axis the resource changes.
uint32_t* config_masks = type_spec_writer.NextBlock<uint32_t>(num_entries);
for (const ResourceEntry* entry : sorted_entries) {
const uint16_t entry_id = entry->id.value().entry_id();
for (const ResourceTableEntryView& entry : sorted_entries) {
const uint16_t entry_id = entry.id.value();
// Populate the config masks for this entry.
uint32_t& entry_config_masks = config_masks[entry_id];
if (entry->visibility.level == Visibility::Level::kPublic) {
if (entry.visibility.level == Visibility::Level::kPublic) {
entry_config_masks |= util::HostToDevice32(ResTable_typeSpec::SPEC_PUBLIC);
}
if (entry->visibility.staged_api) {
if (entry.visibility.staged_api) {
entry_config_masks |= util::HostToDevice32(ResTable_typeSpec::SPEC_STAGED_API);
}
const size_t config_count = entry->values.size();
const size_t config_count = entry.values.size();
for (size_t i = 0; i < config_count; i++) {
const ConfigDescription& config = entry->values[i]->config;
const ConfigDescription& config = entry.values[i]->config;
for (size_t j = i + 1; j < config_count; j++) {
config_masks[entry_id] |= util::HostToDevice32(config.diff(entry->values[j]->config));
config_masks[entry_id] |= util::HostToDevice32(config.diff(entry.values[j]->config));
}
}
}
@ -590,7 +613,7 @@ class PackageFlattener {
}
// Since the entries are sorted by ID, the last ID will be the largest.
const size_t num_entries = type.entries.back()->id.value().entry_id() + 1;
const size_t num_entries = type.entries.back().id.value() + 1;
// The binary resource table lists resource entries for each
// configuration.
@ -603,20 +626,26 @@ class PackageFlattener {
// hardcoded string uses characters which make it an invalid resource name
const std::string obfuscated_resource_name = "0_resource_name_obfuscated";
for (const ResourceEntry* entry : type.entries) {
for (const ResourceTableEntryView& entry : type.entries) {
if (entry.staged_id) {
aliases_.insert(std::make_pair(
entry.staged_id.value().id.id,
ResourceId(package_.id.value(), type.id.value(), entry.id.value()).id));
}
uint32_t local_key_index;
ResourceName resource_name({}, type.type, entry->name);
ResourceName resource_name({}, type.type, entry.name);
if (!collapse_key_stringpool_ ||
name_collapse_exemptions_.find(resource_name) != name_collapse_exemptions_.end()) {
local_key_index = (uint32_t)key_pool_.MakeRef(entry->name).index();
local_key_index = (uint32_t)key_pool_.MakeRef(entry.name).index();
} else {
// 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.
for (auto& config_value : entry->values) {
for (auto& config_value : entry.values) {
config_to_entry_list_map[config_value->config].push_back(
FlatEntry{entry, config_value->value.get(), local_key_index});
FlatEntry{&entry, config_value->value.get(), local_key_index});
}
}
@ -667,6 +696,7 @@ class PackageFlattener {
StringPool key_pool_;
bool collapse_key_stringpool_;
const std::set<ResourceName>& name_collapse_exemptions_;
std::map<uint32_t, uint32_t> aliases_;
};
} // namespace
@ -684,7 +714,8 @@ bool TableFlattener::Consume(IAaptContext* context, ResourceTable* table) {
});
// Write the ResTable header.
const auto& table_view = table->GetPartitionedView();
const auto& table_view =
table->GetPartitionedView(ResourceTableViewOptions{.create_alias_entries = true});
ChunkWriter table_writer(buffer_);
ResTable_header* table_header = table_writer.StartChunk<ResTable_header>(RES_TABLE_TYPE);
table_header->packageCount = util::HostToDevice32(table_view.packages.size());

View File

@ -498,10 +498,20 @@ static bool DeserializePackageFromPb(const pb::Package& pb_package, const ResStr
out_error)) {
return false;
}
entry->overlayable_item = std::move(overlayable_item);
}
if (pb_entry.has_staged_id()) {
const pb::StagedId& pb_staged_id = pb_entry.staged_id();
StagedId staged_id;
if (pb_staged_id.has_source()) {
DeserializeSourceFromPb(pb_staged_id.source(), src_pool, &staged_id.source);
}
staged_id.id = pb_staged_id.staged_id();
entry->staged_id = std::move(staged_id);
}
ResourceId resid(pb_package.package_id().id(), pb_type.type_id().id(),
pb_entry.entry_id().id());
if (resid.is_valid()) {

View File

@ -364,43 +364,52 @@ void SerializeTableToPb(const ResourceTable& table, pb::ResourceTable* out_table
static const char* obfuscated_resource_name = "0_resource_name_obfuscated";
for (const auto& entry : type.entries) {
pb::Entry* pb_entry = pb_type->add_entry();
if (entry->id) {
pb_entry->mutable_entry_id()->set_id(entry->id.value().entry_id());
if (entry.id) {
pb_entry->mutable_entry_id()->set_id(entry.id.value());
}
ResourceName resource_name({}, type.type, entry->name);
ResourceName resource_name({}, type.type, entry.name);
if (options.collapse_key_stringpool &&
options.name_collapse_exemptions.find(resource_name) ==
options.name_collapse_exemptions.end()) {
pb_entry->set_name(obfuscated_resource_name);
} else {
pb_entry->set_name(entry->name);
pb_entry->set_name(entry.name);
}
// Write the Visibility struct.
pb::Visibility* pb_visibility = pb_entry->mutable_visibility();
pb_visibility->set_staged_api(entry->visibility.staged_api);
pb_visibility->set_level(SerializeVisibilityToPb(entry->visibility.level));
pb_visibility->set_staged_api(entry.visibility.staged_api);
pb_visibility->set_level(SerializeVisibilityToPb(entry.visibility.level));
if (source_pool != nullptr) {
SerializeSourceToPb(entry->visibility.source, source_pool.get(),
SerializeSourceToPb(entry.visibility.source, source_pool.get(),
pb_visibility->mutable_source());
}
pb_visibility->set_comment(entry->visibility.comment);
pb_visibility->set_comment(entry.visibility.comment);
if (entry->allow_new) {
if (entry.allow_new) {
pb::AllowNew* pb_allow_new = pb_entry->mutable_allow_new();
if (source_pool != nullptr) {
SerializeSourceToPb(entry->allow_new.value().source, source_pool.get(),
SerializeSourceToPb(entry.allow_new.value().source, source_pool.get(),
pb_allow_new->mutable_source());
}
pb_allow_new->set_comment(entry->allow_new.value().comment);
pb_allow_new->set_comment(entry.allow_new.value().comment);
}
if (entry->overlayable_item) {
SerializeOverlayableItemToPb(entry->overlayable_item.value(), overlayables,
if (entry.overlayable_item) {
SerializeOverlayableItemToPb(entry.overlayable_item.value(), overlayables,
source_pool.get(), pb_entry, out_table);
}
for (const std::unique_ptr<ResourceConfigValue>& config_value : entry->values) {
if (entry.staged_id) {
pb::StagedId* pb_staged_id = pb_entry->mutable_staged_id();
if (source_pool != nullptr) {
SerializeSourceToPb(entry.staged_id.value().source, source_pool.get(),
pb_staged_id->mutable_source());
}
pb_staged_id->set_staged_id(entry.staged_id.value().id.id);
}
for (const 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);

View File

@ -928,4 +928,27 @@ TEST(ProtoSerializeTest, SerializeMacro) {
EXPECT_THAT(deserialized->alias_namespaces, Eq(original->alias_namespaces));
}
TEST(ProtoSerializeTest, StagedId) {
CloningValueTransformer cloner(nullptr);
std::unique_ptr<IAaptContext> context = test::ContextBuilder().Build();
std::unique_ptr<ResourceTable> table = test::ResourceTableBuilder()
.Add(NewResourceBuilder("com.app.a:string/foo")
.SetStagedId(StagedId{.id = 0x01ff0001})
.Build())
.Build();
ResourceTable new_table;
pb::ResourceTable pb_table;
MockFileCollection files;
std::string error;
SerializeTableToPb(*table, &pb_table, context->GetDiagnostics());
ASSERT_TRUE(DeserializeTableFromPb(pb_table, &files, &new_table, &error));
EXPECT_THAT(error, IsEmpty());
auto result = new_table.FindResource(test::ParseNameOrDie("com.app.a:string/foo"));
ASSERT_TRUE(result);
ASSERT_TRUE(result.value().entry->staged_id);
EXPECT_THAT(result.value().entry->staged_id.value().id, Eq(ResourceId(0x01ff0001)));
}
} // namespace aapt

View File

@ -151,6 +151,18 @@ static bool MergeEntry(IAaptContext* context, const Source& src,
dst_entry->overlayable_item = std::move(src_entry->overlayable_item);
}
if (src_entry->staged_id) {
if (dst_entry->staged_id &&
dst_entry->staged_id.value().id != src_entry->staged_id.value().id) {
context->GetDiagnostics()->Error(DiagMessage(src_entry->staged_id.value().source)
<< "conflicting staged id declaration for resource '"
<< src_entry->name << "'");
context->GetDiagnostics()->Error(DiagMessage(dst_entry->staged_id.value().source)
<< "previous declaration here");
}
dst_entry->staged_id = std::move(src_entry->staged_id);
}
return true;
}

View File

@ -80,9 +80,6 @@ void TestDirectoryFixture::TearDown() {
}
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.";
// Create any intermediate directories specified in the path
auto pos = std::find(path.rbegin(), path.rend(), file::sDirSep);
if (pos != path.rend()) {