aapt2: add 'dump overlayable' command

Add a command to print a resource table's <overlayable> resources. Given
the following input

  <overlayable name="TestResources">
    <policy type="system">
      <item type="string" name="a" />
    </policy>
    <policy type="sytem|vendor">
      <item type="string" name="b" />
      <item type="string" name="c" />
    </policy>
  </overlayable>

aapt2 dump overlayable will produce

  name="TestResources" actor=""
    policies="system"
      string/a
    policies="system|vendor"
      string/b
      string/c

Bug: 120609160
Test: manual (aapt2 dump overlayable $(gettop)/frameworks/base/cmds/idmap2/tests/data/target/target.apk)
Change-Id: I21041e6169c62d01f1a469624911ce7cad3e18a8
diff --git a/tools/aapt2/Debug.cpp b/tools/aapt2/Debug.cpp
index 7ffa5ff..137fbd6 100644
--- a/tools/aapt2/Debug.cpp
+++ b/tools/aapt2/Debug.cpp
@@ -246,6 +246,36 @@
   Printer* printer_;
 };
 
+std::string OverlayablePoliciesToString(OverlayableItem::PolicyFlags policies) {
+  static const std::map<OverlayableItem::PolicyFlags, std::string> kFlagToString = {
+    {OverlayableItem::kPublic, "public"},
+    {OverlayableItem::kSystem, "system"},
+    {OverlayableItem::kVendor, "vendor"},
+    {OverlayableItem::kProduct, "product"},
+    {OverlayableItem::kSignature, "signature"},
+    {OverlayableItem::kOdm, "odm"},
+    {OverlayableItem::kOem, "oem"},
+  };
+  std::string str;
+  for (auto const& policy : kFlagToString) {
+    if ((policies & policy.first) != policy.first) {
+      continue;
+    }
+    if (!str.empty()) {
+      str.append("|");
+    }
+    str.append(policy.second);
+    policies &= ~policy.first;
+  }
+  if (policies != 0) {
+    if (!str.empty()) {
+      str.append("|");
+    }
+    str.append(StringPrintf("0x%08x", policies));
+  }
+  return !str.empty() ? str : "none";
+}
+
 }  // namespace
 
 void Debug::PrintTable(const ResourceTable& table, const DebugPrintTableOptions& options,
@@ -312,6 +342,10 @@
             break;
         }
 
+        if (entry->overlayable_item) {
+          printer->Print(" OVERLAYABLE");
+        }
+
         printer->Println();
 
         if (options.show_values) {
@@ -525,4 +559,62 @@
   doc.root->Accept(&xml_visitor);
 }
 
+struct DumpOverlayableEntry {
+  std::string overlayable_section;
+  std::string policy_subsection;
+  std::string resource_name;
+};
+
+void Debug::DumpOverlayable(const ResourceTable& table, text::Printer* printer) {
+  std::vector<DumpOverlayableEntry> items;
+  for (const auto& package : table.packages) {
+    for (const auto& type : package->types) {
+      for (const auto& entry : type->entries) {
+        if (entry->overlayable_item) {
+          const auto& overlayable_item = entry->overlayable_item.value();
+          const auto overlayable_section = StringPrintf(R"(name="%s" actor="%s")",
+              overlayable_item.overlayable->name.c_str(),
+              overlayable_item.overlayable->actor.c_str());
+          const auto policy_subsection = StringPrintf(R"(policies="%s")",
+              OverlayablePoliciesToString(overlayable_item.policies).c_str());
+          const auto value =
+            StringPrintf("%s/%s", to_string(type->type).data(), entry->name.c_str());
+          items.push_back(DumpOverlayableEntry{overlayable_section, policy_subsection, value});
+        }
+      }
+    }
+  }
+
+  std::sort(items.begin(), items.end(),
+      [](const DumpOverlayableEntry& a, const DumpOverlayableEntry& b) {
+        if (a.overlayable_section != b.overlayable_section) {
+          return a.overlayable_section < b.overlayable_section;
+        }
+        if (a.policy_subsection != b.policy_subsection) {
+          return a.policy_subsection < b.policy_subsection;
+        }
+        return a.resource_name < b.resource_name;
+      });
+
+  std::string last_overlayable_section;
+  std::string last_policy_subsection;
+  for (const auto& item : items) {
+    if (last_overlayable_section != item.overlayable_section) {
+      printer->Println(item.overlayable_section);
+      last_overlayable_section = item.overlayable_section;
+    }
+    if (last_policy_subsection != item.policy_subsection) {
+      printer->Indent();
+      printer->Println(item.policy_subsection);
+      last_policy_subsection = item.policy_subsection;
+      printer->Undent();
+    }
+    printer->Indent();
+    printer->Indent();
+    printer->Println(item.resource_name);
+    printer->Undent();
+    printer->Undent();
+  }
+}
+
 }  // namespace aapt
