AAPT2: Reformatted dump command invocations

Use with:
  aapt2 dump apc [apc]
  aapt2 dump configurations [apk]
  aapt2 dump strings [apk]
  aapt2 dump resources [apk]
  aapt2 dump xmlstrings [apk] --file [file]
  aapt2 dump xmltree [apk] --file [file]

Will add permissions and badging in a later commit.

Bug: 73351292
Test: Manual tests of the commands
Change-Id: I97eec01222af14053a98bd70255f1bfecd16b1c4
diff --git a/tools/aapt2/cmd/Dump.cpp b/tools/aapt2/cmd/Dump.cpp
index 6e9c800..b4311c5 100644
--- a/tools/aapt2/cmd/Dump.cpp
+++ b/tools/aapt2/cmd/Dump.cpp
@@ -24,8 +24,11 @@
 
 #include "Debug.h"
 #include "Diagnostics.h"
+#include "LoadedApk.h"
+#include "Util.h"
 #include "format/Container.h"
 #include "format/binary/BinaryResourceParser.h"
+#include "format/binary/XmlFlattener.h"
 #include "format/proto/ProtoDeserialize.h"
 #include "io/FileStream.h"
 #include "io/ZipArchive.h"
@@ -70,184 +73,6 @@
   printer->Println(StringPrintf("Data:     offset=%" PRIi64 " length=%zd", offset, len));
 }
 
