Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8780eb6e authored by Adam Lesinski's avatar Adam Lesinski
Browse files

AAPT2: Add convert command

This command allows a developer to convert their proto APK
(generated from the link phase using --proto-format) into
a binary APK suitable for use on device.

  aapt2 convert -o output.apk input.apk

Test: manual + make aapt2_tests
Change-Id: I10a7c33bb4b57006d01fe00a8bf92f78e04e7e50
parent 4f340a4f
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

toolSources = [
    "cmd/Compile.cpp",
    "cmd/Convert.cpp",
    "cmd/Diff.cpp",
    "cmd/Dump.cpp",
    "cmd/Link.cpp",
+110 −36
Original line number Diff line number Diff line
@@ -21,43 +21,139 @@
#include "format/Archive.h"
#include "format/binary/TableFlattener.h"
#include "format/binary/XmlFlattener.h"
#include "format/proto/ProtoDeserialize.h"
#include "io/BigBufferStream.h"
#include "io/Util.h"
#include "xml/XmlDom.h"

namespace aapt {
using ::aapt::io::IFile;
using ::aapt::io::IFileCollection;
using ::aapt::xml::XmlResource;
using ::android::StringPiece;
using ::std::unique_ptr;

using xml::XmlResource;
namespace aapt {

std::unique_ptr<LoadedApk> LoadedApk::LoadApkFromPath(IAaptContext* context,
                                                      const android::StringPiece& path) {
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) {
    context->GetDiagnostics()->Error(DiagMessage(source) << error);
  if (apk == nullptr) {
    diag->Error(DiagMessage(path) << "failed opening zip: " << error);
    return {};
  }

  if (apk->FindFile("resources.arsc") != nullptr) {
    return LoadBinaryApkFromFileCollection(source, std::move(apk), diag);
  } else if (apk->FindFile("resources.pb") != nullptr) {
    return LoadProtoApkFromFileCollection(source, std::move(apk), diag);
  }
  diag->Error(DiagMessage(path) << "no resource table found");
  return {};
}

std::unique_ptr<LoadedApk> LoadedApk::LoadProtoApkFromFileCollection(
    const Source& source, unique_ptr<io::IFileCollection> collection, IDiagnostics* diag) {
  io::IFile* table_file = collection->FindFile(kProtoResourceTablePath);
  if (table_file == nullptr) {
    diag->Error(DiagMessage(source) << "failed to find " << kProtoResourceTablePath);
    return {};
  }

  std::unique_ptr<io::InputStream> in = table_file->OpenInputStream();
  if (in == nullptr) {
    diag->Error(DiagMessage(source) << "failed to open " << kProtoResourceTablePath);
    return {};
  }

  pb::ResourceTable pb_table;
  io::ZeroCopyInputAdaptor adaptor(in.get());
  if (!pb_table.ParseFromZeroCopyStream(&adaptor)) {
    diag->Error(DiagMessage(source) << "failed to read " << kProtoResourceTablePath);
    return {};
  }

  std::string error;
  std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
  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::ZeroCopyInputAdaptor manifest_adaptor(manifest_in.get());
  if (!pb_node.ParseFromZeroCopyStream(&manifest_adaptor)) {
    diag->Error(DiagMessage(source) << "failed to read proto " << kAndroidManifestPath);
    return {};
  }

  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));
}

std::unique_ptr<LoadedApk> LoadedApk::LoadBinaryApkFromFileCollection(
    const Source& source, unique_ptr<io::IFileCollection> collection, IDiagnostics* diag) {
  io::IFile* table_file = collection->FindFile(kApkResourceTablePath);
  if (table_file == nullptr) {
    diag->Error(DiagMessage(source) << "failed to find " << kApkResourceTablePath);

  io::IFile* file = apk->FindFile("resources.arsc");
  if (!file) {
    context->GetDiagnostics()->Error(DiagMessage(source) << "no resources.arsc found");
    return {};
  }

  std::unique_ptr<io::IData> data = file->OpenAsData();
  if (!data) {
    context->GetDiagnostics()->Error(DiagMessage(source) << "could not open resources.arsc");
  std::unique_ptr<io::IData> data = table_file->OpenAsData();
  if (data == nullptr) {
    diag->Error(DiagMessage(source) << "failed to open " << kApkResourceTablePath);
    return {};
  }

  std::unique_ptr<ResourceTable> table = util::make_unique<ResourceTable>();
  BinaryResourceParser parser(context, table.get(), source, data->data(), data->size(), apk.get());
  BinaryResourceParser parser(diag, table.get(), source, data->data(), data->size(),
                              collection.get());
  if (!parser.Parse()) {
    return {};
  }

  return util::make_unique<LoadedApk>(source, std::move(apk), std::move(table));
  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));
}

bool LoadedApk::WriteToArchive(IAaptContext* context, const TableFlattenerOptions& options,
@@ -148,26 +244,4 @@ bool LoadedApk::WriteToArchive(IAaptContext* context, ResourceTable* split_table
  return true;
}

std::unique_ptr<xml::XmlResource> LoadedApk::InflateManifest(IAaptContext* context) {
  IDiagnostics* diag = context->GetDiagnostics();

  io::IFile* manifest_file = GetFileCollection()->FindFile("AndroidManifest.xml");
  if (manifest_file == nullptr) {
    diag->Error(DiagMessage(source_) << "no AndroidManifest.xml found");
    return {};
  }

  std::unique_ptr<io::IData> manifest_data = manifest_file->OpenAsData();
  if (manifest_data == nullptr) {
    diag->Error(DiagMessage(manifest_file->GetSource()) << "could not open AndroidManifest.xml");
    return {};
  }

  std::unique_ptr<xml::XmlResource> manifest =
      xml::Inflate(manifest_data->data(), manifest_data->size(), diag, manifest_file->GetSource());
  if (manifest == nullptr) {
    diag->Error(DiagMessage() << "failed to read binary AndroidManifest.xml");
  }
  return manifest;
}
}  // namespace aapt
+32 −11
Original line number Diff line number Diff line
@@ -29,20 +29,41 @@

namespace aapt {

constexpr static const char kApkResourceTablePath[] = "resources.arsc";
constexpr static const char kProtoResourceTablePath[] = "resources.pb";
constexpr static const char kAndroidManifestPath[] = "AndroidManifest.xml";

// Info about an APK loaded in memory.
class LoadedApk {
 public:
  LoadedApk(
      const Source& source,
      std::unique_ptr<io::IFileCollection> apk,
      std::unique_ptr<ResourceTable> table)
      : source_(source), apk_(std::move(apk)), table_(std::move(table)) {}
  virtual ~LoadedApk() = default;
  // Loads both binary and proto APKs from disk.
  static std::unique_ptr<LoadedApk> LoadApkFromPath(const ::android::StringPiece& path,
                                                    IDiagnostics* diag);

  // Loads a proto APK from the given file collection.
  static std::unique_ptr<LoadedApk> LoadProtoApkFromFileCollection(
      const Source& source, std::unique_ptr<io::IFileCollection> collection, IDiagnostics* diag);

  // Loads a binary APK from the given file collection.
  static std::unique_ptr<LoadedApk> LoadBinaryApkFromFileCollection(
      const Source& source, std::unique_ptr<io::IFileCollection> collection, IDiagnostics* diag);

  LoadedApk(const Source& source, std::unique_ptr<io::IFileCollection> apk,
            std::unique_ptr<ResourceTable> table, std::unique_ptr<xml::XmlResource> manifest)
      : source_(source),
        apk_(std::move(apk)),
        table_(std::move(table)),
        manifest_(std::move(manifest)) {
  }

  io::IFileCollection* GetFileCollection() {
    return apk_.get();
  }

  const ResourceTable* GetResourceTable() const {
    return table_.get();
  }

  ResourceTable* GetResourceTable() {
    return table_.get();
  }
@@ -51,6 +72,10 @@ class LoadedApk {
    return source_;
  }

  const xml::XmlResource* GetManifest() const {
    return manifest_.get();
  }

  /**
   * Writes the APK on disk at the given path, while also removing the resource
   * files that are not referenced in the resource table.
@@ -71,11 +96,6 @@ class LoadedApk {
                              const TableFlattenerOptions& options, FilterChain* filters,
                              IArchiveWriter* writer, xml::XmlResource* manifest = nullptr);

  /** Inflates the AndroidManifest.xml file from the APK. */
  std::unique_ptr<xml::XmlResource> InflateManifest(IAaptContext* context);

  static std::unique_ptr<LoadedApk> LoadApkFromPath(IAaptContext* context,
                                                    const android::StringPiece& path);

 private:
  DISALLOW_COPY_AND_ASSIGN(LoadedApk);
@@ -83,6 +103,7 @@ class LoadedApk {
  Source source_;
  std::unique_ptr<io::IFileCollection> apk_;
  std::unique_ptr<ResourceTable> table_;
  std::unique_ptr<xml::XmlResource> manifest_;
};

}  // namespace aapt
+4 −1
Original line number Diff line number Diff line
@@ -50,7 +50,7 @@ static void PrintVersion() {
}

static void PrintUsage() {
  std::cerr << "\nusage: aapt2 [compile|link|dump|diff|optimize|version] ..." << std::endl;
  std::cerr << "\nusage: aapt2 [compile|link|dump|diff|optimize|convert|version] ..." << std::endl;
}

extern int Compile(const std::vector<StringPiece>& args, IDiagnostics* diagnostics);
@@ -58,6 +58,7 @@ extern int Link(const std::vector<StringPiece>& args, IDiagnostics* diagnostics)
extern int Dump(const std::vector<StringPiece>& args);
extern int Diff(const std::vector<StringPiece>& args);
extern int Optimize(const std::vector<StringPiece>& args);
extern int Convert(const std::vector<StringPiece>& args);

static int ExecuteCommand(const StringPiece& command, const std::vector<StringPiece>& args,
                          IDiagnostics* diagnostics) {
@@ -71,6 +72,8 @@ static int ExecuteCommand(const StringPiece& command, const std::vector<StringPi
    return Diff(args);
  } else if (command == "optimize") {
    return Optimize(args);
  } else if (command == "convert") {
    return Convert(args);
  } else if (command == "version") {
    PrintVersion();
    return 0;
+228 −0
Original line number Diff line number Diff line
/*
 * 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 "io/BigBufferStream.h"
#include "io/Util.h"
#include "process/IResourceTableConsumer.h"
#include "process/SymbolTable.h"

using ::android::StringPiece;
using ::std::unique_ptr;
using ::std::vector;

namespace aapt {

static bool FlattenXml(IAaptContext* context, const xml::XmlResource& xml,
                       const std::string& entry_path, bool utf16, IArchiveWriter* writer) {
  BigBuffer buffer(4096);
  XmlFlattenerOptions options = {};
  options.use_utf16 = utf16;
  XmlFlattener flattener(&buffer, options);
  if (!flattener.Consume(context, &xml)) {
    return false;
  }
  io::BigBufferInputStream input_stream(&buffer);
  return io::CopyInputStreamToArchive(context, &input_stream, entry_path, ArchiveEntry::kCompress,
                                      writer);
}

bool ConvertProtoApkToBinaryApk(IAaptContext* context, unique_ptr<LoadedApk> apk,
                                const TableFlattenerOptions& options, IArchiveWriter* writer) {
  if (!FlattenXml(context, *apk->GetManifest(), kAndroidManifestPath, true /*utf16*/, writer)) {
    return false;
  }

  BigBuffer buffer(4096);
  TableFlattener table_flattener(options, &buffer);
  if (!table_flattener.Consume(context, apk->GetResourceTable())) {
    return false;
  }

  io::BigBufferInputStream input_stream(&buffer);
  if (!io::CopyInputStreamToArchive(context, &input_stream, kApkResourceTablePath,
                                    ArchiveEntry::kAlign, writer)) {
    return false;
  }

  for (const auto& package : apk->GetResourceTable()->packages) {
    for (const auto& type : package->types) {
      for (const auto& entry : type->entries) {
        for (const auto& config_value : entry->values) {
          const FileReference* file = ValueCast<FileReference>(config_value->value.get());
          if (file != nullptr) {
            if (file->file == nullptr) {
              context->GetDiagnostics()->Warn(DiagMessage(apk->GetSource())
                                              << "no file associated with " << *file);
              return false;
            }

            if (file->type == ResourceFile::Type::kProtoXml) {
              unique_ptr<io::InputStream> in = file->file->OpenInputStream();
              if (in == nullptr) {
                context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
                                                 << "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(apk->GetSource())
                                                 << "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(apk->GetSource())
                                                 << "failed to deserialize proto XML "
                                                 << *file->path << ": " << error);
                return false;
              }

              if (!FlattenXml(context, *xml, *file->path, false /*utf16*/, writer)) {
                context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
                                                 << "failed to serialize XML " << *file->path);
                return false;
              }
            } else {
              if (!io::CopyFileToArchive(context, file->file, *file->path,
                                         file->file->WasCompressed() ? ArchiveEntry::kCompress : 0u,
                                         writer)) {
                context->GetDiagnostics()->Error(DiagMessage(apk->GetSource())
                                                 << "failed to copy file " << *file->path);
                return false;
              }
            }

          } // file
        } // config_value
      } // entry
    } // type
  } // package
  return true;
}

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) {
  Context context;
  std::string output_path;
  TableFlattenerOptions options;
  Flags flags =
      Flags()
          .RequiredFlag("-o", "Output path", &output_path)
          .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;
  }
  return ConvertProtoApkToBinaryApk(&context, std::move(apk), options, writer.get()) ? 0 : 1;
}

}  // namespace aapt
Loading