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
769 lines
28 KiB
C++
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
|