Ryan Mitchell 9634efb6f4 Prepare aapt2 for multiple ids per type
For the SDK finalization changes, aapt2 must be able to handle
resources of the same type having different type ids. The
ResourceTable data structure currently stores package ids and type ids
on ResourceTablePackage and ResourceTableType respectively. This
prevents resource entries of the same type from having different type
ids without having to create another ResourceTableType structure.

JavaClassGenerator assumes each type only appears once in the
ResourceTable and it would need to dedupe the types to ensure one class
containing all the resource types ids is generated. TableFlattener on
the other hand needs a separate ResourceTableType for each type/id
combination so that the types are flattened into separate
ResTable_types.

This change simplifies aapt2's ResourceTable data structure:
- Resource ids are stored exclusively on ResourceEntry structures
  meaning multiple entries can have different type ids while being
  stored in the same ResourceTableType. Classes like JavaClassGenerator
  can simply iterate over a type to see all the resources of the type
  regardless of what their type id is.

- ResourceTable::GetPartitionedView() retrieves a list of resources
  sorted and partitioned by package id, type id, and entry id. Classes
  like TableFlattener can use this view to get separate
  ResourceTavleTypes for each different type id that a type has.

These changes will also make it easy to have a resource span multiple
type ids if it exhausts all of the entry ids in one type id.

The new NewResourcesBuilder replaces the numerous setter methods on
ResourceTable.

Bug: 183102797
Test: aapt2_tests
Change-Id: I60dbcb24143bb958333899cafa7d41faa226d203
2021-03-29 10:22:08 -07:00

358 lines
13 KiB
C++

