AAPT2: Add -A (assets) support

Looks like the build system doesn't support assets/ for
resources, so we will re-introduce them in aapt2, even though
we're just copying them around and they would be better
suited for inclusion in the APK when classes.dex gets inserted.

Bug: 35461578
Test: CTS test android.content.res.cts.AssetManager#testAssetOperations should pass
Change-Id: I18361d7367d476806bcf7154ee76df3f0e83b565
diff --git a/tools/aapt2/integration-tests/AppOne/Android.mk b/tools/aapt2/integration-tests/AppOne/Android.mk
index a6f32d4..38bd5b5 100644
--- a/tools/aapt2/integration-tests/AppOne/Android.mk
+++ b/tools/aapt2/integration-tests/AppOne/Android.mk
@@ -21,6 +21,7 @@
 LOCAL_PACKAGE_NAME := AaptTestAppOne
 LOCAL_MODULE_TAGS := tests
 LOCAL_SRC_FILES := $(call all-java-files-under,src)
+LOCAL_ASSET_DIR := $(LOCAL_PATH)/assets $(LOCAL_PATH)/assets2
 LOCAL_STATIC_ANDROID_LIBRARIES := \
     AaptTestStaticLibOne \
     AaptTestStaticLibTwo
diff --git a/tools/aapt2/integration-tests/AppOne/assets/subdir/subsubdir/test.txt b/tools/aapt2/integration-tests/AppOne/assets/subdir/subsubdir/test.txt
new file mode 100644
index 0000000..1251949
--- /dev/null
+++ b/tools/aapt2/integration-tests/AppOne/assets/subdir/subsubdir/test.txt
@@ -0,0 +1 @@
+subdir/subsubdir/test.txt comes from assets
diff --git a/tools/aapt2/integration-tests/AppOne/assets/test.txt b/tools/aapt2/integration-tests/AppOne/assets/test.txt
new file mode 100644
index 0000000..88266de
--- /dev/null
+++ b/tools/aapt2/integration-tests/AppOne/assets/test.txt
@@ -0,0 +1 @@
+test.txt came from assets
diff --git a/tools/aapt2/integration-tests/AppOne/assets2/new.txt b/tools/aapt2/integration-tests/AppOne/assets2/new.txt
new file mode 100644
index 0000000..f4963a9
--- /dev/null
+++ b/tools/aapt2/integration-tests/AppOne/assets2/new.txt
@@ -0,0 +1 @@
+new.txt came from assets2
diff --git a/tools/aapt2/integration-tests/AppOne/assets2/test.txt b/tools/aapt2/integration-tests/AppOne/assets2/test.txt
new file mode 100644
index 0000000..5d8b36c
--- /dev/null
+++ b/tools/aapt2/integration-tests/AppOne/assets2/test.txt
@@ -0,0 +1 @@
+test.txt came from assets2
diff --git a/tools/aapt2/link/Link.cpp b/tools/aapt2/link/Link.cpp
index c8f0217..1042111 100644
--- a/tools/aapt2/link/Link.cpp
+++ b/tools/aapt2/link/Link.cpp
@@ -77,6 +77,7 @@
   std::string manifest_path;
   std::vector<std::string> include_paths;
   std::vector<std::string> overlay_files;
+  std::vector<std::string> assets_dirs;
   bool output_to_directory = false;
   bool auto_add_overlay = false;
 
@@ -1412,6 +1413,46 @@
     return doc;
   }
 
