diff options
Diffstat (limited to 'tools/aapt2/cmd/Convert.cpp')
-rw-r--r-- | tools/aapt2/cmd/Convert.cpp | 391 |
1 files changed, 391 insertions, 0 deletions
diff --git a/tools/aapt2/cmd/Convert.cpp b/tools/aapt2/cmd/Convert.cpp new file mode 100644 index 000000000000..7f956c525bed --- /dev/null +++ b/tools/aapt2/cmd/Convert.cpp @@ -0,0 +1,391 @@ +/* + * Copyright (C) 2017 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 <vector> + +#include "android-base/macros.h" +#include "androidfw/StringPiece.h" + +#include "Flags.h" +#include "LoadedApk.h" +#include "ValueVisitor.h" +#include "cmd/Util.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 "process/IResourceTableConsumer.h" +#include "process/SymbolTable.h" +#include "util/Util.h" + +using ::android::StringPiece; +using ::android::base::StringPrintf; +using ::std::unique_ptr; +using ::std::vector; + +namespace aapt { + +class IApkSerializer { + public: + IApkSerializer(IAaptContext* context, const Source& source) : context_(context), source_(source) {} + + virtual bool SerializeXml(const xml::XmlResource* xml, const std::string& path, bool utf16, + IArchiveWriter* writer) = 0; + virtual bool SerializeTable(ResourceTable* table, IArchiveWriter* writer) = 0; + virtual bool SerializeFile(FileReference* file, IArchiveWriter* writer) = 0; + + virtual ~IApkSerializer() = default; + + protected: + IAaptContext* context_; + Source source_; +}; + +bool ConvertApk(IAaptContext* context, unique_ptr<LoadedApk> apk, IApkSerializer* serializer, + IArchiveWriter* writer) { + if (!serializer->SerializeXml(apk->GetManifest(), kAndroidManifestPath, true /*utf16*/, writer)) { + context->GetDiagnostics()->Error(DiagMessage(apk->GetSource()) + << "failed to serialize AndroidManifest.xml"); + return false; + } + + if (apk->GetResourceTable() != nullptr) { + // The table might be modified by below code. + auto converted_table = apk->GetResourceTable(); + + // Resources + for (const auto& package : converted_table->packages) { + for (const auto& type : package->types) { + for (const auto& entry : type->entries) { + for (const auto& config_value : entry->values) { + FileReference* file = ValueCast<FileReference>(config_value->value.get()); + if (file != nullptr) { + if (file->file == nullptr) { + context->GetDiagnostics()->Error(DiagMessage(apk->GetSource()) + << "no file associated with " << *file); + return false; + } + + if (!serializer->SerializeFile(file, writer)) { + context->GetDiagnostics()->Error(DiagMessage(apk->GetSource()) + << "failed to serialize file " << *file->path); + return false; + } + } // file + } // config_value + } // entry + } // type + } // package + + // Converted resource table + if (!serializer->SerializeTable(converted_table, writer)) { + context->GetDiagnostics()->Error(DiagMessage(apk->GetSource()) + << "failed to serialize the resource table"); + return false; + } + } + + // Other files + std::unique_ptr<io::IFileCollectionIterator> iterator = apk->GetFileCollection()->Iterator(); + while (iterator->HasNext()) { + io::IFile* file = iterator->Next(); + + std::string path = file->GetSource().path; + // The name of the path has the format "<zip-file-name>@<path-to-file>". + path = path.substr(path.find('@') + 1); + + // Manifest, resource table and resources have already been taken care of. + if (path == kAndroidManifestPath || + path == kApkResourceTablePath || + path == kProtoResourceTablePath || + path.find("res/") == 0) { + continue; + } + + if (!io::CopyFileToArchivePreserveCompression(context, file, path, writer)) { + context->GetDiagnostics()->Error(DiagMessage(apk->GetSource()) + << "failed to copy file " << path); + return false; + } + } + + return true; +} + + +class BinaryApkSerializer : public IApkSerializer { + public: + BinaryApkSerializer(IAaptContext* context, const Source& source, + const TableFlattenerOptions& options) + : IApkSerializer(context, source), tableFlattenerOptions_(options) {} + + bool SerializeXml(const xml::XmlResource* xml, const std::string& path, bool utf16, + IArchiveWriter* writer) override { + BigBuffer buffer(4096); + XmlFlattenerOptions options = {}; + options.use_utf16 = utf16; + options.keep_raw_values = true; + XmlFlattener flattener(&buffer, options); + if (!flattener.Consume(context_, xml)) { + return false; + } + + io::BigBufferInputStream input_stream(&buffer); + return io::CopyInputStreamToArchive(context_, &input_stream, path, ArchiveEntry::kCompress, + writer); + } + + bool SerializeTable(ResourceTable* table, IArchiveWriter* writer) override { + BigBuffer buffer(4096); + TableFlattener table_flattener(tableFlattenerOptions_, &buffer); + if (!table_flattener.Consume(context_, table)) { + return false; + } + + io::BigBufferInputStream input_stream(&buffer); + return io::CopyInputStreamToArchive(context_, &input_stream, kApkResourceTablePath, + ArchiveEntry::kAlign, writer); + } + + bool SerializeFile(FileReference* file, IArchiveWriter* writer) override { + if (file->type == ResourceFile::Type::kProtoXml) { + unique_ptr<io::InputStream> in = file->file->OpenInputStream(); + if (in == nullptr) { + context_->GetDiagnostics()->Error(DiagMessage(source_) + << "failed to open file " << *file->path); + return false; + } + + pb::XmlNode pb_node; + io::ZeroCopyInputAdaptor adaptor(in.get()); + if (!pb_node.ParseFromZeroCopyStream(&adaptor)) { + context_->GetDiagnostics()->Error(DiagMessage(source_) + << "failed to parse proto XML " << *file->path); + return false; + } + + std::string error; + unique_ptr<xml::XmlResource> xml = DeserializeXmlResourceFromPb(pb_node, &error); + if (xml == nullptr) { + context_->GetDiagnostics()->Error(DiagMessage(source_) + << "failed to deserialize proto XML " + << *file->path << ": " << error); + return false; + } + + if (!SerializeXml(xml.get(), *file->path, false /*utf16*/, writer)) { + context_->GetDiagnostics()->Error(DiagMessage(source_) + << "failed to serialize to binary XML: " << *file->path); + return false; + } + + file->type = ResourceFile::Type::kBinaryXml; + } else { + if (!io::CopyFileToArchivePreserveCompression(context_, file->file, *file->path, writer)) { + context_->GetDiagnostics()->Error(DiagMessage(source_) + << "failed to copy file " << *file->path); + return false; + } + } + + return true; + } + + private: + TableFlattenerOptions tableFlattenerOptions_; + + DISALLOW_COPY_AND_ASSIGN(BinaryApkSerializer); +}; + +class ProtoApkSerializer : public IApkSerializer { + public: + ProtoApkSerializer(IAaptContext* context, const Source& source) + : IApkSerializer(context, source) {} + + bool SerializeXml(const xml::XmlResource* xml, const std::string& path, bool utf16, + IArchiveWriter* writer) override { + pb::XmlNode pb_node; + SerializeXmlResourceToPb(*xml, &pb_node); + return io::CopyProtoToArchive(context_, &pb_node, path, ArchiveEntry::kCompress, writer); + } + + bool SerializeTable(ResourceTable* table, IArchiveWriter* writer) override { + pb::ResourceTable pb_table; + SerializeTableToPb(*table, &pb_table); + return io::CopyProtoToArchive(context_, &pb_table, kProtoResourceTablePath, + ArchiveEntry::kCompress, writer); + } + + bool SerializeFile(FileReference* file, IArchiveWriter* writer) override { + if (file->type == ResourceFile::Type::kBinaryXml) { + std::unique_ptr<io::IData> data = file->file->OpenAsData(); + if (!data) { + context_->GetDiagnostics()->Error(DiagMessage(source_) + << "failed to open file " << *file->path); + return false; + } + + std::string error; + std::unique_ptr<xml::XmlResource> xml = xml::Inflate(data->data(), data->size(), &error); + if (xml == nullptr) { + context_->GetDiagnostics()->Error(DiagMessage(source_) << "failed to parse binary XML: " + << error); + return false; + } + + if (!SerializeXml(xml.get(), *file->path, false /*utf16*/, writer)) { + context_->GetDiagnostics()->Error(DiagMessage(source_) + << "failed to serialize to proto XML: " << *file->path); + return false; + } + + file->type = ResourceFile::Type::kProtoXml; + } else { + if (!io::CopyFileToArchivePreserveCompression(context_, file->file, *file->path, writer)) { + context_->GetDiagnostics()->Error(DiagMessage(source_) + << "failed to copy file " << *file->path); + return false; + } + } + + return true; + } + + private: + DISALLOW_COPY_AND_ASSIGN(ProtoApkSerializer); +}; + +class Context : public IAaptContext { + public: + Context() : mangler_({}), symbols_(&mangler_) { + } + + PackageType GetPackageType() override { + return PackageType::kApp; + } + + SymbolTable* GetExternalSymbols() override { + return &symbols_; + } + + IDiagnostics* GetDiagnostics() override { + return &diag_; + } + + const std::string& GetCompilationPackage() override { + return package_; + } + + uint8_t GetPackageId() override { + // Nothing should call this. + UNIMPLEMENTED(FATAL) << "PackageID should not be necessary"; + return 0; + } + + NameMangler* GetNameMangler() override { + UNIMPLEMENTED(FATAL); + return nullptr; + } + + bool IsVerbose() override { + return verbose_; + } + + int GetMinSdkVersion() override { + return 0u; + } + + bool verbose_ = false; + std::string package_; + + private: + DISALLOW_COPY_AND_ASSIGN(Context); + + NameMangler mangler_; + SymbolTable symbols_; + StdErrDiagnostics diag_; +}; + +int Convert(const vector<StringPiece>& args) { + + static const char* kOutputFormatProto = "proto"; + static const char* kOutputFormatBinary = "binary"; + + Context context; + std::string output_path; + Maybe<std::string> output_format; + TableFlattenerOptions options; + Flags flags = + Flags() + .RequiredFlag("-o", "Output path", &output_path) + .OptionalFlag("--output-format", StringPrintf("Format of the output. Accepted values are " + "'%s' and '%s'. When not set, defaults to '%s'.", kOutputFormatProto, + kOutputFormatBinary, kOutputFormatBinary), &output_format) + .OptionalSwitch("--enable-sparse-encoding", + "Enables encoding sparse entries using a binary search tree.\n" + "This decreases APK size at the cost of resource retrieval performance.", + &options.use_sparse_entries) + .OptionalSwitch("-v", "Enables verbose logging", &context.verbose_); + if (!flags.Parse("aapt2 convert", args, &std::cerr)) { + return 1; + } + + if (flags.GetArgs().size() != 1) { + std::cerr << "must supply a single proto APK\n"; + flags.Usage("aapt2 convert", &std::cerr); + return 1; + } + + const StringPiece& path = flags.GetArgs()[0]; + unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(path, context.GetDiagnostics()); + if (apk == nullptr) { + context.GetDiagnostics()->Error(DiagMessage(path) << "failed to load APK"); + return 1; + } + + Maybe<AppInfo> app_info = + ExtractAppInfoFromBinaryManifest(*apk->GetManifest(), context.GetDiagnostics()); + if (!app_info) { + return 1; + } + + context.package_ = app_info.value().package; + + unique_ptr<IArchiveWriter> writer = + CreateZipFileArchiveWriter(context.GetDiagnostics(), output_path); + if (writer == nullptr) { + return 1; + } + + unique_ptr<IApkSerializer> serializer; + if (!output_format || output_format.value() == kOutputFormatBinary) { + serializer.reset(new BinaryApkSerializer(&context, apk->GetSource(), options)); + } else if (output_format.value() == kOutputFormatProto) { + serializer.reset(new ProtoApkSerializer(&context, apk->GetSource())); + } else { + context.GetDiagnostics()->Error(DiagMessage(path) + << "Invalid value for flag --output-format: " + << output_format.value()); + return 1; + } + + + return ConvertApk(&context, std::move(apk), serializer.get(), writer.get()) ? 0 : 1; +} + +} // namespace aapt |