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/api/current.txt b/api/current.txt
index 861d6e8..5ae6590 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -12871,6 +12871,7 @@
method @Nullable public android.content.res.loader.AssetsProvider getAssetsProvider();
method @NonNull public static android.content.res.loader.ResourcesProvider loadFromApk(@NonNull android.os.ParcelFileDescriptor) throws java.io.IOException;
method @NonNull public static android.content.res.loader.ResourcesProvider loadFromApk(@NonNull android.os.ParcelFileDescriptor, @Nullable android.content.res.loader.AssetsProvider) throws java.io.IOException;
+ method @NonNull public static android.content.res.loader.ResourcesProvider loadFromDirectory(@NonNull String, @Nullable android.content.res.loader.AssetsProvider) throws java.io.IOException;
method @NonNull public static android.content.res.loader.ResourcesProvider loadFromSplit(@NonNull android.content.Context, @NonNull String) throws java.io.IOException;
method @NonNull public static android.content.res.loader.ResourcesProvider loadFromTable(@NonNull android.os.ParcelFileDescriptor, @Nullable android.content.res.loader.AssetsProvider) throws java.io.IOException;
}
diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java
index b9dad85..078d175 100644
--- a/core/java/android/content/res/ApkAssets.java
+++ b/core/java/android/content/res/ApkAssets.java
@@ -86,11 +86,15 @@
/** The path used to load the apk assets represents an resources.arsc file. */
private static final int FORMAT_ARSC = 2;
+ /** the path used to load the apk assets represents a directory. */
+ private static final int FORMAT_DIR = 3;
+
// Format types that change how the apk assets are loaded.
@IntDef(prefix = { "FORMAT_" }, value = {
FORMAT_APK,
FORMAT_IDMAP,
FORMAT_ARSC,
+ FORMAT_DIR
})
@Retention(RetentionPolicy.SOURCE)
public @interface FormatType {}
@@ -227,6 +231,20 @@
}
/**
+ * Creates a new ApkAssets instance from the given directory path. The directory should have the
+ * file structure of an APK.
+ *
+ * @param path The path to a directory on disk.
+ * @param flags flags that change the behavior of loaded apk assets
+ * @return a new instance of ApkAssets.
+ * @throws IOException if a disk I/O error or parsing error occurred.
+ */
+ public static @NonNull ApkAssets loadFromDir(@NonNull String path,
+ @PropertyFlags int flags) throws IOException {
+ return new ApkAssets(FORMAT_DIR, path, flags);
+ }
+
+ /**
* Generates an entirely empty ApkAssets. Needed because the ApkAssets instance and presence
* is required for a lot of APIs, and it's easier to have a non-null reference rather than
* tracking a separate identifier.
diff --git a/core/java/android/content/res/loader/ResourcesProvider.java b/core/java/android/content/res/loader/ResourcesProvider.java
index 1d24dda..040b369 100644
--- a/core/java/android/content/res/loader/ResourcesProvider.java
+++ b/core/java/android/content/res/loader/ResourcesProvider.java
@@ -212,6 +212,22 @@
null);
}
+ /**
+ * Creates a ResourcesProvider from a directory path.
+ *
+ * File-based resources will be resolved within the directory as if the directory is an APK.
+ *
+ * @param path the path of the directory to treat as an APK
+ * @param assetsProvider the assets provider that overrides the loading of file-based resources
+ */
+ @NonNull
+ public static ResourcesProvider loadFromDirectory(@NonNull String path,
+ @Nullable AssetsProvider assetsProvider) throws IOException {
+ return new ResourcesProvider(ApkAssets.loadFromDir(path, ApkAssets.PROPERTY_LOADER),
+ assetsProvider);
+ }
+
+
private ResourcesProvider(@NonNull ApkAssets apkAssets,
@Nullable AssetsProvider assetsProvider) {
this.mApkAssets = apkAssets;
diff --git a/core/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp
index 44f8a31..6acb133 100644
--- a/core/jni/android_content_res_ApkAssets.cpp
+++ b/core/jni/android_content_res_ApkAssets.cpp
@@ -48,6 +48,9 @@
// The path used to load the apk assets represents an resources.arsc file.
FORMAT_ARSC = 2,
+
+ // The path used to load the apk assets represents the a directory.
+ FORMAT_DIRECTORY = 3,
};
static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, const format_type_t format,
@@ -70,6 +73,9 @@
case FORMAT_ARSC:
apk_assets = ApkAssets::LoadTable(path.c_str(), property_flags);
break;
+ case FORMAT_DIRECTORY:
+ apk_assets = ApkAssets::LoadFromDir(path.c_str(), property_flags);
+ break;
default:
const std::string error_msg = base::StringPrintf("Unsupported format type %d", format);
jniThrowException(env, "java/lang/IllegalArgumentException", error_msg.c_str());
@@ -224,8 +230,8 @@
}
const ApkAssets* apk_assets = reinterpret_cast<const ApkAssets*>(ptr);
- std::unique_ptr<Asset> asset = apk_assets->Open(path_utf8.c_str(),
- Asset::AccessMode::ACCESS_RANDOM);
+ std::unique_ptr<Asset> asset = apk_assets->GetAssetsProvider()->Open(
+ path_utf8.c_str(),Asset::AccessMode::ACCESS_RANDOM);
if (asset == nullptr) {
jniThrowException(env, "java/io/FileNotFoundException", path_utf8.c_str());
return 0;
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderTestBase.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderTestBase.kt
index f88878d..bd2bac5 100644
--- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderTestBase.kt
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderTestBase.kt
@@ -28,6 +28,8 @@
import org.junit.Before
import java.io.Closeable
import java.io.FileOutputStream
+import java.io.File
+import java.util.zip.ZipInputStream
abstract class ResourceLoaderTestBase {
protected val PROVIDER_ONE: String = "FrameworksResourceLoaderTests_ProviderOne"
@@ -134,8 +136,42 @@
DataType.SPLIT -> {
ResourcesProvider.loadFromSplit(context, "${this}_Split")
}
+ DataType.DIRECTORY -> {
+ ResourcesProvider.loadFromDirectory(zipToDir("${this}.apk").absolutePath, null)
+ }
}
+
+ /** Extracts an archive-based asset into a directory on disk. */
+ private fun zipToDir(name : String, suffix : String = "") : File {
+ val root = File(context.filesDir, name.split('.')[0] + suffix)
+ if (root.exists()) {
+ return root
+ }
+
+ root.mkdir()
+ ZipInputStream(context.assets.open(name)).use { zis ->
+ while (true) {
+ val entry = zis.nextEntry ?: break
+ val file = File(root, entry.name)
+ if (entry.isDirectory) {
+ continue
+ }
+
+ file.parentFile.mkdirs()
+ file.outputStream().use { output ->
+ var b = zis.read()
+ while (b != -1) {
+ output.write(b)
+ b = zis.read()
+ }
+ }
+ }
+ }
+ return root
+ }
+
+
/** Loads the asset into a temporary file stored in RAM. */
private fun loadAssetIntoMemory(asset: AssetFileDescriptor,
leadingGarbageSize: Int = 0,
@@ -175,5 +211,6 @@
ARSC_RAM_MEMORY,
ARSC_RAM_MEMORY_OFFSETS,
SPLIT,
+ DIRECTORY
}
}
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt
index 11295f3..c01db0d7 100644
--- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt
+++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt
@@ -104,7 +104,7 @@
listOf(DataType.APK_DISK_FD, DataType.APK_DISK_FD_OFFSETS, DataType.APK_RAM_FD,
DataType.APK_RAM_OFFSETS, DataType.ARSC_DISK_FD,
DataType.ARSC_DISK_FD_OFFSETS, DataType.ARSC_RAM_MEMORY,
- DataType.ARSC_RAM_MEMORY_OFFSETS, DataType.SPLIT)
+ DataType.ARSC_RAM_MEMORY_OFFSETS, DataType.SPLIT, DataType.DIRECTORY)
)
// Test resolution of file-based resources and assets with no assets provider.
@@ -164,7 +164,7 @@
"layout" to "TableLayout",
"drawablePng" to Color.WHITE.toString()),
listOf(DataType.APK_DISK_FD, DataType.APK_DISK_FD_OFFSETS, DataType.APK_RAM_FD,
- DataType.APK_RAM_OFFSETS, DataType.SPLIT)
+ DataType.APK_RAM_OFFSETS, DataType.SPLIT, DataType.DIRECTORY)
)
return parameters.flatMap { parameter ->
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;
diff --git a/startop/view_compiler/apk_layout_compiler.cc b/startop/view_compiler/apk_layout_compiler.cc
index 09cdbd5..e70c688 100644
--- a/startop/view_compiler/apk_layout_compiler.cc
+++ b/startop/view_compiler/apk_layout_compiler.cc
@@ -100,10 +100,12 @@
dex_file.MakeClass(StringPrintf("%s.CompiledView", package_name.c_str()))};
std::vector<dex::MethodBuilder> methods;
- assets->ForEachFile("res/", [&](const android::StringPiece& s, android::FileType) {
+ assets->GetAssetsProvider()->ForEachFile("res/", [&](const android::StringPiece& s,
+ android::FileType) {
if (s == "layout") {
auto path = StringPrintf("res/%s/", s.to_string().c_str());
- assets->ForEachFile(path, [&](const android::StringPiece& layout_file, android::FileType) {
+ assets->GetAssetsProvider()->ForEachFile(path, [&](const android::StringPiece& layout_file,
+ android::FileType) {
auto layout_path = StringPrintf("%s%s", path.c_str(), layout_file.to_string().c_str());
android::ApkAssetsCookie cookie = android::kInvalidCookie;
auto asset = resources.OpenNonAsset(layout_path, android::Asset::ACCESS_RANDOM, &cookie);