providers = loader.getProviders();
- for (int j = providers.size() - 1; j >= 0; j--) {
- final AssetsProvider assetsProvider = providers.get(j).getAssetsProvider();
- if (assetsProvider == null) {
- continue;
- }
-
- try {
- final ParcelFileDescriptor fileDescriptor = assetsProvider
- .loadAssetParcelFd(fileName);
- if (fileDescriptor != null) {
- return new AssetFileDescriptor(fileDescriptor, 0,
- AssetFileDescriptor.UNKNOWN_LENGTH);
- }
- } catch (IOException ignored) {
- // When searching, ignore read failures
- }
- }
- }
-
- return null;
- }
-
- final ResourcesProvider provider = findResourcesProvider(cookie);
- if (provider != null && provider.getAssetsProvider() != null) {
- final ParcelFileDescriptor fileDescriptor = provider.getAssetsProvider()
- .loadAssetParcelFd(fileName);
- if (fileDescriptor != null) {
- return new AssetFileDescriptor(fileDescriptor, 0,
- AssetFileDescriptor.UNKNOWN_LENGTH);
- }
- return null;
- }
-
- return null;
- }
-
void xmlBlockGone(int id) {
synchronized (this) {
decRefsLocked(id);
diff --git a/core/java/android/content/res/loader/AssetsProvider.java b/core/java/android/content/res/loader/AssetsProvider.java
index c315494cf7281..0f8f1d1da8d24 100644
--- a/core/java/android/content/res/loader/AssetsProvider.java
+++ b/core/java/android/content/res/loader/AssetsProvider.java
@@ -18,12 +18,10 @@ package android.content.res.loader;
import android.annotation.NonNull;
import android.annotation.Nullable;
+import android.content.res.AssetFileDescriptor;
import android.content.res.AssetManager;
import android.os.ParcelFileDescriptor;
-import java.io.IOException;
-import java.io.InputStream;
-
/**
* Provides callbacks that allow for the value of a file-based resources or assets of a
* {@link ResourcesProvider} to be specified or overridden.
@@ -34,6 +32,10 @@ public interface AssetsProvider {
* Callback that allows the value of a file-based resources or asset to be specified or
* overridden.
*
+ * The system will take ownership of the file descriptor returned from this method, so
+ * {@link ParcelFileDescriptor#dup() dup} the file descriptor before returning if the system
+ * should not own it.
+ *
*
There are two situations in which this method will be called:
*
* - AssetManager is queried for an InputStream of an asset using APIs like
@@ -52,17 +54,7 @@ public interface AssetsProvider {
* @see AssetManager#open
*/
@Nullable
- default InputStream loadAsset(@NonNull String path, int accessMode) throws IOException {
- return null;
- }
-
- /**
- * {@link ParcelFileDescriptor} variant of {@link #loadAsset(String, int)}.
- *
- * @param path the asset path being loaded
- */
- @Nullable
- default ParcelFileDescriptor loadAssetParcelFd(@NonNull String path) throws IOException {
+ default AssetFileDescriptor loadAssetFd(@NonNull String path, int accessMode) {
return null;
}
}
diff --git a/core/java/android/content/res/loader/DirectoryAssetsProvider.java b/core/java/android/content/res/loader/DirectoryAssetsProvider.java
deleted file mode 100644
index 81c2a4c1b4d6b..0000000000000
--- a/core/java/android/content/res/loader/DirectoryAssetsProvider.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.content.res.loader;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.os.ParcelFileDescriptor;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * A {@link AssetsProvider} that searches a directory for assets.
- * Assumes that resource paths are resolvable child paths of the root directory passed in.
- */
-public class DirectoryAssetsProvider implements AssetsProvider {
-
- @NonNull
- private final File mDirectory;
-
- /**
- * Creates a DirectoryAssetsProvider with given root directory.
- *
- * @param directory the root directory to resolve files from
- */
- public DirectoryAssetsProvider(@NonNull File directory) {
- this.mDirectory = directory;
- }
-
- @Nullable
- @Override
- public InputStream loadAsset(@NonNull String path, int accessMode) throws IOException {
- final File file = findFile(path);
- if (file == null || !file.exists()) {
- return null;
- }
- return new FileInputStream(file);
- }
-
- @Nullable
- @Override
- public ParcelFileDescriptor loadAssetParcelFd(@NonNull String path) throws IOException {
- final File file = findFile(path);
- if (file == null || !file.exists()) {
- return null;
- }
- return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
- }
-
- /**
- * Finds the file relative to the root directory.
- *
- * @param path the relative path of the file
- */
- @Nullable
- public File findFile(@NonNull String path) {
- return mDirectory.toPath().resolve(path).toFile();
- }
-
- @NonNull
- public File getDirectory() {
- return mDirectory;
- }
-}
diff --git a/core/java/android/content/res/loader/ResourcesProvider.java b/core/java/android/content/res/loader/ResourcesProvider.java
index 040b3694d0cdf..0a698d18682b9 100644
--- a/core/java/android/content/res/loader/ResourcesProvider.java
+++ b/core/java/android/content/res/loader/ResourcesProvider.java
@@ -50,8 +50,6 @@ public class ResourcesProvider implements AutoCloseable, Closeable {
@GuardedBy("mLock")
private final ApkAssets mApkAssets;
- private final AssetsProvider mAssetsProvider;
-
/**
* Creates an empty ResourcesProvider with no resource data. This is useful for loading
* file-based assets not associated with resource identifiers.
@@ -60,8 +58,8 @@ public class ResourcesProvider implements AutoCloseable, Closeable {
*/
@NonNull
public static ResourcesProvider empty(@NonNull AssetsProvider assetsProvider) {
- return new ResourcesProvider(ApkAssets.loadEmptyForLoader(ApkAssets.PROPERTY_LOADER),
- assetsProvider);
+ return new ResourcesProvider(ApkAssets.loadEmptyForLoader(ApkAssets.PROPERTY_LOADER,
+ assetsProvider));
}
/**
@@ -101,7 +99,7 @@ public class ResourcesProvider implements AutoCloseable, Closeable {
@Nullable AssetsProvider assetsProvider)
throws IOException {
return new ResourcesProvider(ApkAssets.loadFromFd(fileDescriptor.getFileDescriptor(),
- fileDescriptor.toString(), ApkAssets.PROPERTY_LOADER), assetsProvider);
+ fileDescriptor.toString(), ApkAssets.PROPERTY_LOADER, assetsProvider));
}
/**
@@ -130,8 +128,8 @@ public class ResourcesProvider implements AutoCloseable, Closeable {
long offset, long length, @Nullable AssetsProvider assetsProvider)
throws IOException {
return new ResourcesProvider(ApkAssets.loadFromFd(fileDescriptor.getFileDescriptor(),
- fileDescriptor.toString(), offset, length, ApkAssets.PROPERTY_LOADER),
- assetsProvider);
+ fileDescriptor.toString(), offset, length, ApkAssets.PROPERTY_LOADER,
+ assetsProvider));
}
/**
@@ -156,7 +154,7 @@ public class ResourcesProvider implements AutoCloseable, Closeable {
throws IOException {
return new ResourcesProvider(
ApkAssets.loadTableFromFd(fileDescriptor.getFileDescriptor(),
- fileDescriptor.toString(), ApkAssets.PROPERTY_LOADER), assetsProvider);
+ fileDescriptor.toString(), ApkAssets.PROPERTY_LOADER, assetsProvider));
}
/**
@@ -187,8 +185,8 @@ public class ResourcesProvider implements AutoCloseable, Closeable {
throws IOException {
return new ResourcesProvider(
ApkAssets.loadTableFromFd(fileDescriptor.getFileDescriptor(),
- fileDescriptor.toString(), offset, length, ApkAssets.PROPERTY_LOADER),
- assetsProvider);
+ fileDescriptor.toString(), offset, length, ApkAssets.PROPERTY_LOADER,
+ assetsProvider));
}
/**
@@ -208,8 +206,8 @@ public class ResourcesProvider implements AutoCloseable, Closeable {
}
String splitPath = appInfo.getSplitCodePaths()[splitIndex];
- return new ResourcesProvider(ApkAssets.loadFromPath(splitPath, ApkAssets.PROPERTY_LOADER),
- null);
+ return new ResourcesProvider(ApkAssets.loadFromPath(splitPath, ApkAssets.PROPERTY_LOADER,
+ null /* assetsProvider */));
}
/**
@@ -223,20 +221,13 @@ public class ResourcesProvider implements AutoCloseable, Closeable {
@NonNull
public static ResourcesProvider loadFromDirectory(@NonNull String path,
@Nullable AssetsProvider assetsProvider) throws IOException {
- return new ResourcesProvider(ApkAssets.loadFromDir(path, ApkAssets.PROPERTY_LOADER),
- assetsProvider);
+ return new ResourcesProvider(ApkAssets.loadFromDir(path, ApkAssets.PROPERTY_LOADER,
+ assetsProvider));
}
- private ResourcesProvider(@NonNull ApkAssets apkAssets,
- @Nullable AssetsProvider assetsProvider) {
+ private ResourcesProvider(@NonNull ApkAssets apkAssets) {
this.mApkAssets = apkAssets;
- this.mAssetsProvider = assetsProvider;
- }
-
- @Nullable
- public AssetsProvider getAssetsProvider() {
- return mAssetsProvider;
}
/** @hide */
diff --git a/core/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp
index 6acb133a4e5b5..fbdd4060d7f26 100644
--- a/core/jni/android_content_res_ApkAssets.cpp
+++ b/core/jni/android_content_res_ApkAssets.cpp
@@ -37,6 +37,21 @@ static struct overlayableinfo_offsets_t {
jmethodID constructor;
} gOverlayableInfoOffsets;
+static struct assetfiledescriptor_offsets_t {
+ jfieldID mFd;
+ jfieldID mStartOffset;
+ jfieldID mLength;
+} gAssetFileDescriptorOffsets;
+
+static struct assetsprovider_offsets_t {
+ jclass classObject;
+ jmethodID loadAssetFd;
+} gAssetsProviderOffsets;
+
+static struct {
+ jmethodID detachFd;
+} gParcelFileDescriptorOffsets;
+
// Keep in sync with f/b/android/content/res/ApkAssets.java
using format_type_t = jint;
enum : format_type_t {
@@ -53,8 +68,97 @@ enum : format_type_t {
FORMAT_DIRECTORY = 3,
};
+class LoaderAssetsProvider : public AssetsProvider {
+ public:
+ static std::unique_ptr Create(JNIEnv* env, jobject assets_provider) {
+ return (!assets_provider) ? nullptr
+ : std::unique_ptr(new LoaderAssetsProvider(
+ env->NewGlobalRef(assets_provider)));
+ }
+
+ ~LoaderAssetsProvider() override {
+ const auto env = AndroidRuntime::getJNIEnv();
+ CHECK(env != nullptr) << "Current thread not attached to a Java VM."
+ << " Failed to close LoaderAssetsProvider.";
+ env->DeleteGlobalRef(assets_provider_);
+ }
+
+ protected:
+ std::unique_ptr OpenInternal(const std::string& path,
+ Asset::AccessMode mode,
+ bool* file_exists) const override {
+ const auto env = AndroidRuntime::getJNIEnv();
+ CHECK(env != nullptr) << "Current thread not attached to a Java VM."
+ << " ResourcesProvider assets cannot be retrieved on current thread.";
+
+ jstring java_string = env->NewStringUTF(path.c_str());
+ if (env->ExceptionCheck()) {
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ return nullptr;
+ }
+
+ // Check if the AssetsProvider provides a value for the path.
+ jobject asset_fd = env->CallObjectMethod(assets_provider_,
+ gAssetsProviderOffsets.loadAssetFd,
+ java_string, static_cast(mode));
+ env->DeleteLocalRef(java_string);
+ if (env->ExceptionCheck()) {
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ return nullptr;
+ }
+
+ if (!asset_fd) {
+ if (file_exists) {
+ *file_exists = false;
+ }
+ return nullptr;
+ }
+
+ const jlong mOffset = env->GetLongField(asset_fd, gAssetFileDescriptorOffsets.mStartOffset);
+ const jlong mLength = env->GetLongField(asset_fd, gAssetFileDescriptorOffsets.mLength);
+ jobject mFd = env->GetObjectField(asset_fd, gAssetFileDescriptorOffsets.mFd);
+ env->DeleteLocalRef(asset_fd);
+
+ if (!mFd) {
+ jniThrowException(env, "java/lang/NullPointerException", nullptr);
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ return nullptr;
+ }
+
+ // Gain ownership of the file descriptor.
+ const jint fd = env->CallIntMethod(mFd, gParcelFileDescriptorOffsets.detachFd);
+ env->DeleteLocalRef(mFd);
+ if (env->ExceptionCheck()) {
+ env->ExceptionDescribe();
+ env->ExceptionClear();
+ return nullptr;
+ }
+
+ if (file_exists) {
+ *file_exists = true;
+ }
+
+ return ApkAssets::CreateAssetFromFd(base::unique_fd(fd),
+ nullptr /* path */,
+ static_cast(mOffset),
+ static_cast(mLength));
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(LoaderAssetsProvider);
+
+ explicit LoaderAssetsProvider(jobject assets_provider)
+ : assets_provider_(assets_provider) { }
+
+ // The global reference to the AssetsProvider
+ jobject assets_provider_;
+};
+
static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, const format_type_t format,
- jstring java_path, const jint property_flags) {
+ jstring java_path, const jint property_flags, jobject assets_provider) {
ScopedUtfChars path(env, java_path);
if (path.c_str() == nullptr) {
return 0;
@@ -62,19 +166,20 @@ static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, const format_type_t forma
ATRACE_NAME(base::StringPrintf("LoadApkAssets(%s)", path.c_str()).c_str());
+ auto loader_assets = LoaderAssetsProvider::Create(env, assets_provider);
std::unique_ptr apk_assets;
switch (format) {
case FORMAT_APK:
- apk_assets = ApkAssets::Load(path.c_str(), property_flags);
+ apk_assets = ApkAssets::Load(path.c_str(), property_flags, std::move(loader_assets));
break;
case FORMAT_IDMAP:
apk_assets = ApkAssets::LoadOverlay(path.c_str(), property_flags);
break;
case FORMAT_ARSC:
- apk_assets = ApkAssets::LoadTable(path.c_str(), property_flags);
+ apk_assets = ApkAssets::LoadTable(path.c_str(), property_flags, std::move(loader_assets));
break;
case FORMAT_DIRECTORY:
- apk_assets = ApkAssets::LoadFromDir(path.c_str(), property_flags);
+ apk_assets = ApkAssets::LoadFromDir(path.c_str(), property_flags, std::move(loader_assets));
break;
default:
const std::string error_msg = base::StringPrintf("Unsupported format type %d", format);
@@ -92,7 +197,7 @@ static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, const format_type_t forma
static jlong NativeLoadFromFd(JNIEnv* env, jclass /*clazz*/, const format_type_t format,
jobject file_descriptor, jstring friendly_name,
- const jint property_flags) {
+ const jint property_flags, jobject assets_provider) {
ScopedUtfChars friendly_name_utf8(env, friendly_name);
if (friendly_name_utf8.c_str() == nullptr) {
return 0;
@@ -112,15 +217,16 @@ static jlong NativeLoadFromFd(JNIEnv* env, jclass /*clazz*/, const format_type_t
return 0;
}
+ auto loader_assets = LoaderAssetsProvider::Create(env, assets_provider);
std::unique_ptr apk_assets;
switch (format) {
case FORMAT_APK:
apk_assets = ApkAssets::LoadFromFd(std::move(dup_fd), friendly_name_utf8.c_str(),
- property_flags);
+ property_flags, std::move(loader_assets));
break;
case FORMAT_ARSC:
apk_assets = ApkAssets::LoadTableFromFd(std::move(dup_fd), friendly_name_utf8.c_str(),
- property_flags);
+ property_flags, std::move(loader_assets));
break;
default:
const std::string error_msg = base::StringPrintf("Unsupported format type %d", format);
@@ -140,12 +246,14 @@ static jlong NativeLoadFromFd(JNIEnv* env, jclass /*clazz*/, const format_type_t
static jlong NativeLoadFromFdOffset(JNIEnv* env, jclass /*clazz*/, const format_type_t format,
jobject file_descriptor, jstring friendly_name,
const jlong offset, const jlong length,
- const jint property_flags) {
+ const jint property_flags, jobject assets_provider) {
ScopedUtfChars friendly_name_utf8(env, friendly_name);
if (friendly_name_utf8.c_str() == nullptr) {
return 0;
}
+ ATRACE_NAME(base::StringPrintf("LoadApkAssetsFd(%s)", friendly_name_utf8.c_str()).c_str());
+
if (offset < 0) {
jniThrowException(env, "java/lang/IllegalArgumentException",
"offset cannot be negative");
@@ -170,18 +278,19 @@ static jlong NativeLoadFromFdOffset(JNIEnv* env, jclass /*clazz*/, const format_
return 0;
}
- ATRACE_NAME(base::StringPrintf("LoadApkAssetsFd(%s)", friendly_name_utf8.c_str()).c_str());
-
+ auto loader_assets = LoaderAssetsProvider::Create(env, assets_provider);
std::unique_ptr apk_assets;
switch (format) {
case FORMAT_APK:
apk_assets = ApkAssets::LoadFromFd(std::move(dup_fd), friendly_name_utf8.c_str(),
- property_flags, static_cast(offset),
+ property_flags, std::move(loader_assets),
+ static_cast(offset),
static_cast(length));
break;
case FORMAT_ARSC:
apk_assets = ApkAssets::LoadTableFromFd(std::move(dup_fd), friendly_name_utf8.c_str(),
- property_flags, static_cast(offset),
+ property_flags, std::move(loader_assets),
+ static_cast(offset),
static_cast(length));
break;
default:
@@ -199,8 +308,9 @@ static jlong NativeLoadFromFdOffset(JNIEnv* env, jclass /*clazz*/, const format_
return reinterpret_cast(apk_assets.release());
}
-static jlong NativeLoadEmpty(JNIEnv* env, jclass /*clazz*/, jint flags) {
- std::unique_ptr apk_assets = ApkAssets::LoadEmpty(flags);
+static jlong NativeLoadEmpty(JNIEnv* env, jclass /*clazz*/, jint flags, jobject assets_provider) {
+ auto loader_assets = LoaderAssetsProvider::Create(env, assets_provider);
+ auto apk_assets = ApkAssets::LoadEmpty(flags, std::move(loader_assets));
return reinterpret_cast(apk_assets.release());
}
@@ -302,11 +412,15 @@ static jboolean NativeDefinesOverlayable(JNIEnv* env, jclass /*clazz*/, jlong pt
// JNI registration.
static const JNINativeMethod gApkAssetsMethods[] = {
- {"nativeLoad", "(ILjava/lang/String;I)J", (void*)NativeLoad},
- {"nativeLoadEmpty", "(I)J", (void*)NativeLoadEmpty},
- {"nativeLoadFd", "(ILjava/io/FileDescriptor;Ljava/lang/String;I)J", (void*)NativeLoadFromFd},
- {"nativeLoadFdOffsets", "(ILjava/io/FileDescriptor;Ljava/lang/String;JJI)J",
- (void*)NativeLoadFromFdOffset},
+ {"nativeLoad", "(ILjava/lang/String;ILandroid/content/res/loader/AssetsProvider;)J",
+ (void*)NativeLoad},
+ {"nativeLoadEmpty", "(ILandroid/content/res/loader/AssetsProvider;)J", (void*)NativeLoadEmpty},
+ {"nativeLoadFd",
+ "(ILjava/io/FileDescriptor;Ljava/lang/String;ILandroid/content/res/loader/AssetsProvider;)J",
+ (void*)NativeLoadFromFd},
+ {"nativeLoadFdOffsets",
+ "(ILjava/io/FileDescriptor;Ljava/lang/String;JJILandroid/content/res/loader/AssetsProvider;)J",
+ (void*)NativeLoadFromFdOffset},
{"nativeDestroy", "(J)V", (void*)NativeDestroy},
{"nativeGetAssetPath", "(J)Ljava/lang/String;", (void*)NativeGetAssetPath},
{"nativeGetStringBlock", "(J)J", (void*)NativeGetStringBlock},
@@ -323,6 +437,21 @@ int register_android_content_res_ApkAssets(JNIEnv* env) {
gOverlayableInfoOffsets.constructor = GetMethodIDOrDie(env, gOverlayableInfoOffsets.classObject,
"", "(Ljava/lang/String;Ljava/lang/String;)V");
+ jclass assetFd = FindClassOrDie(env, "android/content/res/AssetFileDescriptor");
+ gAssetFileDescriptorOffsets.mFd =
+ GetFieldIDOrDie(env, assetFd, "mFd", "Landroid/os/ParcelFileDescriptor;");
+ gAssetFileDescriptorOffsets.mStartOffset = GetFieldIDOrDie(env, assetFd, "mStartOffset", "J");
+ gAssetFileDescriptorOffsets.mLength = GetFieldIDOrDie(env, assetFd, "mLength", "J");
+
+ jclass assetsProvider = FindClassOrDie(env, "android/content/res/loader/AssetsProvider");
+ gAssetsProviderOffsets.classObject = MakeGlobalRefOrDie(env, assetsProvider);
+ gAssetsProviderOffsets.loadAssetFd = GetMethodIDOrDie(
+ env, gAssetsProviderOffsets.classObject, "loadAssetFd",
+ "(Ljava/lang/String;I)Landroid/content/res/AssetFileDescriptor;");
+
+ jclass parcelFd = FindClassOrDie(env, "android/os/ParcelFileDescriptor");
+ gParcelFileDescriptorOffsets.detachFd = GetMethodIDOrDie(env, parcelFd, "detachFd", "()I");
+
return RegisterMethodsOrDie(env, "android/content/res/ApkAssets", gApkAssetsMethods,
arraysize(gApkAssetsMethods));
}
diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp
index 062b886f54a17..cb5a332c6e85f 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -75,12 +75,6 @@ static struct typedvalue_offsets_t {
jfieldID mDensity;
} gTypedValueOffsets;
-static struct assetfiledescriptor_offsets_t {
- jfieldID mFd;
- jfieldID mStartOffset;
- jfieldID mLength;
-} gAssetFileDescriptorOffsets;
-
// This is also used by asset_manager.cpp.
assetmanager_offsets_t gAssetManagerOffsets;
@@ -1596,12 +1590,6 @@ int register_android_content_AssetManager(JNIEnv* env) {
GetFieldIDOrDie(env, typedValue, "changingConfigurations", "I");
gTypedValueOffsets.mDensity = GetFieldIDOrDie(env, typedValue, "density", "I");
- jclass assetFd = FindClassOrDie(env, "android/content/res/AssetFileDescriptor");
- gAssetFileDescriptorOffsets.mFd =
- GetFieldIDOrDie(env, assetFd, "mFd", "Landroid/os/ParcelFileDescriptor;");
- gAssetFileDescriptorOffsets.mStartOffset = GetFieldIDOrDie(env, assetFd, "mStartOffset", "J");
- gAssetFileDescriptorOffsets.mLength = GetFieldIDOrDie(env, assetFd, "mLength", "J");
-
jclass assetManager = FindClassOrDie(env, "android/content/res/AssetManager");
gAssetManagerOffsets.mObject = GetFieldIDOrDie(env, assetManager, "mObject", "J");
diff --git a/core/tests/ResourceLoaderTests/assets/base_asset.txt b/core/tests/ResourceLoaderTests/assets/base_asset.txt
new file mode 100644
index 0000000000000..8e62cc3462387
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/assets/base_asset.txt
@@ -0,0 +1 @@
+Base
\ No newline at end of file
diff --git a/core/tests/ResourceLoaderTests/resources/provider1/assets/loader_asset.txt b/core/tests/ResourceLoaderTests/resources/provider1/assets/loader_asset.txt
new file mode 100644
index 0000000000000..0e41ffa475af3
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/provider1/assets/loader_asset.txt
@@ -0,0 +1 @@
+LoaderOne
\ No newline at end of file
diff --git a/core/tests/ResourceLoaderTests/resources/provider2/assets/loader_asset.txt b/core/tests/ResourceLoaderTests/resources/provider2/assets/loader_asset.txt
new file mode 100644
index 0000000000000..bca782ec1b2bf
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/provider2/assets/loader_asset.txt
@@ -0,0 +1 @@
+LoaderTwo
\ No newline at end of file
diff --git a/core/tests/ResourceLoaderTests/resources/provider3/assets/loader_asset.txt b/core/tests/ResourceLoaderTests/resources/provider3/assets/loader_asset.txt
new file mode 100644
index 0000000000000..bae8ef79a2ce4
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/provider3/assets/loader_asset.txt
@@ -0,0 +1 @@
+LoaderThree
\ No newline at end of file
diff --git a/core/tests/ResourceLoaderTests/resources/provider4/assets/loader_asset.txt b/core/tests/ResourceLoaderTests/resources/provider4/assets/loader_asset.txt
new file mode 100644
index 0000000000000..b75d9963575bb
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/provider4/assets/loader_asset.txt
@@ -0,0 +1 @@
+LoaderFour
\ No newline at end of file
diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetsTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetsTest.kt
deleted file mode 100644
index da5092de06275..0000000000000
--- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetsTest.kt
+++ /dev/null
@@ -1,220 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.content.res.loader.test
-
-import android.content.res.AssetManager
-import android.content.res.loader.AssetsProvider
-import android.content.res.loader.DirectoryAssetsProvider
-import android.content.res.loader.ResourcesLoader
-import android.content.res.loader.ResourcesProvider
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.rules.TestName
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-import org.mockito.Mockito.anyInt
-import org.mockito.Mockito.anyString
-import org.mockito.Mockito.doAnswer
-import org.mockito.Mockito.doReturn
-import org.mockito.Mockito.eq
-import org.mockito.Mockito.inOrder
-import org.mockito.Mockito.mock
-import java.io.File
-import java.io.FileNotFoundException
-import java.io.IOException
-import java.nio.file.Paths
-
-@RunWith(Parameterized::class)
-class ResourceLoaderAssetsTest : ResourceLoaderTestBase() {
-
- companion object {
- private const val BASE_TEST_PATH = "android/content/res/loader/test/file.txt"
- private const val TEST_TEXT = "some text"
-
- @JvmStatic
- @Parameterized.Parameters(name = "{0}")
- fun parameters(): Array> {
- val fromInputStream: AssetsProvider.(String) -> Any? = {
- loadAsset(eq(it), anyInt())
- }
-
- val fromFileDescriptor: AssetsProvider.(String) -> Any? = {
- loadAssetParcelFd(eq(it))
- }
-
- val openAsset: AssetManager.() -> String? = {
- open(BASE_TEST_PATH).reader().readText()
- }
-
- val openNonAsset: AssetManager.() -> String? = {
- openNonAssetFd(BASE_TEST_PATH).readText()
- }
-
- return arrayOf(
- arrayOf("assets", fromInputStream, openAsset),
- arrayOf("", fromFileDescriptor, openNonAsset)
- )
- }
- }
-
- @get:Rule
- val testName = TestName()
-
- @JvmField
- @field:Parameterized.Parameter(0)
- var prefix: String? = null
-
- @field:Parameterized.Parameter(1)
- lateinit var loadAssetFunction: AssetsProvider.(String) -> Any?
-
- @field:Parameterized.Parameter(2)
- lateinit var openAssetFunction: AssetManager.() -> String?
-
- private val testPath: String
- get() = Paths.get(prefix.orEmpty(), BASE_TEST_PATH).toString()
-
- private fun AssetsProvider.loadAsset() = loadAssetFunction(testPath)
-
- private fun AssetManager.openAsset() = openAssetFunction()
-
- private lateinit var testDir: File
-
- @Before
- fun setUpTestDir() {
- testDir = context.filesDir.resolve("DirectoryAssetsProvider_${testName.methodName}")
- testDir.resolve(testPath).apply { parentFile!!.mkdirs() }.writeText(TEST_TEXT)
- }
-
- @Test
- fun multipleProvidersSearchesBackwards() {
- // DirectoryResourceLoader relies on a private field and can't be spied directly, so wrap it
- val assetsProvider = DirectoryAssetsProvider(testDir)
- val assetProviderWrapper = mock(AssetsProvider::class.java).apply {
- doAnswer { assetsProvider.loadAsset(it.arguments[0] as String, it.arguments[1] as Int) }
- .`when`(this).loadAsset(anyString(), anyInt())
- doAnswer { assetsProvider.loadAssetParcelFd(it.arguments[0] as String) }
- .`when`(this).loadAssetParcelFd(anyString())
- }
-
- val one = ResourcesProvider.empty(assetProviderWrapper)
- val two = mockProvider {
- doReturn(null).`when`(it).loadAsset()
- }
-
- val loader = ResourcesLoader()
- loader.providers = listOf(one, two)
- resources.addLoaders(loader)
-
- assertOpenedAsset()
- inOrder(two.assetsProvider, one.assetsProvider).apply {
- verify(two.assetsProvider)?.loadAsset()
- verify(one.assetsProvider)?.loadAsset()
- }
- }
-
- @Test
- fun multipleLoadersSearchesBackwards() {
- // DirectoryResourceLoader relies on a private field and can't be spied directly, so wrap it
- val assetsProvider = DirectoryAssetsProvider(testDir)
- val assetProviderWrapper = mock(AssetsProvider::class.java).apply {
- doAnswer { assetsProvider.loadAsset(it.arguments[0] as String, it.arguments[1] as Int) }
- .`when`(this).loadAsset(anyString(), anyInt())
- doAnswer { assetsProvider.loadAssetParcelFd(it.arguments[0] as String) }
- .`when`(this).loadAssetParcelFd(anyString())
- }
-
- val one = ResourcesProvider.empty(assetProviderWrapper)
- val two = mockProvider {
- doReturn(null).`when`(it).loadAsset()
- }
-
- val loader1 = ResourcesLoader()
- loader1.addProvider(one)
- val loader2 = ResourcesLoader()
- loader2.addProvider(two)
-
- resources.addLoaders(loader1, loader2)
-
- assertOpenedAsset()
- inOrder(two.assetsProvider, one.assetsProvider).apply {
- verify(two.assetsProvider)?.loadAsset()
- verify(one.assetsProvider)?.loadAsset()
- }
- }
-
- @Test(expected = FileNotFoundException::class)
- fun failToFindThrowsFileNotFound() {
- val assetsProvider1 = mock(AssetsProvider::class.java).apply {
- doReturn(null).`when`(this).loadAsset()
- }
- val assetsProvider2 = mock(AssetsProvider::class.java).apply {
- doReturn(null).`when`(this).loadAsset()
- }
-
- val loader = ResourcesLoader()
- val one = ResourcesProvider.empty(assetsProvider1)
- val two = ResourcesProvider.empty(assetsProvider2)
- resources.addLoaders(loader)
- loader.providers = listOf(one, two)
-
- assertOpenedAsset()
- }
-
- @Test
- fun throwingIOExceptionIsSkipped() {
- val assetsProvider1 = DirectoryAssetsProvider(testDir)
- val assetsProvider2 = mock(AssetsProvider::class.java).apply {
- doAnswer { throw IOException() }.`when`(this).loadAsset()
- }
-
- val loader = ResourcesLoader()
- val one = ResourcesProvider.empty(assetsProvider1)
- val two = ResourcesProvider.empty(assetsProvider2)
- resources.addLoaders(loader)
- loader.providers = listOf(one, two)
-
- assertOpenedAsset()
- }
-
- @Test(expected = IllegalStateException::class)
- fun throwingNonIOExceptionCausesFailure() {
- val assetsProvider1 = DirectoryAssetsProvider(testDir)
- val assetsProvider2 = mock(AssetsProvider::class.java).apply {
- doAnswer { throw IllegalStateException() }.`when`(this).loadAsset()
- }
-
- val loader = ResourcesLoader()
- val one = ResourcesProvider.empty(assetsProvider1)
- val two = ResourcesProvider.empty(assetsProvider2)
- resources.addLoaders(loader)
- loader.providers = listOf(one, two)
-
- assertOpenedAsset()
- }
-
- private fun mockProvider(block: (AssetsProvider) -> Unit = {}): ResourcesProvider {
- return ResourcesProvider.empty(mock(AssetsProvider::class.java).apply {
- block.invoke(this)
- })
- }
-
- private fun assertOpenedAsset() {
- assertThat(resources.assets.openAsset()).isEqualTo(TEST_TEXT)
- }
-}
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 bd2bac50f1002..4764c1008d2fd 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
@@ -20,15 +20,19 @@ import android.content.Context
import android.content.res.AssetFileDescriptor
import android.content.res.Configuration
import android.content.res.Resources
+import android.content.res.loader.AssetsProvider
import android.content.res.loader.ResourcesProvider
import android.os.ParcelFileDescriptor
import android.system.Os
+import android.util.ArrayMap
import androidx.test.InstrumentationRegistry
+import org.json.JSONObject
import org.junit.After
import org.junit.Before
import java.io.Closeable
import java.io.FileOutputStream
import java.io.File
+import java.io.FileDescriptor
import java.util.zip.ZipInputStream
abstract class ResourceLoaderTestBase {
@@ -36,6 +40,29 @@ abstract class ResourceLoaderTestBase {
protected val PROVIDER_TWO: String = "FrameworksResourceLoaderTests_ProviderTwo"
protected val PROVIDER_THREE: String = "FrameworksResourceLoaderTests_ProviderThree"
protected val PROVIDER_FOUR: String = "FrameworksResourceLoaderTests_ProviderFour"
+ protected val PROVIDER_EMPTY: String = "empty"
+
+ companion object {
+ /** Converts the map to a stable JSON string representation. */
+ fun mapToString(m: Map): String {
+ return JSONObject(ArrayMap().apply { putAll(m) }).toString()
+ }
+
+ /** Creates a lambda that runs multiple resources queries and concatenates the results. */
+ fun query(queries: Map String>): Resources.() -> String {
+ return {
+ val resultMap = ArrayMap()
+ queries.forEach { q ->
+ resultMap[q.key] = try {
+ q.value.invoke(this)
+ } catch (e: Exception) {
+ e.javaClass.simpleName
+ }
+ }
+ mapToString(resultMap)
+ }
+ }
+ }
// Data type of the current test iteration
open lateinit var dataType: DataType
@@ -65,86 +92,140 @@ abstract class ResourceLoaderTestBase {
}
}
- protected fun String.openProvider(dataType: DataType)
- :ResourcesProvider = when (dataType) {
- DataType.APK_DISK_FD -> {
- val file = context.copiedAssetFile("${this}.apk")
- ResourcesProvider.loadFromApk(ParcelFileDescriptor.fromFd(file.fd)).apply {
- file.close()
+ protected fun String.openProvider(dataType: DataType, assetsProvider: MemoryAssetsProvider?)
+ :ResourcesProvider {
+ if (assetsProvider != null) {
+ openedObjects += assetsProvider
+ }
+ return when (dataType) {
+ DataType.APK_DISK_FD -> {
+ val file = context.copiedAssetFile("${this}.apk")
+ ResourcesProvider.loadFromApk(ParcelFileDescriptor.fromFd(file.fd),
+ assetsProvider).apply {
+ file.close()
+ }
}
- }
- DataType.APK_DISK_FD_OFFSETS -> {
- val asset = context.assets.openFd("${this}.apk")
- ResourcesProvider.loadFromApk(asset.parcelFileDescriptor, asset.startOffset,
- asset.length, null).apply {
- asset.close()
+ DataType.APK_DISK_FD_OFFSETS -> {
+ val asset = context.assets.openFd("${this}.apk")
+ ResourcesProvider.loadFromApk(asset.parcelFileDescriptor, asset.startOffset,
+ asset.length, assetsProvider).apply {
+ asset.close()
+ }
}
- }
- DataType.ARSC_DISK_FD -> {
- val file = context.copiedAssetFile("${this}.arsc")
- ResourcesProvider.loadFromTable(ParcelFileDescriptor.fromFd(file.fd), null).apply {
- file.close()
+ DataType.ARSC_DISK_FD -> {
+ val file = context.copiedAssetFile("${this}.arsc")
+ ResourcesProvider.loadFromTable(ParcelFileDescriptor.fromFd(file.fd),
+ assetsProvider).apply {
+ file.close()
+ }
}
- }
- DataType.ARSC_DISK_FD_OFFSETS -> {
- val asset = context.assets.openFd("${this}.arsc")
- ResourcesProvider.loadFromTable(asset.parcelFileDescriptor, asset.startOffset,
- asset.length, null).apply {
- asset.close()
+ DataType.ARSC_DISK_FD_OFFSETS -> {
+ val asset = context.assets.openFd("${this}.arsc")
+ ResourcesProvider.loadFromTable(asset.parcelFileDescriptor, asset.startOffset,
+ asset.length, assetsProvider).apply {
+ asset.close()
+ }
}
- }
- DataType.APK_RAM_OFFSETS -> {
- val asset = context.assets.openFd("${this}.apk")
- val leadingGarbageSize = 100L
- val trailingGarbageSize = 55L
- val fd = loadAssetIntoMemory(asset, leadingGarbageSize.toInt(),
- trailingGarbageSize.toInt())
- ResourcesProvider.loadFromApk(fd, leadingGarbageSize, asset.declaredLength,
- null).apply {
- asset.close()
- fd.close()
+ DataType.APK_RAM_OFFSETS -> {
+ val asset = context.assets.openFd("${this}.apk")
+ val leadingGarbageSize = 100L
+ val trailingGarbageSize = 55L
+ val fd = loadAssetIntoMemory(asset, leadingGarbageSize.toInt(),
+ trailingGarbageSize.toInt())
+ ResourcesProvider.loadFromApk(fd, leadingGarbageSize, asset.declaredLength,
+ assetsProvider).apply {
+ asset.close()
+ fd.close()
+ }
}
- }
- DataType.APK_RAM_FD -> {
- val asset = context.assets.openFd("${this}.apk")
- var fd = loadAssetIntoMemory(asset)
- ResourcesProvider.loadFromApk(fd).apply {
- asset.close()
- fd.close()
+ DataType.APK_RAM_FD -> {
+ val asset = context.assets.openFd("${this}.apk")
+ var fd = loadAssetIntoMemory(asset)
+ ResourcesProvider.loadFromApk(fd, assetsProvider).apply {
+ asset.close()
+ fd.close()
+ }
}
- }
- DataType.ARSC_RAM_MEMORY -> {
- val asset = context.assets.openFd("${this}.arsc")
- var fd = loadAssetIntoMemory(asset)
- ResourcesProvider.loadFromTable(fd, null).apply {
- asset.close()
- fd.close()
+ DataType.ARSC_RAM_MEMORY -> {
+ val asset = context.assets.openFd("${this}.arsc")
+ var fd = loadAssetIntoMemory(asset)
+ ResourcesProvider.loadFromTable(fd, assetsProvider).apply {
+ asset.close()
+ fd.close()
+ }
}
- }
- DataType.ARSC_RAM_MEMORY_OFFSETS -> {
- val asset = context.assets.openFd("${this}.arsc")
- val leadingGarbageSize = 100L
- val trailingGarbageSize = 55L
- val fd = loadAssetIntoMemory(asset, leadingGarbageSize.toInt(),
- trailingGarbageSize.toInt())
- ResourcesProvider.loadFromTable(fd, leadingGarbageSize, asset.declaredLength,
- null).apply {
- asset.close()
- fd.close()
+ DataType.ARSC_RAM_MEMORY_OFFSETS -> {
+ val asset = context.assets.openFd("${this}.arsc")
+ val leadingGarbageSize = 100L
+ val trailingGarbageSize = 55L
+ val fd = loadAssetIntoMemory(asset, leadingGarbageSize.toInt(),
+ trailingGarbageSize.toInt())
+ ResourcesProvider.loadFromTable(fd, leadingGarbageSize, asset.declaredLength,
+ assetsProvider).apply {
+ asset.close()
+ fd.close()
+ }
+ }
+ DataType.EMPTY -> {
+ if (equals(PROVIDER_EMPTY)) {
+ ResourcesProvider.empty(EmptyAssetsProvider())
+ } else {
+ if (assetsProvider == null) ResourcesProvider.empty(ZipAssetsProvider(this))
+ else ResourcesProvider.empty(assetsProvider)
+ }
+ }
+ DataType.DIRECTORY -> {
+ ResourcesProvider.loadFromDirectory(zipToDir("${this}.apk").absolutePath,
+ assetsProvider)
+ }
+ DataType.SPLIT -> {
+ ResourcesProvider.loadFromSplit(context, "${this}_Split")
}
- }
- DataType.SPLIT -> {
- ResourcesProvider.loadFromSplit(context, "${this}_Split")
- }
- DataType.DIRECTORY -> {
- ResourcesProvider.loadFromDirectory(zipToDir("${this}.apk").absolutePath, null)
}
}
+ class EmptyAssetsProvider : AssetsProvider
+
+ /** */
+ inner class ZipAssetsProvider(val providerName : String) : AssetsProvider {
+ val root: File = zipToDir("${providerName}.apk")
+
+ override fun loadAssetFd(path: String, accessMode: Int): AssetFileDescriptor? {
+ val f = File(root, path)
+ return if (f.exists()) AssetFileDescriptor(
+ ParcelFileDescriptor.open(File(root, path),
+ ParcelFileDescriptor.MODE_READ_ONLY), 0,
+ AssetFileDescriptor.UNKNOWN_LENGTH) else null
+ }
+ }
+
+ /** AssetsProvider for testing that returns file descriptors to files in RAM. */
+ class MemoryAssetsProvider : AssetsProvider, Closeable {
+ var loadAssetResults = HashMap()
+
+ fun addLoadAssetFdResult(path : String, value : String) = apply {
+ val fd = Os.memfd_create(path, 0)
+ val valueBytes = value.toByteArray()
+ Os.write(fd, valueBytes, 0, valueBytes.size)
+ loadAssetResults[path] = fd
+ }
+
+ override fun loadAssetFd(path: String, accessMode: Int): AssetFileDescriptor? {
+ return if (loadAssetResults.containsKey(path)) AssetFileDescriptor(
+ ParcelFileDescriptor.dup(loadAssetResults[path]), 0,
+ AssetFileDescriptor.UNKNOWN_LENGTH) else null
+ }
+
+ override fun close() {
+ for (f in loadAssetResults.values) {
+ Os.close(f)
+ }
+ }
+ }
/** 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)
+ private fun zipToDir(name : String) : File {
+ val root = File(context.filesDir, name.split('.')[0])
if (root.exists()) {
return root
}
@@ -210,7 +291,8 @@ abstract class ResourceLoaderTestBase {
ARSC_DISK_FD_OFFSETS,
ARSC_RAM_MEMORY,
ARSC_RAM_MEMORY_OFFSETS,
- SPLIT,
- DIRECTORY
+ EMPTY,
+ DIRECTORY,
+ SPLIT
}
}
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 c01db0d7428b6..a9945369d1df2 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
@@ -26,9 +26,7 @@ import android.graphics.Color
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.ColorDrawable
import android.os.IBinder
-import android.util.ArrayMap
import androidx.test.rule.ActivityTestRule
-import org.json.JSONObject
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotEquals
import org.junit.Rule
@@ -51,26 +49,6 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() {
private val mTestActivityRule = ActivityTestRule(TestActivity::class.java)
companion object {
- /** Converts the map to a stable JSON string representation. */
- private fun mapToString(m : Map) :String {
- return JSONObject(ArrayMap().apply { putAll(m) }).toString()
- }
-
- /** Creates a lambda that runs multiple resources queries and concatenates the results. */
- fun query(queries : Map String>) :Resources.() -> String {
- return {
- val resultMap = ArrayMap()
- queries.forEach { q ->
- resultMap[q.key] = try {
- q.value.invoke(this)
- } catch (e : Exception) {
- e.javaClass.simpleName
- }
- }
- mapToString(resultMap)
- }
- }
-
@Parameterized.Parameters(name = "{1} {0}")
@JvmStatic
fun parameters(): Array {
@@ -109,21 +87,13 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() {
// Test resolution of file-based resources and assets with no assets provider.
parameters += Parameter(
- "fileBased",
+ "tableFileBased",
query(mapOf(
// Drawable xml in res directory
"drawableXml" to { res ->
(res.getDrawable(R.drawable.drawable_xml) as ColorDrawable)
.color.toString()
},
- // File in the assets directory
- "openAsset" to { res ->
- res.assets.open("asset.txt").reader().readText()
- },
- // From assets directory returning file descriptor
- "openAssetFd" to { res ->
- res.assets.openFd("asset.txt").readText()
- },
// Asset as compiled XML layout in res directory
"layout" to { res ->
res.getLayout(R.layout.layout).advanceToRoot().name
@@ -135,38 +105,109 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() {
}
)),
mapOf("drawableXml" to Color.parseColor("#B2D2F2").toString(),
- "openAsset" to "In assets directory",
- "openAssetFd" to "In assets directory",
"layout" to "MysteryLayout",
"drawablePng" to Color.parseColor("#FF00FF").toString()),
mapOf("drawableXml" to Color.parseColor("#000001").toString(),
- "openAsset" to "One",
- "openAssetFd" to "One",
"layout" to "RelativeLayout",
"drawablePng" to Color.RED.toString()),
mapOf("drawableXml" to Color.parseColor("#000002").toString(),
- "openAsset" to "Two",
- "openAssetFd" to "Two",
"layout" to "LinearLayout",
"drawablePng" to Color.GREEN.toString()),
mapOf("drawableXml" to Color.parseColor("#000003").toString(),
- "openAsset" to "Three",
- "openAssetFd" to "Three",
"layout" to "FrameLayout",
"drawablePng" to Color.BLUE.toString()),
mapOf("drawableXml" to Color.parseColor("#000004").toString(),
- "openAsset" to "Four",
- "openAssetFd" to "Four",
"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.DIRECTORY)
)
+ // Test resolution of assets.
+ parameters += Parameter(
+ "fileBased",
+ query(mapOf(
+ // File in the assets directory
+ "openAsset" to { res ->
+ res.assets.open("asset.txt").reader().readText()
+ },
+ // From assets directory returning file descriptor
+ "openAssetFd" to { res ->
+ res.assets.openFd("asset.txt").readText()
+ },
+ // Asset as compiled XML layout in res directory
+ "layout" to { res ->
+ res.assets.openXmlResourceParser("res/layout/layout.xml")
+ .advanceToRoot().name
+ }
+ )),
+ mapOf("openAsset" to "In assets directory",
+ "openAssetFd" to "In assets directory",
+ "layout" to "MysteryLayout"),
+
+ mapOf("openAsset" to "One",
+ "openAssetFd" to "One",
+ "layout" to "RelativeLayout"),
+
+ mapOf("openAsset" to "Two",
+ "openAssetFd" to "Two",
+ "layout" to "LinearLayout"),
+
+ mapOf("openAsset" to "Three",
+ "openAssetFd" to "Three",
+ "layout" to "FrameLayout"),
+
+ mapOf("openAsset" to "Four",
+ "openAssetFd" to "Four",
+ "layout" to "TableLayout"),
+ listOf(DataType.EMPTY)
+ )
+
+ // Test assets from apk and provider
+ parameters += Parameter(
+ "fileBasedApkAssetsProvider",
+ query(mapOf(
+ // File in the assets directory
+ "openAsset" to { res ->
+ res.assets.open("asset.txt").reader().readText()
+ },
+ // From assets directory returning file descriptor
+ "openAssetFd" to { res ->
+ res.assets.openFd("asset.txt").readText()
+ }
+ )),
+ mapOf("openAsset" to "In assets directory",
+ "openAssetFd" to "In assets directory"),
+
+ mapOf("openAsset" to "AssetsOne",
+ "openAssetFd" to "AssetsOne"),
+ { MemoryAssetsProvider().addLoadAssetFdResult("assets/asset.txt",
+ "AssetsOne") },
+
+ mapOf("openAsset" to "Two",
+ "openAssetFd" to "Two"),
+ null /* assetProviderTwo */,
+
+ mapOf("openAsset" to "AssetsThree",
+ "openAssetFd" to "AssetsThree"),
+ { MemoryAssetsProvider().addLoadAssetFdResult("assets/asset.txt",
+ "AssetsThree") },
+
+ mapOf("openAsset" to "Four",
+ "openAssetFd" to "Four"),
+ null /* assetProviderFour */,
+ listOf(DataType.APK_DISK_FD, DataType.APK_DISK_FD_OFFSETS, DataType.APK_RAM_FD,
+ DataType.APK_RAM_OFFSETS, DataType.DIRECTORY)
+
+ )
+
+ // TODO(151949807): Increase testing for cookie based APIs and for what happens when
+ // some providers do not overlay base resources
+
return parameters.flatMap { parameter ->
parameter.dataTypes.map { dataType ->
arrayOf(dataType, parameter)
@@ -188,10 +229,15 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() {
private val valueThree by lazy { mapToString(parameter.valueThree) }
private val valueFour by lazy { mapToString(parameter.valueFour) }
- private fun openOne() = PROVIDER_ONE.openProvider(dataType)
- private fun openTwo() = PROVIDER_TWO.openProvider(dataType)
- private fun openThree() = PROVIDER_THREE.openProvider(dataType)
- private fun openFour() = PROVIDER_FOUR.openProvider(dataType)
+ private fun openOne() = PROVIDER_ONE.openProvider(dataType,
+ parameter.assetProviderOne?.invoke())
+ private fun openTwo() = PROVIDER_TWO.openProvider(dataType,
+ parameter.assetProviderTwo?.invoke())
+ private fun openThree() = PROVIDER_THREE.openProvider(dataType,
+ parameter.assetProviderThree?.invoke())
+ private fun openFour() = PROVIDER_FOUR.openProvider(dataType,
+ parameter.assetProviderFour?.invoke())
+ private fun openEmpty() = PROVIDER_EMPTY.openProvider(DataType.EMPTY, null)
// Class method for syntax highlighting purposes
private fun getValue(c: Context = context) = parameter.getValue(c.resources)
@@ -289,6 +335,27 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() {
assertEquals(valueOriginal, getValue())
}
+ @Test
+ fun emptyProvider() {
+ val testOne = openOne()
+ val testTwo = openTwo()
+ val testEmpty = openEmpty()
+ val loader = ResourcesLoader()
+
+ resources.addLoaders(loader)
+ loader.providers = listOf(testOne, testEmpty, testTwo)
+ assertEquals(valueTwo, getValue())
+
+ loader.removeProvider(testTwo)
+ assertEquals(valueOne, getValue())
+
+ loader.removeProvider(testOne)
+ assertEquals(valueOriginal, getValue())
+
+ loader.providers = Collections.emptyList()
+ assertEquals(valueOriginal, getValue())
+ }
+
@Test(expected = UnsupportedOperationException::class)
fun getProvidersDoesNotLeakMutability() {
val testOne = openOne()
@@ -476,6 +543,9 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() {
loader1.removeProvider(testOne)
assertEquals(valueFour, getValue())
+
+ loader2.removeProvider(testFour)
+ assertEquals(valueThree, getValue())
}
private fun createContext(context: Context, id: Int): Context {
@@ -644,15 +714,29 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() {
}
data class Parameter(
- val testPrefix: String,
- val getValue: Resources.() -> String,
- val valueOriginal: Map,
- val valueOne: Map,
- val valueTwo: Map,
- val valueThree: Map,
- val valueFour: Map,
- val dataTypes: List
+ val testPrefix: String,
+ val getValue: Resources.() -> String,
+ val valueOriginal: Map,
+ val valueOne: Map,
+ val assetProviderOne: (() -> MemoryAssetsProvider)? = null,
+ val valueTwo: Map,
+ val assetProviderTwo: (() -> MemoryAssetsProvider)? = null,
+ val valueThree: Map,
+ val assetProviderThree: (() -> MemoryAssetsProvider)? = null,
+ val valueFour: Map,
+ val assetProviderFour: (() -> MemoryAssetsProvider)? = null,
+ val dataTypes: List
) {
+ constructor(testPrefix: String,
+ getValue: Resources.() -> String,
+ valueOriginal : Map,
+ valueOne: Map,
+ valueTwo: Map,
+ valueThree: Map,
+ valueFour: Map,
+ dataTypes: List): this(testPrefix, getValue, valueOriginal, valueOne,
+ null, valueTwo, null, valueThree, null, valueFour, null, dataTypes)
+
override fun toString() = testPrefix
}
}
diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp
index f5bf84f18a89d..202651dc86d58 100644
--- a/libs/androidfw/ApkAssets.cpp
+++ b/libs/androidfw/ApkAssets.cpp
@@ -144,8 +144,8 @@ class ZipAssetsProvider : public AssetsProvider {
}
protected:
- std::unique_ptr OpenInternal(
- const std::string& path, Asset::AccessMode mode, bool* file_exists) const override {
+ std::unique_ptr OpenInternal(
+ const std::string& path, Asset::AccessMode mode, bool* file_exists) const override {
if (file_exists) {
*file_exists = false;
}
@@ -292,49 +292,91 @@ class EmptyAssetsProvider : public AssetsProvider {
DISALLOW_COPY_AND_ASSIGN(EmptyAssetsProvider);
};
-std::unique_ptr ApkAssets::Load(const std::string& path,
- const package_property_t flags) {
+// AssetProvider implementation
+class MultiAssetsProvider : public AssetsProvider {
+ public:
+ ~MultiAssetsProvider() override = default;
+
+ static std::unique_ptr Create(
+ std::unique_ptr child, std::unique_ptr parent) {
+ CHECK(parent != nullptr) << "parent provider must not be null";
+ return (!child) ? std::move(parent)
+ : std::unique_ptr(new MultiAssetsProvider(
+ std::move(child), std::move(parent)));
+ }
+
+ bool ForEachFile(const std::string& root_path,
+ const std::function& f) const override {
+ // TODO: Only call the function once for files defined in the parent and child
+ return child_->ForEachFile(root_path, f) && parent_->ForEachFile(root_path, f);
+ }
+
+ protected:
+ std::unique_ptr OpenInternal(
+ const std::string& path, Asset::AccessMode mode, bool* file_exists) const override {
+ auto asset = child_->Open(path, mode, file_exists);
+ return (asset) ? std::move(asset) : parent_->Open(path, mode, file_exists);
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MultiAssetsProvider);
+
+ MultiAssetsProvider(std::unique_ptr child,
+ std::unique_ptr parent)
+ : child_(std::move(child)), parent_(std::move(parent)) { }
+
+ std::unique_ptr child_;
+ std::unique_ptr parent_;
+};
+
+// Opens the archive using the file path. Calling CloseArchive on the zip handle will close the
+// file.
+std::unique_ptr ApkAssets::Load(
+ const std::string& path, const package_property_t flags,
+ std::unique_ptr override_asset) {
auto assets = ZipAssetsProvider::Create(path);
- return (assets) ? LoadImpl(std::move(assets), path, nullptr /*idmap_asset*/,
- nullptr /*loaded_idmap*/, flags)
+ return (assets) ? LoadImpl(std::move(assets), path, flags, std::move(override_asset))
: nullptr;
}
-std::unique_ptr ApkAssets::LoadFromFd(unique_fd fd,
- const std::string& friendly_name,
- const package_property_t flags,
- const off64_t offset,
- const off64_t length) {
+// Opens the archive using the file file descriptor with the specified file offset and read length.
+// If the `assume_ownership` parameter is 'true' calling CloseArchive will close the file.
+std::unique_ptr ApkAssets::LoadFromFd(
+ unique_fd fd, const std::string& friendly_name, const package_property_t flags,
+ std::unique_ptr override_asset, const off64_t offset,
+ const off64_t length) {
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;
+
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)
+ return (assets) ? LoadImpl(std::move(assets), friendly_name, flags, std::move(override_asset))
: nullptr;
}
-std::unique_ptr ApkAssets::LoadTable(const std::string& path,
- const package_property_t flags) {
- auto resources_asset = CreateAssetFromFile(path);
- return (resources_asset) ? LoadTableImpl(std::move(resources_asset), path, flags)
- : nullptr;
+std::unique_ptr ApkAssets::LoadTable(
+ const std::string& path, const package_property_t flags,
+ std::unique_ptr override_asset) {
+
+ auto assets = CreateAssetFromFile(path);
+ return (assets) ? LoadTableImpl(std::move(assets), path, flags, std::move(override_asset))
+ : nullptr;
}
-std::unique_ptr ApkAssets::LoadTableFromFd(unique_fd fd,
- const std::string& friendly_name,
- const package_property_t flags,
- const off64_t offset,
- const off64_t length) {
- auto resources_asset = CreateAssetFromFd(std::move(fd), nullptr /* path */, offset, length);
- return (resources_asset) ? LoadTableImpl(std::move(resources_asset), friendly_name, flags)
- : nullptr;
+std::unique_ptr ApkAssets::LoadTableFromFd(
+ unique_fd fd, const std::string& friendly_name, const package_property_t flags,
+ std::unique_ptr override_asset, const off64_t offset,
+ const off64_t length) {
+
+ auto assets = CreateAssetFromFd(std::move(fd), nullptr /* path */, offset, length);
+ return (assets) ? LoadTableImpl(std::move(assets), friendly_name, flags,
+ std::move(override_asset))
+ : nullptr;
}
std::unique_ptr ApkAssets::LoadOverlay(const std::string& idmap_path,
const package_property_t flags) {
CHECK((flags & PROPERTY_LOADER) == 0U) << "Cannot load RROs through loaders";
-
std::unique_ptr idmap_asset = CreateAssetFromFile(idmap_path);
if (idmap_asset == nullptr) {
return {};
@@ -351,23 +393,28 @@ std::unique_ptr ApkAssets::LoadOverlay(const std::string& idmap
auto overlay_path = loaded_idmap->OverlayApkPath();
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)
+ return (assets) ? LoadImpl(std::move(assets), overlay_path, flags | PROPERTY_OVERLAY,
+ nullptr /* override_asset */, std::move(idmap_asset),
+ std::move(loaded_idmap))
: nullptr;
}
-std::unique_ptr ApkAssets::LoadFromDir(const std::string& path,
- const package_property_t flags) {
+std::unique_ptr ApkAssets::LoadFromDir(
+ const std::string& path, const package_property_t flags,
+ std::unique_ptr override_asset) {
+
auto assets = DirectoryAssetsProvider::Create(path);
- return (assets) ? LoadImpl(std::move(assets), path, nullptr /*idmap_asset*/,
- nullptr /*loaded_idmap*/, flags)
+ return (assets) ? LoadImpl(std::move(assets), path, flags, std::move(override_asset))
: nullptr;
}
-std::unique_ptr ApkAssets::LoadEmpty(const package_property_t flags) {
- std::unique_ptr loaded_apk(new ApkAssets(
- std::unique_ptr(new EmptyAssetsProvider()), "empty" /* path */,
- -1 /* last_mod-time */, flags));
+std::unique_ptr ApkAssets::LoadEmpty(
+ const package_property_t flags, std::unique_ptr override_asset) {
+
+ auto assets = (override_asset) ? std::move(override_asset)
+ : std::unique_ptr(new EmptyAssetsProvider());
+ std::unique_ptr loaded_apk(new ApkAssets(std::move(assets), "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);
@@ -413,27 +460,30 @@ std::unique_ptr ApkAssets::CreateAssetFromFd(base::unique_fd fd,
Asset::AccessMode::ACCESS_RANDOM);
}
-std::unique_ptr ApkAssets::LoadImpl(std::unique_ptr assets,
- const std::string& path,
- std::unique_ptr idmap_asset,
- std::unique_ptr idmap,
- package_property_t property_flags) {
+std::unique_ptr ApkAssets::LoadImpl(
+ std::unique_ptr assets, const std::string& path,
+ package_property_t property_flags, std::unique_ptr override_assets,
+ std::unique_ptr idmap_asset, std::unique_ptr idmap) {
+
const time_t last_mod_time = getFileModDate(path.c_str());
+ // Open the resource table via mmap unless it is compressed. This logic is taken care of by Open.
+ bool resources_asset_exists = false;
+ auto resources_asset_ = assets->Open(kResourcesArsc, Asset::AccessMode::ACCESS_BUFFER,
+ &resources_asset_exists);
+
+ assets = MultiAssetsProvider::Create(std::move(override_assets), std::move(assets));
+
// Wrap the handle in a unique_ptr so it gets automatically closed.
std::unique_ptr
loaded_apk(new ApkAssets(std::move(assets), path, last_mod_time, property_flags));
- // 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);
}
+ loaded_apk->resources_asset_ = std::move(resources_asset_);
if (!loaded_apk->resources_asset_) {
LOG(ERROR) << "Failed to open '" << kResourcesArsc << "' in APK '" << path << "'.";
return {};
@@ -457,14 +507,17 @@ std::unique_ptr ApkAssets::LoadImpl(std::unique_ptr ApkAssets::LoadTableImpl(std::unique_ptr resources_asset,
- const std::string& path,
- package_property_t property_flags) {
+std::unique_ptr ApkAssets::LoadTableImpl(
+ std::unique_ptr resources_asset, const std::string& path,
+ package_property_t property_flags, std::unique_ptr override_assets) {
+
const time_t last_mod_time = getFileModDate(path.c_str());
+ auto assets = (override_assets) ? std::move(override_assets)
+ : std::unique_ptr(new EmptyAssetsProvider());
+
std::unique_ptr loaded_apk(
- new ApkAssets(std::unique_ptr(new EmptyAssetsProvider()), path, last_mod_time,
- property_flags));
+ new ApkAssets(std::move(assets), path, last_mod_time, property_flags));
loaded_apk->resources_asset_ = std::move(resources_asset);
const StringPiece data(
diff --git a/libs/androidfw/include/androidfw/ApkAssets.h b/libs/androidfw/include/androidfw/ApkAssets.h
index 944476890af04..879b050b65bd5 100644
--- a/libs/androidfw/include/androidfw/ApkAssets.h
+++ b/libs/androidfw/include/androidfw/ApkAssets.h
@@ -75,33 +75,33 @@ class ApkAssets {
// 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 Load(const std::string& path,
- package_property_t flags = 0U);
+ static std::unique_ptr Load(
+ const std::string& path, package_property_t flags = 0U,
+ std::unique_ptr override_asset = nullptr);
// Creates an ApkAssets from the given file descriptor, and takes ownership of the file
// descriptor. The `friendly_name` is some name that will be used to identify the source of
// this ApkAssets in log messages and other debug scenarios.
// If `length` equals kUnknownLength, offset must equal 0; otherwise, the apk data will be read
// using the `offset` into the file descriptor and will be `length` bytes long.
- static std::unique_ptr LoadFromFd(base::unique_fd fd,
- const std::string& friendly_name,
- package_property_t flags = 0U,
- off64_t offset = 0,
- off64_t length = kUnknownLength);
+ static std::unique_ptr LoadFromFd(
+ base::unique_fd fd, const std::string& friendly_name, package_property_t flags = 0U,
+ std::unique_ptr override_asset = nullptr, off64_t offset = 0,
+ off64_t length = kUnknownLength);
// Creates an ApkAssets from the given path which points to a resources.arsc.
- static std::unique_ptr LoadTable(const std::string& path,
- package_property_t flags = 0U);
+ static std::unique_ptr LoadTable(
+ const std::string& path, package_property_t flags = 0U,
+ std::unique_ptr override_asset = nullptr);
// Creates an ApkAssets from the given file descriptor which points to an resources.arsc, and
// takes ownership of the file descriptor.
// If `length` equals kUnknownLength, offset must equal 0; otherwise, the .arsc data will be read
// using the `offset` into the file descriptor and will be `length` bytes long.
- static std::unique_ptr LoadTableFromFd(base::unique_fd fd,
- const std::string& friendly_name,
- package_property_t flags = 0U,
- off64_t offset = 0,
- off64_t length = kUnknownLength);
+ static std::unique_ptr LoadTableFromFd(
+ base::unique_fd fd, const std::string& friendly_name, package_property_t flags = 0U,
+ std::unique_ptr override_asset = nullptr, off64_t offset = 0,
+ off64_t length = kUnknownLength);
// Creates an ApkAssets from an IDMAP, which contains the original APK path, and the overlay
// data.
@@ -110,11 +110,14 @@ class ApkAssets {
// 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 LoadFromDir(const std::string& path,
- package_property_t flags = 0U);
+ static std::unique_ptr LoadFromDir(
+ const std::string& path, package_property_t flags = 0U,
+ std::unique_ptr override_asset = nullptr);
// Creates a totally empty ApkAssets with no resources table and no file entries.
- static std::unique_ptr LoadEmpty(package_property_t flags = 0U);
+ static std::unique_ptr LoadEmpty(
+ package_property_t flags = 0U,
+ std::unique_ptr override_asset = nullptr);
inline const std::string& GetPath() const {
return path_;
@@ -158,15 +161,17 @@ class ApkAssets {
private:
DISALLOW_COPY_AND_ASSIGN(ApkAssets);
- static std::unique_ptr LoadImpl(std::unique_ptr assets,
- const std::string& path,
- std::unique_ptr idmap_asset,
- std::unique_ptr idmap,
- package_property_t property_flags);
+ static std::unique_ptr LoadImpl(
+ std::unique_ptr assets, const std::string& path,
+ package_property_t property_flags,
+ std::unique_ptr override_assets = nullptr,
+ std::unique_ptr idmap_asset = nullptr,
+ std::unique_ptr idmap = nullptr);
- static std::unique_ptr LoadTableImpl(std::unique_ptr resources_asset,
- const std::string& path,
- package_property_t property_flags);
+ static std::unique_ptr LoadTableImpl(
+ std::unique_ptr resources_asset, const std::string& path,
+ package_property_t property_flags,
+ std::unique_ptr override_assets = nullptr);
ApkAssets(std::unique_ptr assets_provider,
std::string path,
diff --git a/startop/view_compiler/apk_layout_compiler.cc b/startop/view_compiler/apk_layout_compiler.cc
index e70c68852b679..eaa3e04cc8147 100644
--- a/startop/view_compiler/apk_layout_compiler.cc
+++ b/startop/view_compiler/apk_layout_compiler.cc
@@ -168,8 +168,7 @@ void CompileApkLayouts(const std::string& filename, CompilationTarget target,
void CompileApkLayoutsFd(android::base::unique_fd fd, CompilationTarget target,
std::ostream& target_out) {
constexpr const char* friendly_name{"viewcompiler assets"};
- auto assets = android::ApkAssets::LoadFromFd(
- std::move(fd), friendly_name, /*system=*/false, /*force_shared_lib=*/false);
+ auto assets = android::ApkAssets::LoadFromFd(std::move(fd), friendly_name);
CompileApkAssetsLayouts(assets, target, target_out);
}