Files
frameworks_base/tools/aapt2/format/binary/TableFlattener.cpp
Winson b2d7f5343d Signature policy for overlayable items
Add encoding/decoding of new policy for overlays. Signature enforces
that an overlay package is signed with the same key as the actor of
the target resource, so that an overlay can be installed by the user
as a normal app but restricted to those built by the author of the
actor (which can be the same as the target).

This also enforces that a valid policy is specified.

This doesn't implement the actors nor the signature check.

Bug: 119402606

Test: ResourceParserTest ParseOverlayablePolicy
Test: ProtoSerializerTest SerializeAndDeserializeOverlayable
Test: aapt2_tests

Change-Id: I8495ad790c2ebd51759bc6eba81149680c209475
2019-02-14 10:07:13 -08:00

769 lines
28 KiB
C++

/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "format/binary/TableFlattener.h"
#include <algorithm>
#include <numeric>
#include <sstream>
#include <type_traits>
#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"
#include "SdkConstants.h"
#include "ValueVisitor.h"
#include "format/binary/ChunkWriter.h"
#include "format/binary/ResourceTypeExtensions.h"
#include "util/BigBuffer.h"
using namespace android;
namespace aapt {
namespace {
template <typename T>
static bool cmp_ids(const T* a, const T* b) {
return a->id.value() < b->id.value();
}
static void strcpy16_htod(uint16_t* dst, size_t len, const StringPiece16& src) {
if (len == 0) {
return;
}
size_t i;
const char16_t* src_data = src.data();
for (i = 0; i < len - 1 && i < src.size(); i++) {
dst[i] = util::HostToDevice16((uint16_t)src_data[i]);
}
dst[i] = 0;
}
static bool cmp_style_entries(const Style::Entry& a, const Style::Entry& b) {
if (a.key.id) {
if (b.key.id) {
return a.key.id.value() < b.key.id.value();
}
return true;
} else if (!b.key.id) {
return a.key.name.value() < b.key.name.value();
}
return false;
}
struct FlatEntry {
ResourceEntry* entry;
Value* value;
// The entry string pool index to the entry's name.
uint32_t entry_key;
};
class MapFlattenVisitor : public ValueVisitor {
public:
using ValueVisitor::Visit;
MapFlattenVisitor(ResTable_entry_ext* out_entry, BigBuffer* buffer)
: out_entry_(out_entry), buffer_(buffer) {
}
void Visit(Attribute* attr) override {
{
Reference key = Reference(ResourceId(ResTable_map::ATTR_TYPE));
BinaryPrimitive val(Res_value::TYPE_INT_DEC, attr->type_mask);
FlattenEntry(&key, &val);
}
if (attr->min_int != std::numeric_limits<int32_t>::min()) {
Reference key = Reference(ResourceId(ResTable_map::ATTR_MIN));
BinaryPrimitive val(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(attr->min_int));
FlattenEntry(&key, &val);
}
if (attr->max_int != std::numeric_limits<int32_t>::max()) {
Reference key = Reference(ResourceId(ResTable_map::ATTR_MAX));
BinaryPrimitive val(Res_value::TYPE_INT_DEC, static_cast<uint32_t>(attr->max_int));
FlattenEntry(&key, &val);
}
for (Attribute::Symbol& s : attr->symbols) {
BinaryPrimitive val(Res_value::TYPE_INT_DEC, s.value);
FlattenEntry(&s.symbol, &val);
}
}
void Visit(Style* style) override {
if (style->parent) {
const Reference& parent_ref = style->parent.value();
CHECK(bool(parent_ref.id)) << "parent has no ID";
out_entry_->parent.ident = util::HostToDevice32(parent_ref.id.value().id);
}
// Sort the style.
std::sort(style->entries.begin(), style->entries.end(), cmp_style_entries);
for (Style::Entry& entry : style->entries) {
FlattenEntry(&entry.key, entry.value.get());
}
}
void Visit(Styleable* styleable) override {
for (auto& attr_ref : styleable->entries) {
BinaryPrimitive val(Res_value{});
FlattenEntry(&attr_ref, &val);
}
}
void Visit(Array* array) override {
const size_t count = array->elements.size();
for (size_t i = 0; i < count; i++) {
Reference key(android::ResTable_map::ATTR_MIN + i);
FlattenEntry(&key, array->elements[i].get());
}
}
void Visit(Plural* plural) override {
const size_t count = plural->values.size();
for (size_t i = 0; i < count; i++) {
if (!plural->values[i]) {
continue;
}
ResourceId q;
switch (i) {
case Plural::Zero:
q.id = android::ResTable_map::ATTR_ZERO;
break;
case Plural::One:
q.id = android::ResTable_map::ATTR_ONE;
break;
case Plural::Two:
q.id = android::ResTable_map::ATTR_TWO;
break;
case Plural::Few:
q.id = android::ResTable_map::ATTR_FEW;
break;
case Plural::Many:
q.id = android::ResTable_map::ATTR_MANY;
break;
case Plural::Other:
q.id = android::ResTable_map::ATTR_OTHER;
break;
default:
LOG(FATAL) << "unhandled plural type";
break;
}
Reference key(q);
FlattenEntry(&key, plural->values[i].get());
}
}
/**
* Call this after visiting a Value. This will finish any work that
* needs to be done to prepare the entry.
*/
void Finish() {
out_entry_->count = util::HostToDevice32(entry_count_);
}
private:
DISALLOW_COPY_AND_ASSIGN(MapFlattenVisitor);
void FlattenKey(Reference* key, ResTable_map* out_entry) {
CHECK(bool(key->id)) << "key has no ID";
out_entry->name.ident = util::HostToDevice32(key->id.value().id);
}
void FlattenValue(Item* value, ResTable_map* out_entry) {
CHECK(value->Flatten(&out_entry->value)) << "flatten failed";
}
void FlattenEntry(Reference* key, Item* value) {
ResTable_map* out_entry = buffer_->NextBlock<ResTable_map>();
FlattenKey(key, out_entry);
FlattenValue(value, out_entry);
out_entry->value.size = util::HostToDevice16(sizeof(out_entry->value));
entry_count_++;
}
ResTable_entry_ext* out_entry_;
BigBuffer* buffer_;
size_t entry_count_ = 0;
};
struct OverlayableChunk {
std::string actor;
Source source;
std::map<OverlayableItem::PolicyFlags, std::set<ResourceId>> policy_ids;
};
class PackageFlattener {
public:
PackageFlattener(IAaptContext* context, ResourceTablePackage* package,
const std::map<size_t, std::string>* shared_libs, bool use_sparse_entries,
bool collapse_key_stringpool, const std::set<std::string>& whitelisted_resources)
: context_(context),
diag_(context->GetDiagnostics()),
package_(package),
shared_libs_(shared_libs),
use_sparse_entries_(use_sparse_entries),
collapse_key_stringpool_(collapse_key_stringpool),
whitelisted_resources_(whitelisted_resources) {
}
bool FlattenPackage(BigBuffer* buffer) {
ChunkWriter pkg_writer(buffer);
ResTable_package* pkg_header = pkg_writer.StartChunk<ResTable_package>(RES_TABLE_PACKAGE_TYPE);
pkg_header->id = util::HostToDevice32(package_->id.value());
// AAPT truncated the package name, so do the same.
// Shared libraries require full package names, so don't truncate theirs.
if (context_->GetPackageType() != PackageType::kApp &&
package_->name.size() >= arraysize(pkg_header->name)) {
diag_->Error(DiagMessage() << "package name '" << package_->name
<< "' is too long. "
"Shared libraries cannot have truncated package names");
return false;
}
// Copy the package name in device endianness.
strcpy16_htod(pkg_header->name, arraysize(pkg_header->name), util::Utf8ToUtf16(package_->name));
// Serialize the types. We do this now so that our type and key strings
// are populated. We write those first.
BigBuffer type_buffer(1024);
FlattenTypes(&type_buffer);
pkg_header->typeStrings = util::HostToDevice32(pkg_writer.size());
StringPool::FlattenUtf16(pkg_writer.buffer(), type_pool_, diag_);
pkg_header->keyStrings = util::HostToDevice32(pkg_writer.size());
StringPool::FlattenUtf8(pkg_writer.buffer(), key_pool_, diag_);
// Append the types.
buffer->AppendBuffer(std::move(type_buffer));
// If there are libraries (or if the package ID is 0x00), encode a library chunk.
if (package_->id.value() == 0x00 || !shared_libs_->empty()) {
FlattenLibrarySpec(buffer);
}
if (!FlattenOverlayable(buffer)) {
return false;
}
pkg_writer.Finish();
return true;
}
private:
DISALLOW_COPY_AND_ASSIGN(PackageFlattener);
template <typename T, bool IsItem>
T* WriteEntry(FlatEntry* entry, BigBuffer* buffer) {
static_assert(
std::is_same<ResTable_entry, T>::value || std::is_same<ResTable_entry_ext, T>::value,
"T must be ResTable_entry or ResTable_entry_ext");
T* result = buffer->NextBlock<T>();
ResTable_entry* out_entry = (ResTable_entry*)result;
if (entry->entry->visibility.level == Visibility::Level::kPublic) {
out_entry->flags |= ResTable_entry::FLAG_PUBLIC;
}
if (entry->value->IsWeak()) {
out_entry->flags |= ResTable_entry::FLAG_WEAK;
}
if (!IsItem) {
out_entry->flags |= ResTable_entry::FLAG_COMPLEX;
}
out_entry->flags = util::HostToDevice16(out_entry->flags);
out_entry->key.index = util::HostToDevice32(entry->entry_key);
out_entry->size = util::HostToDevice16(sizeof(T));
return result;
}
bool FlattenValue(FlatEntry* entry, BigBuffer* buffer) {
if (Item* item = ValueCast<Item>(entry->value)) {
WriteEntry<ResTable_entry, true>(entry, buffer);
Res_value* outValue = buffer->NextBlock<Res_value>();
CHECK(item->Flatten(outValue)) << "flatten failed";
outValue->size = util::HostToDevice16(sizeof(*outValue));
} else {
ResTable_entry_ext* out_entry = WriteEntry<ResTable_entry_ext, false>(entry, buffer);
MapFlattenVisitor visitor(out_entry, buffer);
entry->value->Accept(&visitor);
visitor.Finish();
}
return true;
}
bool FlattenConfig(const ResourceTableType* type, const ConfigDescription& config,
const size_t num_total_entries, std::vector<FlatEntry>* entries,
BigBuffer* buffer) {
CHECK(num_total_entries != 0);
CHECK(num_total_entries <= std::numeric_limits<uint16_t>::max());
ChunkWriter type_writer(buffer);
ResTable_type* type_header = type_writer.StartChunk<ResTable_type>(RES_TABLE_TYPE_TYPE);
type_header->id = type->id.value();
type_header->config = config;
type_header->config.swapHtoD();
std::vector<uint32_t> offsets;
offsets.resize(num_total_entries, 0xffffffffu);
BigBuffer values_buffer(512);
for (FlatEntry& flat_entry : *entries) {
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 '"
<< ResourceNameRef(package_->name, type->type, flat_entry.entry->name)
<< "' for configuration '" << config << "'");
return false;
}
}
bool sparse_encode = use_sparse_entries_;
// Only sparse encode if the entries will be read on platforms O+.
sparse_encode =
sparse_encode && (context_->GetMinSdkVersion() >= SDK_O || config.sdkVersion >= SDK_O);
// Only sparse encode if the offsets are representable in 2 bytes.
sparse_encode =
sparse_encode && (values_buffer.size() / 4u) <= std::numeric_limits<uint16_t>::max();
// Only sparse encode if the ratio of populated entries to total entries is below some
// threshold.
sparse_encode =
sparse_encode && ((100 * entries->size()) / num_total_entries) < kSparseEncodingThreshold;
if (sparse_encode) {
type_header->entryCount = util::HostToDevice32(entries->size());
type_header->flags |= ResTable_type::FLAG_SPARSE;
ResTable_sparseTypeEntry* indices =
type_writer.NextBlock<ResTable_sparseTypeEntry>(entries->size());
for (size_t i = 0; i < num_total_entries; i++) {
if (offsets[i] != ResTable_type::NO_ENTRY) {
CHECK((offsets[i] & 0x03) == 0);
indices->idx = util::HostToDevice16(i);
indices->offset = util::HostToDevice16(offsets[i] / 4u);
indices++;
}
}
} else {
type_header->entryCount = util::HostToDevice32(num_total_entries);
uint32_t* indices = type_writer.NextBlock<uint32_t>(num_total_entries);
for (size_t i = 0; i < num_total_entries; i++) {
indices[i] = util::HostToDevice32(offsets[i]);
}
}
type_header->entriesStart = util::HostToDevice32(type_writer.size());
type_writer.buffer()->AppendBuffer(std::move(values_buffer));
type_writer.Finish();
return true;
}
std::vector<ResourceTableType*> CollectAndSortTypes() {
std::vector<ResourceTableType*> sorted_types;
for (auto& type : package_->types) {
if (type->type == ResourceType::kStyleable) {
// Styleables aren't real Resource Types, they are represented in the
// R.java file.
continue;
}
CHECK(bool(type->id)) << "type must have an ID set";
sorted_types.push_back(type.get());
}
std::sort(sorted_types.begin(), sorted_types.end(), cmp_ids<ResourceTableType>);
return sorted_types;
}
std::vector<ResourceEntry*> CollectAndSortEntries(ResourceTableType* type) {
// Sort the entries by entry ID.
std::vector<ResourceEntry*> sorted_entries;
for (auto& entry : type->entries) {
CHECK(bool(entry->id)) << "entry must have an ID set";
sorted_entries.push_back(entry.get());
}
std::sort(sorted_entries.begin(), sorted_entries.end(), cmp_ids<ResourceEntry>);
return sorted_entries;
}
bool FlattenOverlayable(BigBuffer* buffer) {
std::set<ResourceId> seen_ids;
std::map<std::string, OverlayableChunk> overlayable_chunks;
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>";
if (!entry->overlayable_item) {
continue;
}
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());
CHECK(seen_ids.find(id) == seen_ids.end())
<< "multiple overlayable definitions found for resource "
<< ResourceName(package_->name, type->type, entry->name).to_string();
seen_ids.insert(id);
// Find the overlayable chunk with the specified name
OverlayableChunk* overlayable_chunk = nullptr;
auto iter = overlayable_chunks.find(item.overlayable->name);
if (iter == overlayable_chunks.end()) {
OverlayableChunk chunk{item.overlayable->actor, item.overlayable->source};
overlayable_chunk =
&overlayable_chunks.insert({item.overlayable->name, chunk}).first->second;
} else {
OverlayableChunk& chunk = iter->second;
if (!(chunk.source == item.overlayable->source)) {
// The name of an overlayable set of resources must be unique
context_->GetDiagnostics()->Error(DiagMessage(item.overlayable->source)
<< "duplicate overlayable name"
<< item.overlayable->name << "'");
context_->GetDiagnostics()->Error(DiagMessage(chunk.source)
<< "previous declaration here");
return false;
}
CHECK(chunk.actor == item.overlayable->actor);
overlayable_chunk = &chunk;
}
if (item.policies == 0) {
context_->GetDiagnostics()->Error(DiagMessage(item.overlayable->source)
<< "overlayable "
<< entry->name
<< " does not specify policy");
return false;
}
uint32_t policy_flags = 0;
if (item.policies & OverlayableItem::Policy::kPublic) {
policy_flags |= ResTable_overlayable_policy_header::POLICY_PUBLIC;
}
if (item.policies & OverlayableItem::Policy::kSystem) {
policy_flags |= ResTable_overlayable_policy_header::POLICY_SYSTEM_PARTITION;
}
if (item.policies & OverlayableItem::Policy::kVendor) {
policy_flags |= ResTable_overlayable_policy_header::POLICY_VENDOR_PARTITION;
}
if (item.policies & OverlayableItem::Policy::kProduct) {
policy_flags |= ResTable_overlayable_policy_header::POLICY_PRODUCT_PARTITION;
}
if (item.policies & OverlayableItem::Policy::kSignature) {
policy_flags |= ResTable_overlayable_policy_header::POLICY_SIGNATURE;
}
auto policy = overlayable_chunk->policy_ids.find(policy_flags);
if (policy != overlayable_chunk->policy_ids.end()) {
policy->second.insert(id);
} else {
overlayable_chunk->policy_ids.insert(
std::make_pair(policy_flags, std::set<ResourceId>{id}));
}
}
}
for (auto& overlayable_pair : overlayable_chunks) {
std::string name = overlayable_pair.first;
OverlayableChunk& overlayable = overlayable_pair.second;
// Write the header of the overlayable chunk
ChunkWriter overlayable_writer(buffer);
auto* overlayable_type =
overlayable_writer.StartChunk<ResTable_overlayable_header>(RES_TABLE_OVERLAYABLE_TYPE);
if (name.size() >= arraysize(overlayable_type->name)) {
diag_->Error(DiagMessage() << "overlayable name '" << name
<< "' exceeds maximum length ("
<< arraysize(overlayable_type->name)
<< " utf16 characters)");
return false;
}
strcpy16_htod(overlayable_type->name, arraysize(overlayable_type->name),
util::Utf8ToUtf16(name));
if (overlayable.actor.size() >= arraysize(overlayable_type->actor)) {
diag_->Error(DiagMessage() << "overlayable name '" << overlayable.actor
<< "' exceeds maximum length ("
<< arraysize(overlayable_type->actor)
<< " utf16 characters)");
return false;
}
strcpy16_htod(overlayable_type->actor, arraysize(overlayable_type->actor),
util::Utf8ToUtf16(overlayable.actor));
// Write each policy block for the overlayable
for (auto& policy_ids : overlayable.policy_ids) {
ChunkWriter policy_writer(buffer);
auto* policy_type = policy_writer.StartChunk<ResTable_overlayable_policy_header>(
RES_TABLE_OVERLAYABLE_POLICY_TYPE);
policy_type->policy_flags = util::HostToDevice32(static_cast<uint32_t>(policy_ids.first));
policy_type->entry_count = util::HostToDevice32(static_cast<uint32_t>(
policy_ids.second.size()));
// Write the ids after the policy header
auto* id_block = policy_writer.NextBlock<ResTable_ref>(policy_ids.second.size());
for (const ResourceId& id : policy_ids.second) {
id_block->ident = util::HostToDevice32(id.id);
id_block++;
}
policy_writer.Finish();
}
overlayable_writer.Finish();
}
return true;
}
bool FlattenTypeSpec(ResourceTableType* type, std::vector<ResourceEntry*>* sorted_entries,
BigBuffer* buffer) {
ChunkWriter type_spec_writer(buffer);
ResTable_typeSpec* spec_header =
type_spec_writer.StartChunk<ResTable_typeSpec>(RES_TABLE_TYPE_SPEC_TYPE);
spec_header->id = type->id.value();
if (sorted_entries->empty()) {
type_spec_writer.Finish();
return true;
}
// 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() + 1;
spec_header->entryCount = util::HostToDevice32(num_entries);
// Reserve space for the masks of each resource in this type. These
// show for which configuration axis the resource changes.
uint32_t* config_masks = type_spec_writer.NextBlock<uint32_t>(num_entries);
const size_t actual_num_entries = sorted_entries->size();
for (size_t entryIndex = 0; entryIndex < actual_num_entries; entryIndex++) {
ResourceEntry* entry = sorted_entries->at(entryIndex);
// Populate the config masks for this entry.
if (entry->visibility.level == Visibility::Level::kPublic) {
config_masks[entry->id.value()] |= util::HostToDevice32(ResTable_typeSpec::SPEC_PUBLIC);
}
const size_t config_count = entry->values.size();
for (size_t i = 0; i < config_count; i++) {
const ConfigDescription& config = entry->values[i]->config;
for (size_t j = i + 1; j < config_count; j++) {
config_masks[entry->id.value()] |=
util::HostToDevice32(config.diff(entry->values[j]->config));
}
}
}
type_spec_writer.Finish();
return true;
}
bool FlattenTypes(BigBuffer* buffer) {
// Sort the types by their IDs. They will be inserted into the StringPool in
// this order.
std::vector<ResourceTableType*> sorted_types = CollectAndSortTypes();
size_t expected_type_id = 1;
for (ResourceTableType* type : sorted_types) {
// If there is a gap in the type IDs, fill in the StringPool
// with empty values until we reach the ID we expect.
while (type->id.value() > expected_type_id) {
std::stringstream type_name;
type_name << "?" << expected_type_id;
type_pool_.MakeRef(type_name.str());
expected_type_id++;
}
expected_type_id++;
type_pool_.MakeRef(to_string(type->type));
std::vector<ResourceEntry*> sorted_entries = CollectAndSortEntries(type);
if (sorted_entries.empty()) {
continue;
}
if (!FlattenTypeSpec(type, &sorted_entries, buffer)) {
return false;
}
// Since the entries are sorted by ID, the last ID will be the largest.
const size_t num_entries = sorted_entries.back()->id.value() + 1;
// The binary resource table lists resource entries for each
// configuration.
// We store them inverted, where a resource entry lists the values for
// each
// configuration available. Here we reverse this to match the binary
// table.
std::map<ConfigDescription, std::vector<FlatEntry>> config_to_entry_list_map;
// hardcoded string uses characters which make it an invalid resource name
const std::string obfuscated_resource_name = "0_resource_name_obfuscated";
for (ResourceEntry* entry : sorted_entries) {
uint32_t local_key_index;
if (!collapse_key_stringpool_ ||
whitelisted_resources_.find(entry->name) != whitelisted_resources_.end()) {
local_key_index = (uint32_t)key_pool_.MakeRef(entry->name).index();
} else {
// resource isn't whitelisted, 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) {
config_to_entry_list_map[config_value->config].push_back(
FlatEntry{entry, config_value->value.get(), local_key_index});
}
}
// Flatten a configuration value.
for (auto& entry : config_to_entry_list_map) {
if (!FlattenConfig(type, entry.first, num_entries, &entry.second, buffer)) {
return false;
}
}
}
return true;
}
void FlattenLibrarySpec(BigBuffer* buffer) {
ChunkWriter lib_writer(buffer);
ResTable_lib_header* lib_header =
lib_writer.StartChunk<ResTable_lib_header>(RES_TABLE_LIBRARY_TYPE);
const size_t num_entries = (package_->id.value() == 0x00 ? 1 : 0) + shared_libs_->size();
CHECK(num_entries > 0);
lib_header->count = util::HostToDevice32(num_entries);
ResTable_lib_entry* lib_entry = buffer->NextBlock<ResTable_lib_entry>(num_entries);
if (package_->id.value() == 0x00) {
// Add this package
lib_entry->packageId = util::HostToDevice32(0x00);
strcpy16_htod(lib_entry->packageName, arraysize(lib_entry->packageName),
util::Utf8ToUtf16(package_->name));
++lib_entry;
}
for (auto& map_entry : *shared_libs_) {
lib_entry->packageId = util::HostToDevice32(map_entry.first);
strcpy16_htod(lib_entry->packageName, arraysize(lib_entry->packageName),
util::Utf8ToUtf16(map_entry.second));
++lib_entry;
}
lib_writer.Finish();
}
IAaptContext* context_;
IDiagnostics* diag_;
ResourceTablePackage* package_;
const std::map<size_t, std::string>* shared_libs_;
bool use_sparse_entries_;
StringPool type_pool_;
StringPool key_pool_;
bool collapse_key_stringpool_;
const std::set<std::string>& whitelisted_resources_;
};
} // namespace
bool TableFlattener::Consume(IAaptContext* context, ResourceTable* table) {
if (options_.sort_stringpool_entries) {
// We must do this before writing the resources, since the string pool IDs may change.
table->string_pool.Prune();
table->string_pool.Sort([](const StringPool::Context &a, const StringPool::Context &b) -> int {
int diff = util::compare(a.priority, b.priority);
if (diff == 0) {
diff = a.config.compare(b.config);
}
return diff;
});
}
// Write the ResTable header.
ChunkWriter table_writer(buffer_);
ResTable_header* table_header = table_writer.StartChunk<ResTable_header>(RES_TABLE_TYPE);
table_header->packageCount = util::HostToDevice32(table->packages.size());
// Flatten the values string pool.
StringPool::FlattenUtf8(table_writer.buffer(), table->string_pool,
context->GetDiagnostics());
BigBuffer package_buffer(1024);
// Flatten each package.
for (auto& package : table->packages) {
if (context->GetPackageType() == PackageType::kApp) {
// Write a self mapping entry for this package if the ID is non-standard (0x7f).
const uint8_t package_id = package->id.value();
if (package_id != kFrameworkPackageId && package_id != kAppPackageId) {
auto result = table->included_packages_.insert({package_id, package->name});
if (!result.second && result.first->second != package->name) {
// A mapping for this package ID already exists, and is a different package. Error!
context->GetDiagnostics()->Error(
DiagMessage() << android::base::StringPrintf(
"can't map package ID %02x to '%s'. Already mapped to '%s'", package_id,
package->name.c_str(), result.first->second.c_str()));
return false;
}
}
}
PackageFlattener flattener(context, package.get(), &table->included_packages_,
options_.use_sparse_entries, options_.collapse_key_stringpool,
options_.whitelisted_resources);
if (!flattener.FlattenPackage(&package_buffer)) {
return false;
}
}
// Finally merge all the packages into the main buffer.
table_writer.buffer()->AppendBuffer(std::move(package_buffer));
table_writer.Finish();
return true;
}
} // namespace aapt