-static bool DumpXmlFile(IAaptContext* context, io::IFile* file, bool proto,
-                        text::Printer* printer) {
-  std::unique_ptr<xml::XmlResource> doc;
-  if (proto) {
-    std::unique_ptr<io::InputStream> in = file->OpenInputStream();
-    if (in == nullptr) {
-      context->GetDiagnostics()->Error(DiagMessage() << "failed to open file");
-      return false;
-    }
-
-    io::ZeroCopyInputAdaptor adaptor(in.get());
-    pb::XmlNode pb_node;
-    if (!pb_node.ParseFromZeroCopyStream(&adaptor)) {
-      context->GetDiagnostics()->Error(DiagMessage() << "failed to parse file as proto XML");
-      return false;
-    }
-
-    std::string err;
-    doc = DeserializeXmlResourceFromPb(pb_node, &err);
-    if (doc == nullptr) {
-      context->GetDiagnostics()->Error(DiagMessage() << "failed to deserialize proto XML");
-      return false;
-    }
-    printer->Println("Proto XML");
-  } else {
-    std::unique_ptr<io::IData> data = file->OpenAsData();
-    if (data == nullptr) {
-      context->GetDiagnostics()->Error(DiagMessage() << "failed to open file");
-      return false;
-    }
-
-    std::string err;
-    doc = xml::Inflate(data->data(), data->size(), &err);
-    if (doc == nullptr) {
-      context->GetDiagnostics()->Error(DiagMessage() << "failed to parse file as binary XML");
-      return false;
-    }
-    printer->Println("Binary XML");
-  }
-
-  Debug::DumpXml(*doc, printer);
-  return true;
-}
-
-static bool TryDumpFile(IAaptContext* context, const std::string& file_path,
-                        const DumpOptions& options) {
-  // Use a smaller buffer so that there is less latency for dumping to stdout.
-  constexpr size_t kStdOutBufferSize = 1024u;
-  io::FileOutputStream fout(STDOUT_FILENO, kStdOutBufferSize);
-  Printer printer(&fout);
-
-  std::string err;
-  std::unique_ptr<io::ZipFileCollection> zip = io::ZipFileCollection::Create(file_path, &err);
-  if (zip) {
-    ResourceTable table(/** validate_resources **/ false);
-    bool proto = false;
-    if (io::IFile* file = zip->FindFile("resources.pb")) {
-      proto = true;
-
-      std::unique_ptr<io::IData> data = file->OpenAsData();
-      if (data == nullptr) {
-        context->GetDiagnostics()->Error(DiagMessage(file_path) << "failed to open resources.pb");
-        return false;
-      }
-
-      pb::ResourceTable pb_table;
-      if (!pb_table.ParseFromArray(data->data(), data->size())) {
-        context->GetDiagnostics()->Error(DiagMessage(file_path) << "invalid resources.pb");
-        return false;
-      }
-
-      if (!DeserializeTableFromPb(pb_table, zip.get(), &table, &err)) {
-        context->GetDiagnostics()->Error(DiagMessage(file_path)
-                                         << "failed to parse table: " << err);
-        return false;
-      }
-    } else if (io::IFile* file = zip->FindFile("resources.arsc")) {
-      std::unique_ptr<io::IData> data = file->OpenAsData();
-      if (!data) {
-        context->GetDiagnostics()->Error(DiagMessage(file_path) << "failed to open resources.arsc");
-        return false;
-      }
-
-      BinaryResourceParser parser(context->GetDiagnostics(), &table, Source(file_path),
-                                  data->data(), data->size());
-      if (!parser.Parse()) {
-        return false;
-      }
-    }
-
-    if (!options.file_to_dump_path) {
-      if (proto) {
-        printer.Println("Proto APK");
-      } else {
-        printer.Println("Binary APK");
-      }
-      Debug::PrintTable(table, options.print_options, &printer);
-      return true;
-    }
-
-    io::IFile* file = zip->FindFile(options.file_to_dump_path.value());
-    if (file == nullptr) {
-      context->GetDiagnostics()->Error(DiagMessage(file_path)
-                                       << "file '" << options.file_to_dump_path.value()
-                                       << "' not found in APK");
-      return false;
-    }
-    return DumpXmlFile(context, file, proto, &printer);
-  }
-
-  err.clear();
-
-  io::FileInputStream input(file_path);
-  if (input.HadError()) {
-    context->GetDiagnostics()->Error(DiagMessage(file_path)
-                                     << "failed to open file: " << input.GetError());
-    return false;
-  }
-
-  // Try as a compiled file.
-  ContainerReader reader(&input);
-  if (reader.HadError()) {
-    context->GetDiagnostics()->Error(DiagMessage(file_path)
-                                     << "failed to read container: " << reader.GetError());
-    return false;
-  }
-
-  printer.Println("AAPT2 Container (APC)");
-  ContainerReaderEntry* entry;
-  while ((entry = reader.Next()) != nullptr) {
-    if (entry->Type() == ContainerEntryType::kResTable) {
-      printer.Println("kResTable");
-
-      pb::ResourceTable pb_table;
-      if (!entry->GetResTable(&pb_table)) {
-        context->GetDiagnostics()->Error(DiagMessage(file_path)
-                                         << "failed to parse proto table: " << entry->GetError());
-        continue;
-      }
-
-      ResourceTable table;
-      err.clear();
-      if (!DeserializeTableFromPb(pb_table, nullptr /*files*/, &table, &err)) {
-        context->GetDiagnostics()->Error(DiagMessage(file_path)
-                                         << "failed to parse table: " << err);
-        continue;
-      }
-
-      printer.Indent();
-      Debug::PrintTable(table, options.print_options, &printer);
-      printer.Undent();
-    } else if (entry->Type() == ContainerEntryType::kResFile) {
-      printer.Println("kResFile");
-      pb::internal::CompiledFile pb_compiled_file;
-      off64_t offset;
-      size_t length;
-      if (!entry->GetResFileOffsets(&pb_compiled_file, &offset, &length)) {
-        context->GetDiagnostics()->Error(
-            DiagMessage(file_path) << "failed to parse compiled proto file: " << entry->GetError());
-        continue;
-      }
-
-      ResourceFile file;
-      std::string error;
-      if (!DeserializeCompiledFileFromPb(pb_compiled_file, &file, &error)) {
-        context->GetDiagnostics()->Warn(DiagMessage(file_path)
-                                        << "failed to parse compiled file: " << error);
-        continue;
-      }
-
-      printer.Indent();
-      DumpCompiledFile(file, Source(file_path), offset, length, &printer);
-      printer.Undent();
-    }
-  }
-  return true;
-}
-
 namespace {
 
 class DumpContext : public IAaptContext {
@@ -299,17 +124,290 @@
 
 }  // namespace
 
-int DumpCommand::Action(const std::vector<std::string>& args) {
+// Use a smaller buffer so that there is less latency for dumping to stdout.
+constexpr size_t kStdOutBufferSize = 1024u;
+
+int DumpAPCCommand::Action(const std::vector<std::string>& args) {
   DumpContext context;
-  context.SetVerbose(verbose_);
-  options_.print_options.show_sources = true;
-  options_.print_options.show_values = !no_values_;
-  for (const std::string& arg : args) {
-    if (!TryDumpFile(&context, arg, options_)) {
-      return 1;
+  DebugPrintTableOptions print_options;
+  print_options.show_sources = true;
+  print_options.show_values = !no_values_;
+
+  if (args.size() < 1) {
+    diag_->Error(DiagMessage() << "No dump container specified.");
+    return 1;
+  }
+
+  io::FileOutputStream fout(STDOUT_FILENO, kStdOutBufferSize);
+  Printer printer(&fout);
+
+  for (auto container : args) {
+    io::FileInputStream input(container);
+    if (input.HadError()) {
+      context.GetDiagnostics()->Error(DiagMessage(container)
+                                          << "failed to open file: " << input.GetError());
+      return false;
+    }
+
+    // Try as a compiled file.
+    ContainerReader reader(&input);
+    if (reader.HadError()) {
+      context.GetDiagnostics()->Error(DiagMessage(container)
+                                           << "failed to read container: " << reader.GetError());
+      return false;
+    }
+
+    printer.Println("AAPT2 Container (APC)");
+    ContainerReaderEntry* entry;
+    std::string error;
+    while ((entry = reader.Next()) != nullptr) {
+      if (entry->Type() == ContainerEntryType::kResTable) {
+        printer.Println("kResTable");
+
+        pb::ResourceTable pb_table;
+        if (!entry->GetResTable(&pb_table)) {
+          context.GetDiagnostics()->Error(DiagMessage(container)
+                                               << "failed to parse proto table: "
+                                               << entry->GetError());
+          continue;
+        }
+
+        ResourceTable table;
+        error.clear();
+        if (!DeserializeTableFromPb(pb_table, nullptr /*files*/, &table, &error)) {
+          context.GetDiagnostics()->Error(DiagMessage(container)
+                                               << "failed to parse table: " << error);
+          continue;
+        }
+
+        printer.Indent();
+        Debug::PrintTable(table, print_options, &printer);
+        printer.Undent();
+      } else if (entry->Type() == ContainerEntryType::kResFile) {
+        printer.Println("kResFile");
+        pb::internal::CompiledFile pb_compiled_file;
+        off64_t offset;
+        size_t length;
+        if (!entry->GetResFileOffsets(&pb_compiled_file, &offset, &length)) {
+          context.GetDiagnostics()->Error(
+              DiagMessage(container) << "failed to parse compiled proto file: "
+                                     << entry->GetError());
+          continue;
+        }
+
+        ResourceFile file;
+        if (!DeserializeCompiledFileFromPb(pb_compiled_file, &file, &error)) {
+          context.GetDiagnostics()->Warn(DiagMessage(container)
+                                              << "failed to parse compiled file: " << error);
+          continue;
+        }
+
+        printer.Indent();
+        DumpCompiledFile(file, Source(container), offset, length, &printer);
+        printer.Undent();
+      }
     }
   }
+
   return 0;
 }
 
+int DumpConfigsCommand::Action(const std::vector<std::string>& args) {
+  if (args.size() < 1) {
+    diag_->Error(DiagMessage() << "No dump apk specified.");
+    return 1;
+  }
+
+  auto loaded_apk = LoadedApk::LoadApkFromPath(args[0], diag_);
+  if (!loaded_apk) {
+    return 1;
+  }
+
+  io::FileOutputStream fout(STDOUT_FILENO, kStdOutBufferSize);
+  Printer printer(&fout);
+
+  // Comparison function used to order configurations
+  auto compare = [](ConfigDescription c1, ConfigDescription c2) -> bool {
+      return c1.compare(c2) < 0;
+  };
+
+  // Insert the configurations into a set in order to keep every configuarion seen
+  std::set<ConfigDescription, decltype(compare)> configs(compare);
+  for (auto& package : loaded_apk->GetResourceTable()->packages) {
+    for (auto& type : package->types) {
+      for (auto& entry : type->entries) {
+        for (auto& value : entry->values) {
+          configs.insert(value->config);
+        }
+      }
+    }
+  }
+
+  // Print the configurations in order
+  for (auto& config : configs) {
+    printer.Print(StringPrintf("%s\n", config.to_string().data()));
+  }
+
+  return 0;
+}
+
+int DumpStringsCommand::Action(const std::vector<std::string>& args) {
+  DumpContext context;
+  if (args.size() < 1) {
+    diag_->Error(DiagMessage() << "No dump apk specified.");
+    return 1;
+  }
+
+  io::FileOutputStream fout(STDOUT_FILENO, kStdOutBufferSize);
+  Printer printer(&fout);
+
+  for (auto apk : args) {
+    auto loaded_apk = LoadedApk::LoadApkFromPath(apk, diag_);
+    if (!loaded_apk) {
+      return 1;
+    }
+
+    // Load the run-time xml string pool using the flattened data
+    BigBuffer buffer(4096);
+    StringPool::FlattenUtf8(&buffer, loaded_apk->GetResourceTable()->string_pool,
+                            context.GetDiagnostics());
+    auto data = buffer.to_string();
+    android::ResStringPool pool(data.data(), data.size(), false);
+    Debug::DumpResStringPool(&pool, &printer);
+  }
+
+  return 0;
+}
+
+int DumpTableCommand::Action(const std::vector<std::string>& args) {
+  if (args.size() < 1) {
+    diag_->Error(DiagMessage() << "No dump apk specified.");
+    return 1;
+  }
+
+  io::FileOutputStream fout(STDOUT_FILENO, kStdOutBufferSize);
+  Printer printer(&fout);
+
+  DebugPrintTableOptions print_options;
+  print_options.show_sources = true;
+  print_options.show_values = !no_values_;
+
+  for (auto apk : args) {
+    auto loaded_apk = LoadedApk::LoadApkFromPath(apk, diag_);
+    if (!loaded_apk) {
+      return 1;
+    }
+
+    if (loaded_apk->GetApkFormat()) {
+      printer.Println("Proto APK");
+    } else {
+      printer.Println("Binary APK");
+    }
+
+    Debug::PrintTable(*loaded_apk->GetResourceTable(), print_options, &printer);
+  }
+
+  return 0;
+}
+
+int DumpXmlTreeCommand::Action(const std::vector<std::string>& args) {
+  if (args.size() < 1) {
+    diag_->Error(DiagMessage() << "No dump apk specified");
+    return 1;
+  }
+
+  auto loaded_apk = LoadedApk::LoadApkFromPath(args[0], diag_);
+  if (!loaded_apk) {
+    return 1;
+  }
+
+  io::FileOutputStream fout(STDOUT_FILENO, kStdOutBufferSize);
+  Printer printer(&fout);
+
+  // Dump the xml tree of every passed in file
+  for (auto file : files_) {
+    auto xml = loaded_apk->LoadXml(file, diag_);
+    if (!xml) {
+      return 1;
+    }
+
+    Debug::DumpXml(*xml, &printer);
+  }
+
+  return 0;
+}
+
+int DumpXmlStringsCommand::Action(const std::vector<std::string>& args) {
+  DumpContext context;
+  if (args.size() < 1) {
+    diag_->Error(DiagMessage() << "No dump apk specified.");
+    return 1;
+  }
+
+  auto loaded_apk = LoadedApk::LoadApkFromPath(args[0], diag_);
+  if (!loaded_apk) {
+    return 1;
+  }
+
+  io::FileOutputStream fout(STDOUT_FILENO, kStdOutBufferSize);
+  Printer printer(&fout);
+
+  // Dump the xml strings of every passed in file
+  for (auto xml_file : files_) {
+    android::ResXMLTree tree;
+
+    if (loaded_apk->GetApkFormat() == kProto) {
+      auto xml = loaded_apk->LoadXml(xml_file, diag_);
+      if (!xml) {
+        return 1;
+      }
+
+      // Flatten the xml document to get a binary representation of the proto xml file
+      BigBuffer buffer(4096);
+      XmlFlattenerOptions options = {};
+      options.keep_raw_values = true;
+      XmlFlattener flattener(&buffer, options);
+      if (!flattener.Consume(&context, xml.get())) {
+        return 1;
+      }
+
+      // Load the run-time xml tree using the flattened data
+      std::string data = buffer.to_string();
+      tree.setTo(data.data(), data.size(), /** copyData */ true);
+
+    } else if (loaded_apk->GetApkFormat() == kBinary) {
+      io::IFile* file = loaded_apk->GetFileCollection()->FindFile(xml_file);
+      if (!file) {
+        diag_->Error(DiagMessage(xml_file) << "file '" << xml_file << "' not found in APK");
+        return 1;
+      }
+
+      std::unique_ptr<io::IData> data = file->OpenAsData();
+      if (!data) {
+        diag_->Error(DiagMessage() << "failed to open file");
+        return 1;
+      }
+
+      // Load the run-time xml tree from the file data
+      tree.setTo(data->data(), data->size(), /** copyData */ true);
+    }
+
+    Debug::DumpResStringPool(&tree.getStrings(), &printer);
+  }
+
+  return 0;
+}
+
+/** Preform no action because a subcommand is required. */
+int DumpCommand::Action(const std::vector<std::string>& args) {
+  if (args.size() == 0) {
+    diag_->Error(DiagMessage() << "no subcommand specified");
+  } else {
+    diag_->Error(DiagMessage() << "unknown subcommand '" << args[0] << "'");
+  }
+
+  Usage(&std::cerr);
+  return 1;
+}
+
 }  // namespace aapt