Add ResourcesProvider.loadFromDirectory

This API allows a directory to be loaded as if it was a zipped APK.
This is a substitute for the DirectoryAssetProvider API that
currently does not work in the native layer.

Bug: 142716192
Test: atest FrameworksResourceLoaderTests
Change-Id: Ia13e15653e75b421423dd56f9fe89e183ab4cb9a
diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp
index 946fcc0..f5bf84f 100644
--- a/libs/androidfw/ApkAssets.cpp
+++ b/libs/androidfw/ApkAssets.cpp
@@ -21,6 +21,7 @@
 #include "android-base/errors.h"
 #include "android-base/file.h"
 #include "android-base/logging.h"
+#include "android-base/stringprintf.h"
 #include "android-base/unique_fd.h"
 #include "android-base/utf8.h"
 #include "utils/Compat.h"
@@ -40,28 +41,263 @@
 
 static const std::string kResourcesArsc("resources.arsc");
 
-ApkAssets::ApkAssets(ZipArchiveHandle unmanaged_handle,
+ApkAssets::ApkAssets(std::unique_ptr<const AssetsProvider> assets_provider,
                      std::string path,
                      time_t last_mod_time,
                      package_property_t property_flags)
-    : zip_handle_(unmanaged_handle, ::CloseArchive),
+    : assets_provider_(std::move(assets_provider)),
       path_(std::move(path)),
       last_mod_time_(last_mod_time),
       property_flags_(property_flags) {
 }
 
-std::unique_ptr<const ApkAssets> ApkAssets::Load(const std::string& path,
-                                                 const package_property_t flags) {
-  ::ZipArchiveHandle unmanaged_handle;
-  const int32_t result = ::OpenArchive(path.c_str(), &unmanaged_handle);
-  if (result != 0) {
-    LOG(ERROR) << "Failed to open APK '" << path << "' " << ::ErrorCodeString(result);
-    ::CloseArchive(unmanaged_handle);
-    return {};
+// Provides asset files from a zip file.
+class ZipAssetsProvider : public AssetsProvider {
+ public:
+  ~ZipAssetsProvider() override = default;
+
+  static std::unique_ptr<const AssetsProvider> Create(const std::string& path) {
+    ::ZipArchiveHandle unmanaged_handle;
+    const int32_t result = ::OpenArchive(path.c_str(), &unmanaged_handle);
+    if (result != 0) {
+      LOG(ERROR) << "Failed to open APK '" << path << "' " << ::ErrorCodeString(result);
+      ::CloseArchive(unmanaged_handle);
+      return {};
+    }
+
+    return std::unique_ptr<AssetsProvider>(new ZipAssetsProvider(path, path, unmanaged_handle));
   }
 
-  return LoadImpl(unmanaged_handle,  path, nullptr /*idmap_asset*/, nullptr /*loaded_idmap*/,
-                  flags);
+  static std::unique_ptr<const AssetsProvider> Create(
+      unique_fd fd, const std::string& friendly_name, const off64_t offset = 0,
+      const off64_t length = ApkAssets::kUnknownLength) {
+
+    ::ZipArchiveHandle unmanaged_handle;
+    const int32_t result = (length == ApkAssets::kUnknownLength)
+        ? ::OpenArchiveFd(fd.release(), friendly_name.c_str(), &unmanaged_handle)
+        : ::OpenArchiveFdRange(fd.release(), friendly_name.c_str(), &unmanaged_handle, length,
+                               offset);
+
+    if (result != 0) {
+      LOG(ERROR) << "Failed to open APK '" << friendly_name << "' through FD with offset " << offset
+                 << " and length " << length << ": " << ::ErrorCodeString(result);
+      ::CloseArchive(unmanaged_handle);
+      return {};
+    }
+
+    return std::unique_ptr<AssetsProvider>(new ZipAssetsProvider({}, friendly_name,
+                                                                 unmanaged_handle));
+  }
+
+  // Iterate over all files and directories within the zip. The order of iteration is not
+  // guaranteed to be the same as the order of elements in the central directory but is stable for a
+  // given zip file.
+  bool ForEachFile(const std::string& root_path,
+                   const std::function<void(const StringPiece&, FileType)>& f) const override {
+    // If this is a resource loader from an .arsc, there will be no zip handle
+    if (zip_handle_ == nullptr) {
+      return false;
+    }
+
+    std::string root_path_full = root_path;
+    if (root_path_full.back() != '/') {
+      root_path_full += '/';
+    }
+
+    void* cookie;
+    if (::StartIteration(zip_handle_.get(), &cookie, root_path_full, "") != 0) {
+      return false;
+    }
+
+    std::string name;
+    ::ZipEntry entry{};
+
+    // We need to hold back directories because many paths will contain them and we want to only
+    // surface one.
+    std::set<std::string> dirs{};
+
+    int32_t result;
+    while ((result = ::Next(cookie, &entry, &name)) == 0) {
+      StringPiece full_file_path(name);
+      StringPiece leaf_file_path = full_file_path.substr(root_path_full.size());
+
+      if (!leaf_file_path.empty()) {
+        auto iter = std::find(leaf_file_path.begin(), leaf_file_path.end(), '/');
+        if (iter != leaf_file_path.end()) {
+          std::string dir =
+              leaf_file_path.substr(0, std::distance(leaf_file_path.begin(), iter)).to_string();
+          dirs.insert(std::move(dir));
+        } else {
+          f(leaf_file_path, kFileTypeRegular);
+        }
+      }
+    }
+    ::EndIteration(cookie);
+
+    // Now present the unique directories.
+    for (const std::string& dir : dirs) {
+      f(dir, kFileTypeDirectory);
+    }
+
+    // -1 is end of iteration, anything else is an error.
+    return result == -1;
+  }
+
+ protected:
+    std::unique_ptr<Asset> OpenInternal(
+        const std::string& path, Asset::AccessMode mode, bool* file_exists) const override {
+    if (file_exists) {
+      *file_exists = false;
+    }
+
+    ::ZipEntry entry;
+    int32_t result = ::FindEntry(zip_handle_.get(), path, &entry);
+    if (result != 0) {
+      return {};
+    }
+
+    if (file_exists) {
+      *file_exists = true;
+    }
+
+    const int fd = ::GetFileDescriptor(zip_handle_.get());
+     const off64_t fd_offset = ::GetFileDescriptorOffset(zip_handle_.get());
+    if (entry.method == kCompressDeflated) {
+      std::unique_ptr<FileMap> map = util::make_unique<FileMap>();
+      if (!map->create(GetPath(), fd, entry.offset + fd_offset, entry.compressed_length,
+                       true /*readOnly*/)) {
+        LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << friendly_name_ << "'";
+        return {};
+      }
+
+      std::unique_ptr<Asset> asset =
+          Asset::createFromCompressedMap(std::move(map), entry.uncompressed_length, mode);
+      if (asset == nullptr) {
+        LOG(ERROR) << "Failed to decompress '" << path << "' in APK '" << friendly_name_ << "'";
+        return {};
+      }
+      return asset;
+    } else {
+      std::unique_ptr<FileMap> map = util::make_unique<FileMap>();
+      if (!map->create(GetPath(), fd, entry.offset + fd_offset, entry.uncompressed_length,
+                       true /*readOnly*/)) {
+        LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << friendly_name_ << "'";
+        return {};
+      }
+
+      unique_fd ufd;
+      if (!GetPath()) {
+        // If the `path` is not set, create a new `fd` for the new Asset to own in order to create
+        // new file descriptors using Asset::openFileDescriptor. If the path is set, it will be used
+        // to create new file descriptors.
+        ufd = unique_fd(dup(fd));
+        if (!ufd.ok()) {
+          LOG(ERROR) << "Unable to dup fd '" << path << "' in APK '" << friendly_name_ << "'";
+          return {};
+        }
+      }
+
+      std::unique_ptr<Asset> asset = Asset::createFromUncompressedMap(std::move(map),
+          std::move(ufd), mode);
+      if (asset == nullptr) {
+        LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << friendly_name_ << "'";
+        return {};
+      }
+      return asset;
+    }
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(ZipAssetsProvider);
+
+  explicit ZipAssetsProvider(std::string path,
+                             std::string friendly_name,
+                             ZipArchiveHandle unmanaged_handle)
+                             : zip_handle_(unmanaged_handle, ::CloseArchive),
+                               path_(std::move(path)),
+                               friendly_name_(std::move(friendly_name)) { }
+
+  const char* GetPath() const {
+    return path_.empty() ? nullptr : path_.c_str();
+  }
+
+  using ZipArchivePtr = std::unique_ptr<ZipArchive, void (*)(ZipArchiveHandle)>;
+  ZipArchivePtr zip_handle_;
+  std::string path_;
+  std::string friendly_name_;
+};
+
+class DirectoryAssetsProvider : AssetsProvider {
+ public:
+  ~DirectoryAssetsProvider() override = default;
+
+  static std::unique_ptr<const AssetsProvider> Create(const std::string& path) {
+    struct stat sb{};
+    const int result = stat(path.c_str(), &sb);
+    if (result == -1) {
+      LOG(ERROR) << "Failed to find directory '" << path << "'.";
+      return nullptr;
+    }
+
+    if (!S_ISDIR(sb.st_mode)) {
+      LOG(ERROR) << "Path '" << path << "' is not a directory.";
+      return nullptr;
+    }
+
+    return std::unique_ptr<AssetsProvider>(new DirectoryAssetsProvider(path));
+  }
+
+ protected:
+  std::unique_ptr<Asset> OpenInternal(
+      const std::string& path, Asset::AccessMode /* mode */, bool* file_exists) const override {
+    const std::string resolved_path = ResolvePath(path);
+    if (file_exists) {
+      struct stat sb{};
+      const int result = stat(resolved_path.c_str(), &sb);
+      *file_exists = result != -1 && S_ISREG(sb.st_mode);
+    }
+
+    return ApkAssets::CreateAssetFromFile(resolved_path);
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(DirectoryAssetsProvider);
+
+  explicit DirectoryAssetsProvider(std::string path) : path_(std::move(path)) { }
+
+  inline std::string ResolvePath(const std::string& path) const {
+    return base::StringPrintf("%s%c%s", path_.c_str(), OS_PATH_SEPARATOR, path.c_str());
+  }
+
+  const std::string path_;
+};
+
+// AssetProvider implementation that does not provide any assets. Used for ApkAssets::LoadEmpty.
+class EmptyAssetsProvider : public AssetsProvider {
+ public:
+  EmptyAssetsProvider() = default;
+  ~EmptyAssetsProvider() override = default;
+
+ protected:
+  std::unique_ptr<Asset> OpenInternal(const std::string& /*path */,
+                                      Asset::AccessMode /* mode */,
+                                      bool* file_exists) const override {
+    if (file_exists) {
+      *file_exists = false;
+    }
+    return nullptr;
+  }
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(EmptyAssetsProvider);
+};
+
+std::unique_ptr<const ApkAssets> ApkAssets::Load(const std::string& path,
+                                                 const package_property_t flags) {
+  auto assets = ZipAssetsProvider::Create(path);
+  return (assets) ? LoadImpl(std::move(assets), path, nullptr /*idmap_asset*/,
+                             nullptr /*loaded_idmap*/, flags)
+                  : nullptr;
 }
 
 std::unique_ptr<const ApkAssets> ApkAssets::LoadFromFd(unique_fd fd,
@@ -72,33 +308,17 @@
   CHECK(length >= kUnknownLength) << "length must be greater than or equal to " << kUnknownLength;
   CHECK(length != kUnknownLength || offset == 0) << "offset must be 0 if length is "
                                                  << kUnknownLength;
-
-  ::ZipArchiveHandle unmanaged_handle;
-  const int32_t result = (length == kUnknownLength)
-      ? ::OpenArchiveFd(fd.release(), friendly_name.c_str(), &unmanaged_handle)
-      : ::OpenArchiveFdRange(fd.release(), friendly_name.c_str(), &unmanaged_handle, length,
-                             offset);
-
-  if (result != 0) {
-    LOG(ERROR) << "Failed to open APK '" << friendly_name << "' through FD with offset " << offset
-               << " and length " << length << ": " << ::ErrorCodeString(result);
-    ::CloseArchive(unmanaged_handle);
-    return {};
-  }
-
-  return LoadImpl(unmanaged_handle, friendly_name, nullptr /*idmap_asset*/,
-                  nullptr /*loaded_idmap*/, flags);
+  auto assets = ZipAssetsProvider::Create(std::move(fd), friendly_name, offset, length);
+  return (assets) ? LoadImpl(std::move(assets), friendly_name, nullptr /*idmap_asset*/,
+                             nullptr /*loaded_idmap*/, flags)
+                  : nullptr;
 }
 
 std::unique_ptr<const ApkAssets> ApkAssets::LoadTable(const std::string& path,
                                                       const package_property_t flags) {
   auto resources_asset = CreateAssetFromFile(path);
-  if (!resources_asset) {
-    LOG(ERROR) << "Failed to open ARSC '" << path;
-    return {};
-  }
-
-  return LoadTableImpl(std::move(resources_asset), path, flags);
+  return (resources_asset) ? LoadTableImpl(std::move(resources_asset), path, flags)
+                           : nullptr;
 }
 
 std::unique_ptr<const ApkAssets> ApkAssets::LoadTableFromFd(unique_fd fd,
@@ -107,13 +327,8 @@
                                                             const off64_t offset,
                                                             const off64_t length) {
   auto resources_asset = CreateAssetFromFd(std::move(fd), nullptr /* path */, offset, length);
-  if (!resources_asset) {
-    LOG(ERROR) << "Failed to open ARSC '" << friendly_name << "' through FD with offset " << offset
-               << " and length " << length;
-    return {};
-  }
-
-  return LoadTableImpl(std::move(resources_asset), friendly_name, flags);
+  return (resources_asset) ? LoadTableImpl(std::move(resources_asset), friendly_name, flags)
+                           : nullptr;
 }
 
 std::unique_ptr<const ApkAssets> ApkAssets::LoadOverlay(const std::string& idmap_path,
@@ -133,24 +348,26 @@
     LOG(ERROR) << "failed to load IDMAP " << idmap_path;
     return {};
   }
-
-
-  ::ZipArchiveHandle unmanaged_handle;
+  
   auto overlay_path = loaded_idmap->OverlayApkPath();
-  const int32_t result = ::OpenArchive(overlay_path.c_str(), &unmanaged_handle);
-  if (result != 0) {
-    LOG(ERROR) << "Failed to open overlay APK '" << overlay_path << "' "
-               << ::ErrorCodeString(result);
-    ::CloseArchive(unmanaged_handle);
-    return {};
-  }
+  auto assets = ZipAssetsProvider::Create(overlay_path);
+  return (assets) ? LoadImpl(std::move(assets), overlay_path, std::move(idmap_asset),
+                             std::move(loaded_idmap), flags | PROPERTY_OVERLAY)
+                  : nullptr;
+}
 
-  return LoadImpl(unmanaged_handle, overlay_path, std::move(idmap_asset), std::move(loaded_idmap),
-                  flags | PROPERTY_OVERLAY);
+std::unique_ptr<const ApkAssets> ApkAssets::LoadFromDir(const std::string& path,
+                                                        const package_property_t flags) {
+  auto assets = DirectoryAssetsProvider::Create(path);
+  return (assets) ? LoadImpl(std::move(assets), path, nullptr /*idmap_asset*/,
+                             nullptr /*loaded_idmap*/, flags)
+                  : nullptr;
 }
 
 std::unique_ptr<const ApkAssets> ApkAssets::LoadEmpty(const package_property_t flags) {
-  std::unique_ptr<ApkAssets> loaded_apk(new ApkAssets(nullptr, "empty", -1, flags));
+  std::unique_ptr<ApkAssets> loaded_apk(new ApkAssets(
+      std::unique_ptr<AssetsProvider>(new EmptyAssetsProvider()), "empty" /* path */,
+                                      -1 /* last_mod-time */, flags));
   loaded_apk->loaded_arsc_ = LoadedArsc::CreateEmpty();
   // Need to force a move for mingw32.
   return std::move(loaded_apk);
@@ -196,7 +413,7 @@
                                           Asset::AccessMode::ACCESS_RANDOM);
 }
 
-std::unique_ptr<const ApkAssets> ApkAssets::LoadImpl(ZipArchiveHandle unmanaged_handle,
+std::unique_ptr<const ApkAssets> ApkAssets::LoadImpl(std::unique_ptr<const AssetsProvider> assets,
                                                      const std::string& path,
                                                      std::unique_ptr<Asset> idmap_asset,
                                                      std::unique_ptr<const LoadedIdmap> idmap,
@@ -205,24 +422,19 @@
 
   // Wrap the handle in a unique_ptr so it gets automatically closed.
   std::unique_ptr<ApkAssets>
-      loaded_apk(new ApkAssets(unmanaged_handle, path, last_mod_time, property_flags));
+      loaded_apk(new ApkAssets(std::move(assets), path, last_mod_time, property_flags));
 
-  // Find the resource table.
-  ::ZipEntry entry;
-  int32_t result = ::FindEntry(loaded_apk->zip_handle_.get(), kResourcesArsc, &entry);
-  if (result != 0) {
-    // There is no resources.arsc, so create an empty LoadedArsc and return.
+  // Open the resource table via mmap unless it is compressed. This logic is taken care of by Open.
+  bool resources_asset_exists = false;
+  loaded_apk->resources_asset_ = loaded_apk->assets_provider_->Open(
+      kResourcesArsc, Asset::AccessMode::ACCESS_BUFFER, &resources_asset_exists);
+
+  if (!resources_asset_exists) {
     loaded_apk->loaded_arsc_ = LoadedArsc::CreateEmpty();
     return std::move(loaded_apk);
   }
 
-  if (entry.method == kCompressDeflated) {
-    ANDROID_LOG(WARNING) << kResourcesArsc << " in APK '" << path << "' is compressed.";
-  }
-
-  // Open the resource table via mmap unless it is compressed. This logic is taken care of by Open.
-  loaded_apk->resources_asset_ = loaded_apk->Open(kResourcesArsc, Asset::AccessMode::ACCESS_BUFFER);
-  if (loaded_apk->resources_asset_ == nullptr) {
+  if (!loaded_apk->resources_asset_) {
     LOG(ERROR) << "Failed to open '" << kResourcesArsc << "' in APK '" << path << "'.";
     return {};
   }
@@ -236,7 +448,7 @@
       loaded_apk->resources_asset_->getLength());
   loaded_apk->loaded_arsc_ = LoadedArsc::Load(data, loaded_apk->loaded_idmap_.get(),
                                               property_flags);
-  if (loaded_apk->loaded_arsc_ == nullptr) {
+  if (!loaded_apk->loaded_arsc_) {
     LOG(ERROR) << "Failed to load '" << kResourcesArsc << "' in APK '" << path << "'.";
     return {};
   }
@@ -251,7 +463,8 @@
   const time_t last_mod_time = getFileModDate(path.c_str());
 
   std::unique_ptr<ApkAssets> loaded_apk(
-      new ApkAssets(nullptr, path, last_mod_time, property_flags));
+      new ApkAssets(std::unique_ptr<AssetsProvider>(new EmptyAssetsProvider()), path, last_mod_time,
+                    property_flags));
   loaded_apk->resources_asset_ = std::move(resources_asset);
 
   const StringPiece data(
@@ -267,111 +480,9 @@
   return std::move(loaded_apk);
 }
 
-std::unique_ptr<Asset> ApkAssets::Open(const std::string& path, Asset::AccessMode mode) const {
-  // If this is a resource loader from an .arsc, there will be no zip handle
-  if (zip_handle_ == nullptr) {
-    return {};
-  }
-
-  ::ZipEntry entry;
-  int32_t result = ::FindEntry(zip_handle_.get(), path, &entry);
-  if (result != 0) {
-    return {};
-  }
-
-  const int fd = ::GetFileDescriptor(zip_handle_.get());
-  const off64_t fd_offset = ::GetFileDescriptorOffset(zip_handle_.get());
-  if (entry.method == kCompressDeflated) {
-    std::unique_ptr<FileMap> map = util::make_unique<FileMap>();
-    if (!map->create(path_.c_str(), fd, fd_offset + entry.offset, entry.compressed_length,
-                     true /*readOnly*/)) {
-      LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << path_ << "'";
-      return {};
-    }
-
-    std::unique_ptr<Asset> asset =
-        Asset::createFromCompressedMap(std::move(map), entry.uncompressed_length, mode);
-    if (asset == nullptr) {
-      LOG(ERROR) << "Failed to decompress '" << path << "'.";
-      return {};
-    }
-    return asset;
-  } else {
-    std::unique_ptr<FileMap> map = util::make_unique<FileMap>();
-    if (!map->create(path_.c_str(), fd, fd_offset + entry.offset, entry.uncompressed_length,
-                     true /*readOnly*/)) {
-      LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << path_ << "'";
-      return {};
-    }
-
-    // TODO: apks created from file descriptors residing in RAM currently cannot open file
-    //  descriptors to the assets they contain. This is because the Asset::openFileDeescriptor uses
-    //  the zip path on disk to create a new file descriptor. This is fixed in a future change
-    //  in the change topic.
-    std::unique_ptr<Asset> asset = Asset::createFromUncompressedMap(std::move(map),
-        unique_fd(-1) /* fd*/, mode);
-    if (asset == nullptr) {
-      LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << path_ << "'";
-      return {};
-    }
-    return asset;
-  }
-}
-
-bool ApkAssets::ForEachFile(const std::string& root_path,
-                            const std::function<void(const StringPiece&, FileType)>& f) const {
-  // If this is a resource loader from an .arsc, there will be no zip handle
-  if (zip_handle_ == nullptr) {
-    return false;
-  }
-
-  std::string root_path_full = root_path;
-  if (root_path_full.back() != '/') {
-    root_path_full += '/';
-  }
-
-  void* cookie;
-  if (::StartIteration(zip_handle_.get(), &cookie, root_path_full, "") != 0) {
-    return false;
-  }
-
-  std::string name;
-  ::ZipEntry entry;
-
-  // We need to hold back directories because many paths will contain them and we want to only
-  // surface one.
-  std::set<std::string> dirs;
-
-  int32_t result;
-  while ((result = ::Next(cookie, &entry, &name)) == 0) {
-    StringPiece full_file_path(name);
-    StringPiece leaf_file_path = full_file_path.substr(root_path_full.size());
-
-    if (!leaf_file_path.empty()) {
-      auto iter = std::find(leaf_file_path.begin(), leaf_file_path.end(), '/');
-      if (iter != leaf_file_path.end()) {
-        std::string dir =
-            leaf_file_path.substr(0, std::distance(leaf_file_path.begin(), iter)).to_string();
-        dirs.insert(std::move(dir));
-      } else {
-        f(leaf_file_path, kFileTypeRegular);
-      }
-    }
-  }
-  ::EndIteration(cookie);
-
-  // Now present the unique directories.
-  for (const std::string& dir : dirs) {
-    f(dir, kFileTypeDirectory);
-  }
-
-  // -1 is end of iteration, anything else is an error.
-  return result == -1;
-}
-
 bool ApkAssets::IsUpToDate() const {
   if (IsLoader()) {
-    // Loaders are invalidated by the app, not the system, so assume up to date.
+    // Loaders are invalidated by the app, not the system, so assume they are up to date.
     return true;
   }
 
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 3208662..f20e184 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -463,7 +463,7 @@
       files->add(info);
     };
 
-    if (!apk_assets->ForEachFile(full_path, func)) {
+    if (!apk_assets->GetAssetsProvider()->ForEachFile(full_path, func)) {
       return {};
     }
   }
@@ -487,7 +487,7 @@
       continue;
     }
 
-    std::unique_ptr<Asset> asset = apk_assets_[i]->Open(filename, mode);
+    std::unique_ptr<Asset> asset = apk_assets_[i]->GetAssetsProvider()->Open(filename, mode);
     if (asset) {
       if (out_cookie != nullptr) {
         *out_cookie = i;
@@ -508,7 +508,7 @@
   if (cookie < 0 || static_cast<size_t>(cookie) >= apk_assets_.size()) {
     return {};
   }
-  return apk_assets_[cookie]->Open(filename, mode);
+  return apk_assets_[cookie]->GetAssetsProvider()->Open(filename, mode);
 }
 
 ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_override,
diff --git a/libs/androidfw/include/androidfw/ApkAssets.h b/libs/androidfw/include/androidfw/ApkAssets.h
index 643dc5c..9444768 100644
--- a/libs/androidfw/include/androidfw/ApkAssets.h
+++ b/libs/androidfw/include/androidfw/ApkAssets.h
@@ -35,13 +35,46 @@
 
 class LoadedIdmap;
 
+// Interface for retrieving assets provided by an ApkAssets.
+class AssetsProvider {
+ public:
+  virtual ~AssetsProvider() = default;
+
+  // Opens a file for reading.
+  std::unique_ptr<Asset> Open(const std::string& path,
+                              Asset::AccessMode mode = Asset::AccessMode::ACCESS_RANDOM,
+                              bool* file_exists = nullptr) const {
+    return OpenInternal(path, mode, file_exists);
+  }
+
+  // Iterate over all files and directories provided by the zip. The order of iteration is stable.
+  virtual bool ForEachFile(const std::string& /* path */,
+                           const std::function<void(const StringPiece&, FileType)>& /* f */) const {
+    return true;
+  }
+
+ protected:
+  AssetsProvider() = default;
+
+  virtual std::unique_ptr<Asset> OpenInternal(const std::string& path,
+                                              Asset::AccessMode mode,
+                                              bool* file_exists) const = 0;
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(AssetsProvider);
+};
+
+class ZipAssetsProvider;
+
 // Holds an APK.
 class ApkAssets {
+ public:
   // This means the data extends to the end of the file.
   static constexpr off64_t kUnknownLength = -1;
 
- public:
-  // Creates an ApkAssets from the zip path.
+  // Creates an ApkAssets.
+  // If `system` is true, the package is marked as a system package, and allows some functions to
+  // filter out this package when computing what configurations/resources are available.
   static std::unique_ptr<const ApkAssets> Load(const std::string& path,
                                                package_property_t flags = 0U);
 
@@ -75,19 +108,22 @@
   static std::unique_ptr<const ApkAssets> LoadOverlay(const std::string& idmap_path,
                                                       package_property_t flags = 0U);
 
+  // Creates an ApkAssets from the directory path. File-based resources are read within the
+  // directory as if the directory is an APK.
+  static std::unique_ptr<const ApkAssets> LoadFromDir(const std::string& path,
+                                                      package_property_t flags = 0U);
+
   // Creates a totally empty ApkAssets with no resources table and no file entries.
   static std::unique_ptr<const ApkAssets> LoadEmpty(package_property_t flags = 0U);
 
-  std::unique_ptr<Asset> Open(const std::string& path,
-                              Asset::AccessMode mode = Asset::AccessMode::ACCESS_RANDOM) const;
-
-  bool ForEachFile(const std::string& path,
-                   const std::function<void(const StringPiece&, FileType)>& f) const;
-
   inline const std::string& GetPath() const {
     return path_;
   }
 
+  inline const AssetsProvider* GetAssetsProvider() const {
+    return assets_provider_.get();
+  }
+
   // This is never nullptr.
   inline const LoadedArsc* GetLoadedArsc() const {
     return loaded_arsc_.get();
@@ -122,7 +158,7 @@
  private:
   DISALLOW_COPY_AND_ASSIGN(ApkAssets);
 
-  static std::unique_ptr<const ApkAssets> LoadImpl(ZipArchiveHandle unmanaged_handle,
+  static std::unique_ptr<const ApkAssets> LoadImpl(std::unique_ptr<const AssetsProvider> assets,
                                                    const std::string& path,
                                                    std::unique_ptr<Asset> idmap_asset,
                                                    std::unique_ptr<const LoadedIdmap> idmap,
@@ -132,14 +168,12 @@
                                                         const std::string& path,
                                                         package_property_t property_flags);
 
-  ApkAssets(ZipArchiveHandle unmanaged_handle,
+  ApkAssets(std::unique_ptr<const AssetsProvider> assets_provider,
             std::string path,
             time_t last_mod_time,
             package_property_t property_flags);
 
-  using ZipArchivePtr = std::unique_ptr<ZipArchive, void (*)(ZipArchiveHandle)>;
-
-  ZipArchivePtr zip_handle_;
+  std::unique_ptr<const AssetsProvider> assets_provider_;
   const std::string path_;
   time_t last_mod_time_;
   package_property_t property_flags_ = 0U;
diff --git a/libs/androidfw/include/androidfw/Asset.h b/libs/androidfw/include/androidfw/Asset.h
index 7576174..298509e 100644
--- a/libs/androidfw/include/androidfw/Asset.h
+++ b/libs/androidfw/include/androidfw/Asset.h
@@ -159,6 +159,7 @@
     /* AssetManager needs access to our "create" functions */
     friend class AssetManager;
     friend class ApkAssets;
+    friend class ZipAssetsProvider;
 
     /*
      * Create the asset from a named file on disk.
diff --git a/libs/androidfw/tests/ApkAssets_test.cpp b/libs/androidfw/tests/ApkAssets_test.cpp
index 26bf5ff..ce9e532 100644
--- a/libs/androidfw/tests/ApkAssets_test.cpp
+++ b/libs/androidfw/tests/ApkAssets_test.cpp
@@ -42,7 +42,7 @@
   const LoadedArsc* loaded_arsc = loaded_apk->GetLoadedArsc();
   ASSERT_THAT(loaded_arsc, NotNull());
   ASSERT_THAT(loaded_arsc->GetPackageById(0x7fu), NotNull());
-  ASSERT_THAT(loaded_apk->Open("res/layout/main.xml"), NotNull());
+  ASSERT_THAT(loaded_apk->GetAssetsProvider()->Open("res/layout/main.xml"), NotNull());
 }
 
 TEST(ApkAssetsTest, LoadApkFromFd) {
@@ -57,7 +57,7 @@
   const LoadedArsc* loaded_arsc = loaded_apk->GetLoadedArsc();
   ASSERT_THAT(loaded_arsc, NotNull());
   ASSERT_THAT(loaded_arsc->GetPackageById(0x7fu), NotNull());
-  ASSERT_THAT(loaded_apk->Open("res/layout/main.xml"), NotNull());
+  ASSERT_THAT(loaded_apk->GetAssetsProvider()->Open("res/layout/main.xml"), NotNull());
 }
 
 TEST(ApkAssetsTest, LoadApkAsSharedLibrary) {
@@ -84,9 +84,11 @@
       ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk");
   ASSERT_THAT(loaded_apk, NotNull());
 
-  { ASSERT_THAT(loaded_apk->Open("res/layout/main.xml", Asset::ACCESS_BUFFER), NotNull()); }
+  { ASSERT_THAT(loaded_apk->GetAssetsProvider()->Open("res/layout/main.xml",
+                                                      Asset::ACCESS_BUFFER), NotNull()); }
 
-  { ASSERT_THAT(loaded_apk->Open("res/layout/main.xml", Asset::ACCESS_BUFFER), NotNull()); }
+  { ASSERT_THAT(loaded_apk->GetAssetsProvider()->Open("res/layout/main.xml",
+                                                      Asset::ACCESS_BUFFER), NotNull()); }
 }
 
 TEST(ApkAssetsTest, OpenUncompressedAssetFd) {
@@ -94,7 +96,8 @@
       ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk");
   ASSERT_THAT(loaded_apk, NotNull());
 
-  auto asset = loaded_apk->Open("assets/uncompressed.txt", Asset::ACCESS_UNKNOWN);
+  auto asset = loaded_apk->GetAssetsProvider()->Open("assets/uncompressed.txt",
+                                                     Asset::ACCESS_UNKNOWN);
   ASSERT_THAT(asset, NotNull());
 
   off64_t start, length;