Implementation of 'aapt2 apkinfo' command.

Bug: b/228950123
Test: Dump_test.cpp, ApkInfo_test.cpp
Change-Id: Ibc63d826df5b7e83a1e61560a2d2fcad471c128d
diff --git a/tools/aapt2/cmd/ApkInfo.cpp b/tools/aapt2/cmd/ApkInfo.cpp
new file mode 100644
index 0000000..7c9df4c
--- /dev/null
+++ b/tools/aapt2/cmd/ApkInfo.cpp
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2022 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 "ApkInfo.h"
+
+#include <fcntl.h>
+
+#include <iostream>
+#include <memory>
+
+#include "Diagnostics.h"
+#include "LoadedApk.h"
+#include "android-base/file.h"  // for O_BINARY
+#include "android-base/utf8.h"
+#include "androidfw/StringPiece.h"
+#include "dump/DumpManifest.h"
+#include "format/proto/ProtoSerialize.h"
+
+using ::android::StringPiece;
+
+namespace aapt {
+
+int ExportApkInfo(LoadedApk* apk, bool include_resource_table,
+                  const std::unordered_set<std::string>& xml_resources, pb::ApkInfo* out_apk_info,
+                  IDiagnostics* diag) {
+  auto result = DumpBadgingProto(apk, out_apk_info->mutable_badging(), diag);
+  if (result != 0) {
+    return result;
+  }
+
+  if (include_resource_table) {
+    SerializeTableToPb(*apk->GetResourceTable(), out_apk_info->mutable_resource_table(), diag);
+  }
+
+  for (auto& xml_resource : xml_resources) {
+    auto xml = apk->LoadXml(xml_resource, diag);
+    if (xml) {
+      auto out_xml = out_apk_info->add_xml_files();
+      out_xml->set_path(xml_resource);
+      SerializeXmlResourceToPb(*xml, out_xml->mutable_root(),
+                               {/* remove_empty_text_nodes= */ true});
+    }
+  }
+
+  return 0;
+}
+
+int ApkInfoCommand::Action(const std::vector<std::string>& args) {
+  if (args.size() != 1) {
+    std::cerr << "must supply a single APK\n";
+    Usage(&std::cerr);
+    return 1;
+  }
+  const StringPiece& path = args[0];
+  std::unique_ptr<LoadedApk> apk = LoadedApk::LoadApkFromPath(path, diag_);
+  if (!apk) {
+    return 1;
+  }
+
+  pb::ApkInfo out_apk_info;
+  int result =
+      ExportApkInfo(apk.get(), include_resource_table_, xml_resources_, &out_apk_info, diag_);
+  if (result != 0) {
+    diag_->Error(DiagMessage() << "Failed to serialize ApkInfo into proto.");
+    return result;
+  }
+
+  int mode = O_WRONLY | O_CREAT | O_TRUNC | O_BINARY;
+  int outfd = ::android::base::utf8::open(output_path_.c_str(), mode, 0666);
+  if (outfd == -1) {
+    diag_->Error(DiagMessage() << "Failed to open output file.");
+    return 1;
+  }
+
+  bool is_serialized = out_apk_info.SerializeToFileDescriptor(outfd);
+  close(outfd);
+
+  return is_serialized ? 0 : 1;
+}
+
+}  // namespace aapt
\ No newline at end of file
diff --git a/tools/aapt2/cmd/ApkInfo.h b/tools/aapt2/cmd/ApkInfo.h
new file mode 100644
index 0000000..d682678
--- /dev/null
+++ b/tools/aapt2/cmd/ApkInfo.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#ifndef AAPT2_APKINFO_H
+#define AAPT2_APKINFO_H
+
+#include "Command.h"
+#include "Diagnostics.h"
+
+namespace aapt {
+
+class ApkInfoCommand : public Command {
+ public:
+  explicit ApkInfoCommand(IDiagnostics* diag) : Command("apkinfo"), diag_(diag) {
+    SetDescription("Dump information about an APK in binary proto format.");
+    AddRequiredFlag("-o", "Output path", &output_path_, Command::kPath);
+    AddOptionalSwitch("--include-resource-table", "Include the resource table data into output.",
+                      &include_resource_table_);
+    AddOptionalFlagList("--include-xml",
+                        "Include an XML file content into output. Multiple XML files might be "
+                        "requested during single invocation.",
+                        &xml_resources_);
+  }
+
+  int Action(const std::vector<std::string>& args) override;
+
+ private:
+  IDiagnostics* diag_;
+  std::string output_path_;
+  bool include_resource_table_ = false;
+  std::unordered_set<std::string> xml_resources_;
+};
+
+}  // namespace aapt
+
+#endif  // AAPT2_APKINFO_H
diff --git a/tools/aapt2/cmd/ApkInfo_test.cpp b/tools/aapt2/cmd/ApkInfo_test.cpp
new file mode 100644
index 0000000..70539c0
--- /dev/null
+++ b/tools/aapt2/cmd/ApkInfo_test.cpp
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2022 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 "ApkInfo.h"
+
+#include "ApkInfo.pb.h"
+#include "LoadedApk.h"
+#include "android-base/unique_fd.h"
+#include "io/StringStream.h"
+#include "test/Test.h"
+
+using testing::Eq;
+using testing::Ne;
+
+namespace aapt {
+
+using ApkInfoTest = CommandTestFixture;
+
+void AssertProducedAndExpectedInfo(const std::string& produced_path,
+                                   const std::string& expected_path) {
+  android::base::unique_fd fd(open(produced_path.c_str(), O_RDONLY));
+  ASSERT_NE(fd.get(), -1);
+
+  pb::ApkInfo produced_apk_info;
+  produced_apk_info.ParseFromFileDescriptor(fd.get());
+
+  std::string expected;
+  ::android::base::ReadFileToString(expected_path, &expected);
+
+  EXPECT_EQ(produced_apk_info.DebugString(), expected);
+}
+
+class NoopDiagnostics : public IDiagnostics {
+ public:
+  void Log(Level level, DiagMessageActual& actualMsg) override {
+  }
+};
+static NoopDiagnostics noop_diag;
+
+TEST_F(ApkInfoTest, ApkInfoWithBadging) {
+  auto apk_path = file::BuildPath(
+      {android::base::GetExecutableDirectory(), "integration-tests", "DumpTest", "components.apk"});
+  auto out_info_path = GetTestPath("apk_info.pb");
+
+  ApkInfoCommand command(&noop_diag);
+  command.Execute({"-o", out_info_path, apk_path}, &std::cerr);
+
+  auto expected_path =
+      file::BuildPath({android::base::GetExecutableDirectory(), "integration-tests", "DumpTest",
+                       "components_expected_proto.txt"});
+  AssertProducedAndExpectedInfo(out_info_path, expected_path);
+}
+
+TEST_F(ApkInfoTest, FullApkInfo) {
+  auto apk_path = file::BuildPath(
+      {android::base::GetExecutableDirectory(), "integration-tests", "DumpTest", "components.apk"});
+  auto out_info_path = GetTestPath("apk_info.pb");
+
+  ApkInfoCommand command(&noop_diag);
+  command.Execute({"-o", out_info_path, "--include-resource-table", "--include-xml",
+                   "AndroidManifest.xml", "--include-xml", "res/oy.xml", apk_path},
+                  &std::cerr);
+
+  auto expected_path =
+      file::BuildPath({android::base::GetExecutableDirectory(), "integration-tests", "DumpTest",
+                       "components_full_proto.txt"});
+  AssertProducedAndExpectedInfo(out_info_path, expected_path);
+}
+
+}  // namespace aapt
\ No newline at end of file