/*
* Copyright (C) 2016 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 "LoadedApk.h"
#include "ResourceValues.h"
#include "ValueVisitor.h"
#include "format/Archive.h"
#include "format/binary/TableFlattener.h"
#include "format/binary/XmlFlattener.h"
#include "format/proto/ProtoDeserialize.h"
#include "format/proto/ProtoSerialize.h"
#include "io/BigBufferStream.h"
#include "io/Util.h"
#include "xml/XmlDom.h"
using ::aapt::io::IFile;
using ::aapt::io::IFileCollection;
using ::aapt::xml::XmlResource;
using ::android::StringPiece;
using ::std::unique_ptr;
namespace aapt {
static ApkFormat DetermineApkFormat(io::IFileCollection* apk) {
if (apk->FindFile(kApkResourceTablePath) != nullptr) {
return ApkFormat::kBinary;
} else if (apk->FindFile(kProtoResourceTablePath) != nullptr) {
return ApkFormat::kProto;
} else {
// If the resource table is not present, attempt to read the manifest.
io::IFile* manifest_file = apk->FindFile(kAndroidManifestPath);
if (manifest_file == nullptr) {
return ApkFormat::kUnknown;
}
// First try in proto format.
std::unique_ptr<io::InputStream> manifest_in = manifest_file->OpenInputStream();
if (manifest_in != nullptr) {
pb::XmlNode pb_node;
io::ProtoInputStreamReader proto_reader(manifest_in.get());
if (proto_reader.ReadMessage(&pb_node)) {
return ApkFormat::kProto;
}
}
// If it didn't work, try in binary format.
std::unique_ptr<io::IData> manifest_data = manifest_file->OpenAsData();
if (manifest_data != nullptr) {
std::string error;
std::unique_ptr<xml::XmlResource> manifest =
xml::Inflate(manifest_data->data(), manifest_data->size(), &error);
if (manifest != nullptr) {
return ApkFormat::kBinary;
}
}
return ApkFormat::kUnknown;
}
}
std::unique_ptr<LoadedApk> LoadedApk::LoadApkFromPath(const StringPiece& path, IDiagnostics* diag) {
Source source(path);
std::string error;
std::unique_ptr<io::ZipFileCollection> apk = io::ZipFileCollection::Create(path, &error);
if (apk == nullptr) {
diag->Error(DiagMessage(path) << "failed opening zip: " << error);
return {};
}
ApkFormat apkFormat = DetermineApkFormat(apk.get());
switch (apkFormat) {
case ApkFormat::kBinary:
return LoadBinaryApkFromFileCollection(source, std::move(apk), diag);
case ApkFormat::kProto:
return LoadProtoApkFromFileCollection(source, std::move(apk), diag);
default:
diag->Error(DiagMessage(path) << "could not identify format of APK");
return {};
}
}
std::unique_ptr<LoadedApk> LoadedApk::LoadProtoApkFromFileCollection(
const Source& source, unique_ptr<io::IFileCollection> collection, IDiagnostics* diag) {
std::unique_ptr<ResourceTable> table;
io::IFile* table_file = collection->FindFile(kProtoResourceTablePath);
if (table_file != nullptr) {
pb::ResourceTable pb_table;
std::unique_ptr<io::InputStream> in = table_file->OpenInputStream();
if (in == nullptr) {
diag->Error(DiagMessage(source) << "failed to open " << kProtoResourceTablePath);
return {};
}
io::ProtoInputStreamReader proto_reader(in.get());
if (!proto_reader.ReadMessage(&pb_table)) {
diag->Error(DiagMessage(source) << "failed to read " << kProtoResourceTablePath);
return {};
}
std::string error;
table = util::make_unique<ResourceTable>(ResourceTable::Validation::kDisabled);
if (!DeserializeTableFromPb(pb_table, collection.get(), table.get(), &error)) {
diag->Error(DiagMessage(source)
<< "failed to deserialize " << kProtoResourceTablePath << ": " << error);
return {};
}
}
io::IFile* manifest_file = collection->FindFile(kAndroidManifestPath);
if (manifest_file == nullptr) {
diag->Error(DiagMessage(source) << "failed to find " << kAndroidManifestPath);
return {};
}
std::unique_ptr<io::InputStream> manifest_in = manifest_file->OpenInputStream();
if (manifest_in == nullptr) {
diag->Error(DiagMessage(source) << "failed to open " << kAndroidManifestPath);
return {};
}
pb::XmlNode pb_node;
io::ProtoInputStreamReader proto_reader(manifest_in.get());
if (!proto_reader.ReadMessage(&pb_node)) {
diag->Error(DiagMessage(source) << "failed to read proto " << kAndroidManifestPath);
return {};
}
std::string error;
std::unique_ptr<xml::XmlResource> manifest = DeserializeXmlResourceFromPb(pb_node, &error);
if (manifest == nullptr) {
diag->Error(DiagMessage(source)
<< "failed to deserialize proto " << kAndroidManifestPath << ": " << error);
return {};
}
return util::make_unique<LoadedApk>(source, std::move(collection), std::move(table),
std::move(manifest), ApkFormat::kProto);
}
std::unique_ptr<LoadedApk> LoadedApk::LoadBinaryApkFromFileCollection(
const Source& source, unique_ptr<io::IFileCollection> collection, IDiagnostics* diag) {
std::unique_ptr<ResourceTable> table;
io::IFile* table_file = collection->FindFile(kApkResourceTablePath);
if (table_file != nullptr) {
table = util::make_unique<ResourceTable>(ResourceTable::Validation::kDisabled);
std::unique_ptr<io::IData> data = table_file->OpenAsData();
if (data == nullptr) {
diag->Error(DiagMessage(source) << "failed to open " << kApkResourceTablePath);
return {};
}
BinaryResourceParser parser(diag, table.get(), source, data->data(), data->size(),
collection.get());
if (!parser.Parse()) {
return {};
}
}
io::IFile* manifest_file = collection->FindFile(kAndroidManifestPath);
if (manifest_file == nullptr) {
diag->Error(DiagMessage(source) << "failed to find " << kAndroidManifestPath);
return {};
}
std::unique_ptr<io::IData> manifest_data = manifest_file->OpenAsData();
if (manifest_data == nullptr) {
diag->Error(DiagMessage(source) << "failed to open " << kAndroidManifestPath);
return {};
}
std::string error;
std::unique_ptr<xml::XmlResource> manifest =
xml::Inflate(manifest_data->data(), manifest_data->size(), &error);
if (manifest == nullptr) {
diag->Error(DiagMessage(source)
<< "failed to parse binary " << kAndroidManifestPath << ": " << error);
return {};
}
return util::make_unique<LoadedApk>(source, std::move(collection), std::move(table),
std::move(manifest), ApkFormat::kBinary);
}
bool LoadedApk::WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options,
IArchiveWriter* writer) {
FilterChain empty;
return WriteToArchive(context, table_.get(), options, &empty, writer);
}
bool LoadedApk::WriteToArchive(IAaptContext* context, ResourceTable* split_table,
const TableFlattenerOptions& options, FilterChain* filters,
IArchiveWriter* writer, XmlResource* manifest) {
std::set<std::string> referenced_resources;
// List the files being referenced in the resource table.
for (auto& pkg : split_table->packages) {
for (auto& type : pkg->types) {
for (auto& entry : type->entries) {
for (auto& config_value : entry->values) {
FileReference* file_ref = ValueCast<FileReference>(config_value->value.get());
if (file_ref) {
referenced_resources.insert(*file_ref->path);
}
}
}
}
}
std::unique_ptr<io::IFileCollectionIterator> iterator = apk_->Iterator();
while (iterator->HasNext()) {
io::IFile* file = iterator->Next();
std::string path = file->GetSource().path;
std::string output_path = path;
bool is_resource = path.find("res/") == 0;
if (is_resource) {
auto it = options.shortened_path_map.find(path);
if (it != options.shortened_path_map.end()) {
output_path = it->second;
}
}
// Skip resources that are not referenced if requested.
if (is_resource && referenced_resources.find(output_path) == referenced_resources.end()) {
if (context->IsVerbose()) {
context->GetDiagnostics()->Note(DiagMessage()
<< "Removing resource '" << path << "' from APK.");
}
continue;
}
if (!filters->Keep(path)) {
if (context->IsVerbose()) {
context->GetDiagnostics()->Note(DiagMessage() << "Filtered '" << path << "' from APK.");
}
continue;
}
// The resource table needs to be re-serialized since it might have changed.
if (format_ == ApkFormat::kBinary && path == kApkResourceTablePath) {
BigBuffer buffer(4096);
// TODO(adamlesinski): How to determine if there were sparse entries (and if to encode
// with sparse entries) b/35389232.
TableFlattener flattener(options, &buffer);
if (!flattener.Consume(context, split_table)) {
return false;
}
io::BigBufferInputStream input_stream(&buffer);
if (!io::CopyInputStreamToArchive(context,
&input_stream,
path,
ArchiveEntry::kAlign,
writer)) {
return false;
}
} else if (format_ == ApkFormat::kProto && path == kProtoResourceTablePath) {
SerializeTableOptions proto_serialize_options;
proto_serialize_options.collapse_key_stringpool =
options.collapse_key_stringpool;
proto_serialize_options.name_collapse_exemptions =
options.name_collapse_exemptions;
pb::ResourceTable pb_table;
SerializeTableToPb(*split_table, &pb_table, context->GetDiagnostics(),
proto_serialize_options);
if (!io::CopyProtoToArchive(context,
&pb_table,
path,
ArchiveEntry::kAlign, writer)) {
return false;
}
} else if (manifest != nullptr && path == "AndroidManifest.xml") {
BigBuffer buffer(8192);
XmlFlattenerOptions xml_flattener_options;
xml_flattener_options.use_utf16 = true;
XmlFlattener xml_flattener(&buffer, xml_flattener_options);
if (!xml_flattener.Consume(context, manifest)) {
context->GetDiagnostics()->Error(DiagMessage(path) << "flattening failed");
return false;
}
uint32_t compression_flags = file->WasCompressed() ? ArchiveEntry::kCompress : 0u;
io::BigBufferInputStream manifest_buffer_in(&buffer);
if (!io::CopyInputStreamToArchive(context, &manifest_buffer_in, path, compression_flags,
writer)) {
return false;
}
} else {
if (!io::CopyFileToArchivePreserveCompression(
context, file, output_path, writer)) {
return false;
}
}
}
return true;
}
std::unique_ptr<xml::XmlResource> LoadedApk::LoadXml(const std::string& file_path,
IDiagnostics* diag) const {
io::IFile* file = apk_->FindFile(file_path);
if (file == nullptr) {
diag->Error(DiagMessage() << "failed to find file");
return nullptr;
}
std::unique_ptr<xml::XmlResource> doc;
if (format_ == ApkFormat::kProto) {
std::unique_ptr<io::InputStream> in = file->OpenInputStream();
if (!in) {
diag->Error(DiagMessage() << "failed to open file");
return nullptr;
}
pb::XmlNode pb_node;
io::ProtoInputStreamReader proto_reader(in.get());
if (!proto_reader.ReadMessage(&pb_node)) {
diag->Error(DiagMessage() << "failed to parse file as proto XML");
return nullptr;
}
std::string err;
doc = DeserializeXmlResourceFromPb(pb_node, &err);
if (!doc) {
diag->Error(DiagMessage() << "failed to deserialize proto XML: " << err);
return nullptr;
}
} else if (format_ == ApkFormat::kBinary) {
std::unique_ptr<io::IData> data = file->OpenAsData();
if (!data) {
diag->Error(DiagMessage() << "failed to open file");
return nullptr;
}
std::string err;
doc = xml::Inflate(data->data(), data->size(), &err);
if (!doc) {
diag->Error(DiagMessage() << "failed to parse file as binary XML: " << err);
return nullptr;
}
}
return doc;
}
} // namespace aapt