diff options
| author | 2020-03-10 13:49:12 -0700 | |
|---|---|---|
| committer | 2020-03-19 18:33:55 -0700 | |
| commit | c07aa702703388747bd6e9b1091127e2736ffcd8 (patch) | |
| tree | 9d2f3a07e88e7d5a2ef367c4b8d95c81e07cb576 | |
| parent | 349695f3701e90746cb2197552299dcb99960ce8 (diff) | |
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
| -rw-r--r-- | api/current.txt | 1 | ||||
| -rw-r--r-- | core/java/android/content/res/ApkAssets.java | 18 | ||||
| -rw-r--r-- | core/java/android/content/res/loader/ResourcesProvider.java | 16 | ||||
| -rw-r--r-- | core/jni/android_content_res_ApkAssets.cpp | 10 | ||||
| -rw-r--r-- | core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderTestBase.kt | 37 | ||||
| -rw-r--r-- | core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt | 4 | ||||
| -rw-r--r-- | libs/androidfw/ApkAssets.cpp | 457 | ||||
| -rw-r--r-- | libs/androidfw/AssetManager2.cpp | 6 | ||||
| -rw-r--r-- | libs/androidfw/include/androidfw/ApkAssets.h | 60 | ||||
| -rw-r--r-- | libs/androidfw/include/androidfw/Asset.h | 1 | ||||
| -rw-r--r-- | libs/androidfw/tests/ApkAssets_test.cpp | 13 | ||||
| -rw-r--r-- | startop/view_compiler/apk_layout_compiler.cc | 6 |
12 files changed, 429 insertions, 200 deletions
diff --git a/api/current.txt b/api/current.txt index 861d6e8e5958..5ae65901fec8 100644 --- a/api/current.txt +++ b/api/current.txt @@ -12871,6 +12871,7 @@ package android.content.res.loader { 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 b9dad85fe84c..078d175b9a80 100644 --- a/core/java/android/content/res/ApkAssets.java +++ b/core/java/android/content/res/ApkAssets.java @@ -86,11 +86,15 @@ public final class ApkAssets { /** 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 @@ public final class ApkAssets { } /** + * 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 1d24dda8a4bf..040b3694d0cd 100644 --- a/core/java/android/content/res/loader/ResourcesProvider.java +++ b/core/java/android/content/res/loader/ResourcesProvider.java @@ -212,6 +212,22 @@ public class ResourcesProvider implements AutoCloseable, Closeable { 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 44f8a31a4e22..6acb133a4e5b 100644 --- a/core/jni/android_content_res_ApkAssets.cpp +++ b/core/jni/android_content_res_ApkAssets.cpp @@ -48,6 +48,9 @@ enum : format_type_t { // 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 @@ static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, const format_type_t forma 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 @@ static jlong NativeOpenXml(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring fil } 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 f88878d02b20..bd2bac50f100 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.After 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 @@ abstract class ResourceLoaderTestBase { 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 @@ abstract class ResourceLoaderTestBase { 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 11295f32cd2c..c01db0d7428b 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 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() { 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 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() { "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 946fcc03f57e..f5bf84f18a89 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 @@ using base::unique_fd; 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)); + } + + 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()); } - return LoadImpl(unmanaged_handle, path, nullptr /*idmap_asset*/, nullptr /*loaded_idmap*/, - flags); + 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 @@ std::unique_ptr<const ApkAssets> ApkAssets::LoadFromFd(unique_fd fd, 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 @@ std::unique_ptr<const ApkAssets> ApkAssets::LoadTableFromFd(unique_fd fd, 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 @@ std::unique_ptr<const ApkAssets> ApkAssets::LoadOverlay(const std::string& idmap 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 @@ std::unique_ptr<Asset> ApkAssets::CreateAssetFromFd(base::unique_fd fd, 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 @@ std::unique_ptr<const ApkAssets> ApkAssets::LoadImpl(ZipArchiveHandle unmanaged_ // 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 @@ std::unique_ptr<const ApkAssets> ApkAssets::LoadImpl(ZipArchiveHandle unmanaged_ 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 @@ std::unique_ptr<const ApkAssets> ApkAssets::LoadTableImpl(std::unique_ptr<Asset> 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 @@ std::unique_ptr<const ApkAssets> ApkAssets::LoadTableImpl(std::unique_ptr<Asset> 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 32086625a726..f20e18453f8b 100644 --- a/libs/androidfw/AssetManager2.cpp +++ b/libs/androidfw/AssetManager2.cpp @@ -463,7 +463,7 @@ std::unique_ptr<AssetDir> AssetManager2::OpenDir(const std::string& dirname) con files->add(info); }; - if (!apk_assets->ForEachFile(full_path, func)) { + if (!apk_assets->GetAssetsProvider()->ForEachFile(full_path, func)) { return {}; } } @@ -487,7 +487,7 @@ std::unique_ptr<Asset> AssetManager2::OpenNonAsset(const std::string& filename, 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 @@ std::unique_ptr<Asset> AssetManager2::OpenNonAsset(const std::string& filename, 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 643dc5c861f7..944476890af0 100644 --- a/libs/androidfw/include/androidfw/ApkAssets.h +++ b/libs/androidfw/include/androidfw/ApkAssets.h @@ -35,13 +35,46 @@ namespace android { 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 @@ class ApkAssets { 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 @@ class ApkAssets { 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 @@ class ApkAssets { 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 75761747a5b4..298509eb37a1 100644 --- a/libs/androidfw/include/androidfw/Asset.h +++ b/libs/androidfw/include/androidfw/Asset.h @@ -159,6 +159,7 @@ private: /* 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 26bf5ffe5e91..ce9e53244801 100644 --- a/libs/androidfw/tests/ApkAssets_test.cpp +++ b/libs/androidfw/tests/ApkAssets_test.cpp @@ -42,7 +42,7 @@ TEST(ApkAssetsTest, LoadApk) { 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 @@ TEST(ApkAssetsTest, LoadApkFromFd) { 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 @@ TEST(ApkAssetsTest, CreateAndDestroyAssetKeepsApkAssetsOpen) { 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 @@ TEST(ApkAssetsTest, OpenUncompressedAssetFd) { 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 09cdbd5fee58..e70c68852b67 100644 --- a/startop/view_compiler/apk_layout_compiler.cc +++ b/startop/view_compiler/apk_layout_compiler.cc @@ -100,10 +100,12 @@ void CompileApkAssetsLayouts(const std::unique_ptr<const android::ApkAssets>& as 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); |