diff --git a/tools/aapt2/Debug.h b/tools/aapt2/Debug.h
index a43197c..9443d60 100644
--- a/tools/aapt2/Debug.h
+++ b/tools/aapt2/Debug.h
@@ -39,6 +39,7 @@
   static void DumpHex(const void* data, size_t len);
   static void DumpXml(const xml::XmlResource& doc, text::Printer* printer);
   static void DumpResStringPool(const android::ResStringPool* pool, text::Printer* printer);
+  static void DumpOverlayable(const ResourceTable& table, text::Printer* printer);
 };
 
 }  // namespace aapt
diff --git a/tools/aapt2/cmd/Dump.cpp b/tools/aapt2/cmd/Dump.cpp
index 429aff1..3982d12 100644
--- a/tools/aapt2/cmd/Dump.cpp
+++ b/tools/aapt2/cmd/Dump.cpp
@@ -394,6 +394,17 @@
   return 0;
 }
 
+int DumpOverlayableCommand::Dump(LoadedApk* apk) {
+  ResourceTable* table = apk->GetResourceTable();
+  if (!table) {
+    GetDiagnostics()->Error(DiagMessage() << "Failed to retrieve resource table");
+    return 1;
+  }
+
+  Debug::DumpOverlayable(*table, GetPrinter());
+  return 0;
+}
+
 const char DumpBadgerCommand::kBadgerData[2925] = {
     32,  32,  32,  32,  32,  32,  32,  32,  32,  32,  32,  32,  32,  32,  32,  32,  32,  32,  32,
     32,  32,  32,  32,  32,  32,  95,  46,  32,  32,  32,  32,  32,  32,  32,  32,  32,  32,  32,
diff --git a/tools/aapt2/cmd/Dump.h b/tools/aapt2/cmd/Dump.h
index 7ded9bc..cd51f7a 100644
--- a/tools/aapt2/cmd/Dump.h
+++ b/tools/aapt2/cmd/Dump.h
@@ -240,6 +240,16 @@
   std::vector<std::string> files_;
 };
 
+class DumpOverlayableCommand : public DumpApkCommand {
+ public:
+  explicit DumpOverlayableCommand(text::Printer* printer, IDiagnostics* diag)
+      : DumpApkCommand("overlayable", printer, diag) {
+    SetDescription("Print the <overlayable> resources of an APK.");
+  }
+
+  int Dump(LoadedApk* apk) override;
+};
+
 /** The default dump command. Performs no action because a subcommand is required. */
 class DumpCommand : public Command {
  public:
@@ -255,8 +265,8 @@
     AddOptionalSubcommand(util::make_unique<DumpTableCommand>(printer, diag_));
     AddOptionalSubcommand(util::make_unique<DumpXmlStringsCommand>(printer, diag_));
     AddOptionalSubcommand(util::make_unique<DumpXmlTreeCommand>(printer, diag_));
+    AddOptionalSubcommand(util::make_unique<DumpOverlayableCommand>(printer, diag_));
     AddOptionalSubcommand(util::make_unique<DumpBadgerCommand>(printer), /* hidden */ true);
-    // TODO(b/120609160): Add aapt2 overlayable dump command
   }
 
   int Action(const std::vector<std::string>& args) override {