+  bool CopyAssetsDirsToApk(IArchiveWriter* writer) {
+    std::map<std::string, std::unique_ptr<io::RegularFile>> merged_assets;
+    for (const std::string& assets_dir : options_.assets_dirs) {
+      Maybe<std::vector<std::string>> files =
+          file::FindFiles(assets_dir, context_->GetDiagnostics(), nullptr);
+      if (!files) {
+        return false;
+      }
+
+      for (const std::string& file : files.value()) {
+        std::string full_key = "assets/" + file;
+        std::string full_path = assets_dir;
+        file::AppendPath(&full_path, file);
+
+        auto iter = merged_assets.find(full_key);
+        if (iter == merged_assets.end()) {
+          merged_assets.emplace(std::move(full_key),
+                                util::make_unique<io::RegularFile>(Source(std::move(full_path))));
+        } else if (context_->IsVerbose()) {
+          context_->GetDiagnostics()->Warn(DiagMessage(iter->second->GetSource())
+                                           << "asset file overrides '" << full_path << "'");
+        }
+      }
+    }
+
+    for (auto& entry : merged_assets) {
+      uint32_t compression_flags = ArchiveEntry::kCompress;
+      std::string extension = file::GetExtension(entry.first).to_string();
+      if (options_.extensions_to_not_compress.count(extension) > 0) {
+        compression_flags = 0u;
+      }
+
+      if (!CopyFileToArchive(entry.second.get(), entry.first, compression_flags, writer,
+                             context_)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
   /**
    * Writes the AndroidManifest, ResourceTable, and all XML files referenced by
    * the ResourceTable to the IArchiveWriter.
@@ -1724,11 +1765,9 @@
     }
 
     // Start writing the base APK.
-    std::unique_ptr<IArchiveWriter> archive_writer =
-        MakeArchiveWriter(options_.output_path);
+    std::unique_ptr<IArchiveWriter> archive_writer = MakeArchiveWriter(options_.output_path);
     if (!archive_writer) {
-      context_->GetDiagnostics()->Error(DiagMessage()
-                                        << "failed to create archive");
+      context_->GetDiagnostics()->Error(DiagMessage() << "failed to create archive");
       return 1;
     }
 
@@ -1743,16 +1782,15 @@
       XmlReferenceLinker manifest_linker;
       if (manifest_linker.Consume(context_, manifest_xml.get())) {
         if (options_.generate_proguard_rules_path &&
-            !proguard::CollectProguardRulesForManifest(
-                Source(options_.manifest_path), manifest_xml.get(),
-                &proguard_keep_set)) {
+            !proguard::CollectProguardRulesForManifest(Source(options_.manifest_path),
+                                                       manifest_xml.get(), &proguard_keep_set)) {
           error = true;
         }
 
         if (options_.generate_main_dex_proguard_rules_path &&
-            !proguard::CollectProguardRulesForManifest(
-                Source(options_.manifest_path), manifest_xml.get(),
-                &proguard_main_dex_keep_set, true)) {
+            !proguard::CollectProguardRulesForManifest(Source(options_.manifest_path),
+                                                       manifest_xml.get(),
+                                                       &proguard_main_dex_keep_set, true)) {
           error = true;
         }
 
@@ -1776,13 +1814,15 @@
     }
 
     if (error) {
-      context_->GetDiagnostics()->Error(DiagMessage()
-                                        << "failed processing manifest");
+      context_->GetDiagnostics()->Error(DiagMessage() << "failed processing manifest");
       return 1;
     }
 
-    if (!WriteApk(archive_writer.get(), &proguard_keep_set, manifest_xml.get(),
-                  &final_table_)) {
+    if (!WriteApk(archive_writer.get(), &proguard_keep_set, manifest_xml.get(), &final_table_)) {
+      return 1;
+    }
+
+    if (!CopyAssetsDirsToApk(archive_writer.get())) {
       return 1;
     }
 
@@ -1863,12 +1903,6 @@
                            proguard_main_dex_keep_set)) {
       return 1;
     }
-
-    if (context_->IsVerbose()) {
-      DebugPrintTableOptions debug_print_table_options;
-      debug_print_table_options.show_sources = true;
-      Debug::PrintTable(&final_table_, debug_print_table_options);
-    }
     return 0;
   }
 
@@ -1916,6 +1950,9 @@
           .RequiredFlag("--manifest", "Path to the Android manifest to build",
                         &options.manifest_path)
           .OptionalFlagList("-I", "Adds an Android APK to link against", &options.include_paths)
+          .OptionalFlagList("-A",
+                            "An assets directory to include in the APK. These are unprocessed.",
+                            &options.assets_dirs)
           .OptionalFlagList("-R",
                             "Compilation unit to link, using `overlay` semantics.\n"
                             "The last conflicting resource given takes precedence.",
diff --git a/tools/aapt2/util/Files.cpp b/tools/aapt2/util/Files.cpp
index aa840e2..d10351b 100644
--- a/tools/aapt2/util/Files.cpp
+++ b/tools/aapt2/util/Files.cpp
@@ -264,5 +264,57 @@
   return true;
 }
 
+Maybe<std::vector<std::string>> FindFiles(const android::StringPiece& path, IDiagnostics* diag,
+                                          const FileFilter* filter) {
+  const std::string root_dir = path.to_string();
+  std::unique_ptr<DIR, decltype(closedir)*> d(opendir(root_dir.data()), closedir);
+  if (!d) {
+    diag->Error(DiagMessage() << android::base::SystemErrorCodeToString(errno));
+    return {};
+  }
+
+  std::vector<std::string> files;
+  std::vector<std::string> subdirs;
+  while (struct dirent* entry = readdir(d.get())) {
+    if (util::StartsWith(entry->d_name, ".")) {
+      continue;
+    }
+
+    std::string file_name = entry->d_name;
+    std::string full_path = root_dir;
+    AppendPath(&full_path, file_name);
+    const FileType file_type = GetFileType(full_path);
+
+    if (filter != nullptr) {
+      if (!(*filter)(file_name, file_type)) {
+        continue;
+      }
+    }
+
+    if (file_type == file::FileType::kDirectory) {
+      subdirs.push_back(std::move(file_name));
+    } else {
+      files.push_back(std::move(file_name));
+    }
+  }
+
+  // Now process subdirs.
+  for (const std::string& subdir : subdirs) {
+    std::string full_subdir = root_dir;
+    AppendPath(&full_subdir, subdir);
+    Maybe<std::vector<std::string>> subfiles = FindFiles(full_subdir, diag, filter);
+    if (!subfiles) {
+      return {};
+    }
+
+    for (const std::string& subfile : subfiles.value()) {
+      std::string new_file = subdir;
+      AppendPath(&new_file, subfile);
+      files.push_back(new_file);
+    }
+  }
+  return files;
+}
+
 }  // namespace file
 }  // namespace aapt
diff --git a/tools/aapt2/util/Files.h b/tools/aapt2/util/Files.h
index 95c492f..b3b1e48 100644
--- a/tools/aapt2/util/Files.h
+++ b/tools/aapt2/util/Files.h
@@ -132,6 +132,11 @@
   std::vector<std::string> pattern_tokens_;
 };
 
+// Returns a list of files relative to the directory identified by `path`.
+// An optional FileFilter filters out any files that don't pass.
+Maybe<std::vector<std::string>> FindFiles(const android::StringPiece& path, IDiagnostics* diag,
+                                          const FileFilter* filter = nullptr);
+
 }  // namespace file
 }  // namespace aapt