Move AssetsProvider to native layer
Querying in the native layer for assets provided through
AssetsProviders does not currently work. This change refactors the
AssetProvider API to return a file descriptor that is read in the
native layer and can bubble up to the java layer.
This change also removes the InputStream API to favor of developers
using memfd_create.
Bug: 142716192
Test: atest ResourceLoaderValuesTest
Change-Id: I1a7eca0994c3b7cc32008d9a72bf91086ff0e816
diff --git a/api/current.txt b/api/current.txt
index 5ae6590..8a35cc7 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -12846,14 +12846,7 @@
package android.content.res.loader {
public interface AssetsProvider {
- method @Nullable public default java.io.InputStream loadAsset(@NonNull String, int) throws java.io.IOException;
- method @Nullable public default android.os.ParcelFileDescriptor loadAssetParcelFd(@NonNull String) throws java.io.IOException;
- }
-
- public class DirectoryAssetsProvider implements android.content.res.loader.AssetsProvider {
- ctor public DirectoryAssetsProvider(@NonNull java.io.File);
- method @Nullable public java.io.File findFile(@NonNull String);
- method @NonNull public java.io.File getDirectory();
+ method @Nullable public default android.content.res.AssetFileDescriptor loadAssetFd(@NonNull String, int);
}
public class ResourcesLoader {
@@ -12868,7 +12861,6 @@
public class ResourcesProvider implements java.lang.AutoCloseable java.io.Closeable {
method public void close();
method @NonNull public static android.content.res.loader.ResourcesProvider empty(@NonNull android.content.res.loader.AssetsProvider);
- 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;
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 5ade261..24589cf 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -1441,7 +1441,7 @@
try {
try {
apkAssets = fd != null
- ? ApkAssets.loadFromFd(fd, debugPathName, 0 /* flags */)
+ ? ApkAssets.loadFromFd(fd, debugPathName, 0 /* flags */, null /* assets */)
: ApkAssets.loadFromPath(apkPath);
} catch (IOException e) {
throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK,
diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
index 88b4c29..27399e4 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -220,7 +220,7 @@
try {
try {
apkAssets = fd != null
- ? ApkAssets.loadFromFd(fd, debugPathName, 0 /* flags */)
+ ? ApkAssets.loadFromFd(fd, debugPathName, 0 /* flags */, null /* assets */)
: ApkAssets.loadFromPath(apkPath);
} catch (IOException e) {
throw new PackageParser.PackageParserException(
diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java
index 078d175..bc41806 100644
--- a/core/java/android/content/res/ApkAssets.java
+++ b/core/java/android/content/res/ApkAssets.java
@@ -20,6 +20,7 @@
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.om.OverlayableInfo;
+import android.content.res.loader.AssetsProvider;
import android.content.res.loader.ResourcesProvider;
import com.android.internal.annotations.GuardedBy;
@@ -112,6 +113,9 @@
@PropertyFlags
private final int mFlags;
+ @Nullable
+ private final AssetsProvider mAssets;
+
/**
* Creates a new ApkAssets instance from the given path on disk.
*
@@ -133,7 +137,21 @@
*/
public static @NonNull ApkAssets loadFromPath(@NonNull String path, @PropertyFlags int flags)
throws IOException {
- return new ApkAssets(FORMAT_APK, path, flags);
+ return new ApkAssets(FORMAT_APK, path, flags, null /* assets */);
+ }
+
+ /**
+ * Creates a new ApkAssets instance from the given path on disk.
+ *
+ * @param path The path to an APK on disk.
+ * @param flags flags that change the behavior of loaded apk assets
+ * @param assets The assets provider that overrides the loading of file-based resources
+ * @return a new instance of ApkAssets.
+ * @throws IOException if a disk I/O error or parsing error occurred.
+ */
+ public static @NonNull ApkAssets loadFromPath(@NonNull String path, @PropertyFlags int flags,
+ @Nullable AssetsProvider assets) throws IOException {
+ return new ApkAssets(FORMAT_APK, path, flags, assets);
}
/**
@@ -145,12 +163,14 @@
* @param fd The FileDescriptor of an open, readable APK.
* @param friendlyName The friendly name used to identify this ApkAssets when logging.
* @param flags flags that change the behavior of loaded apk assets
+ * @param assets The assets provider that overrides the loading of file-based resources
* @return a new instance of ApkAssets.
* @throws IOException if a disk I/O error or parsing error occurred.
*/
public static @NonNull ApkAssets loadFromFd(@NonNull FileDescriptor fd,
- @NonNull String friendlyName, @PropertyFlags int flags) throws IOException {
- return new ApkAssets(FORMAT_APK, fd, friendlyName, flags);
+ @NonNull String friendlyName, @PropertyFlags int flags,
+ @Nullable AssetsProvider assets) throws IOException {
+ return new ApkAssets(FORMAT_APK, fd, friendlyName, flags, assets);
}
/**
@@ -166,13 +186,15 @@
* @param length The number of bytes of the apk, or {@link AssetFileDescriptor#UNKNOWN_LENGTH}
* if it extends to the end of the file.
* @param flags flags that change the behavior of loaded apk assets
+ * @param assets The assets provider that overrides the loading of file-based resources
* @return a new instance of ApkAssets.
* @throws IOException if a disk I/O error or parsing error occurred.
*/
public static @NonNull ApkAssets loadFromFd(@NonNull FileDescriptor fd,
- @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags)
+ @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags,
+ @Nullable AssetsProvider assets)
throws IOException {
- return new ApkAssets(FORMAT_APK, fd, friendlyName, offset, length, flags);
+ return new ApkAssets(FORMAT_APK, fd, friendlyName, offset, length, flags, assets);
}
/**
@@ -186,7 +208,7 @@
*/
public static @NonNull ApkAssets loadOverlayFromPath(@NonNull String idmapPath,
@PropertyFlags int flags) throws IOException {
- return new ApkAssets(FORMAT_IDMAP, idmapPath, flags);
+ return new ApkAssets(FORMAT_IDMAP, idmapPath, flags, null /* assets */);
}
/**
@@ -199,12 +221,14 @@
* @param fd The FileDescriptor of an open, readable resources.arsc.
* @param friendlyName The friendly name used to identify this ApkAssets when logging.
* @param flags flags that change the behavior of loaded apk assets
+ * @param assets The assets provider that overrides the loading of file-based resources
* @return a new instance of ApkAssets.
* @throws IOException if a disk I/O error or parsing error occurred.
*/
public static @NonNull ApkAssets loadTableFromFd(@NonNull FileDescriptor fd,
- @NonNull String friendlyName, @PropertyFlags int flags) throws IOException {
- return new ApkAssets(FORMAT_ARSC, fd, friendlyName, flags);
+ @NonNull String friendlyName, @PropertyFlags int flags,
+ @Nullable AssetsProvider assets) throws IOException {
+ return new ApkAssets(FORMAT_ARSC, fd, friendlyName, flags, assets);
}
/**
@@ -221,13 +245,14 @@
* @param length The number of bytes of the table, or {@link AssetFileDescriptor#UNKNOWN_LENGTH}
* if it extends to the end of the file.
* @param flags flags that change the behavior of loaded apk assets
+ * @param assets The assets provider that overrides the loading of file-based resources
* @return a new instance of ApkAssets.
* @throws IOException if a disk I/O error or parsing error occurred.
*/
public static @NonNull ApkAssets loadTableFromFd(@NonNull FileDescriptor fd,
- @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags)
- throws IOException {
- return new ApkAssets(FORMAT_ARSC, fd, friendlyName, offset, length, flags);
+ @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags,
+ @Nullable AssetsProvider assets) throws IOException {
+ return new ApkAssets(FORMAT_ARSC, fd, friendlyName, offset, length, flags, assets);
}
/**
@@ -236,12 +261,13 @@
*
* @param path The path to a directory on disk.
* @param flags flags that change the behavior of loaded apk assets
+ * @param assets The assets provider that overrides the loading of file-based resources
* @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);
+ @PropertyFlags int flags, @Nullable AssetsProvider assets) throws IOException {
+ return new ApkAssets(FORMAT_DIR, path, flags, assets);
}
/**
@@ -250,43 +276,50 @@
* tracking a separate identifier.
*
* @param flags flags that change the behavior of loaded apk assets
+ * @param assets The assets provider that overrides the loading of file-based resources
*/
@NonNull
- public static ApkAssets loadEmptyForLoader(@PropertyFlags int flags) {
- return new ApkAssets(flags);
+ public static ApkAssets loadEmptyForLoader(@PropertyFlags int flags,
+ @Nullable AssetsProvider assets) {
+ return new ApkAssets(flags, assets);
}
- private ApkAssets(@FormatType int format, @NonNull String path, @PropertyFlags int flags)
- throws IOException {
+ private ApkAssets(@FormatType int format, @NonNull String path, @PropertyFlags int flags,
+ @Nullable AssetsProvider assets) throws IOException {
Objects.requireNonNull(path, "path");
mFlags = flags;
- mNativePtr = nativeLoad(format, path, flags);
+ mNativePtr = nativeLoad(format, path, flags, assets);
mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
+ mAssets = assets;
}
private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd,
- @NonNull String friendlyName, @PropertyFlags int flags) throws IOException {
- Objects.requireNonNull(fd, "fd");
- Objects.requireNonNull(friendlyName, "friendlyName");
- mFlags = flags;
- mNativePtr = nativeLoadFd(format, fd, friendlyName, flags);
- mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
- }
-
- private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd,
- @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags)
+ @NonNull String friendlyName, @PropertyFlags int flags, @Nullable AssetsProvider assets)
throws IOException {
Objects.requireNonNull(fd, "fd");
Objects.requireNonNull(friendlyName, "friendlyName");
mFlags = flags;
- mNativePtr = nativeLoadFdOffsets(format, fd, friendlyName, offset, length, flags);
+ mNativePtr = nativeLoadFd(format, fd, friendlyName, flags, assets);
mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
+ mAssets = assets;
}
- private ApkAssets(@PropertyFlags int flags) {
+ private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd,
+ @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags,
+ @Nullable AssetsProvider assets) throws IOException {
+ Objects.requireNonNull(fd, "fd");
+ Objects.requireNonNull(friendlyName, "friendlyName");
mFlags = flags;
- mNativePtr = nativeLoadEmpty(flags);
+ mNativePtr = nativeLoadFdOffsets(format, fd, friendlyName, offset, length, flags, assets);
+ mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
+ mAssets = assets;
+ }
+
+ private ApkAssets(@PropertyFlags int flags, @Nullable AssetsProvider assets) {
+ mFlags = flags;
+ mNativePtr = nativeLoadEmpty(flags, assets);
mStringBlock = null;
+ mAssets = assets;
}
@UnsupportedAppUsage
@@ -312,6 +345,14 @@
}
/**
+ * Returns the assets provider that overrides the loading of assets present in this apk assets.
+ */
+ @Nullable
+ public AssetsProvider getAssetsProvider() {
+ return mAssets;
+ }
+
+ /**
* Retrieve a parser for a compiled XML file. This is associated with a single APK and
* <em>NOT</em> a full AssetManager. This means that shared-library references will not be
* dynamically assigned runtime package IDs.
@@ -382,13 +423,15 @@
}
private static native long nativeLoad(@FormatType int format, @NonNull String path,
- @PropertyFlags int flags) throws IOException;
- private static native long nativeLoadEmpty(@PropertyFlags int flags);
+ @PropertyFlags int flags, @Nullable AssetsProvider asset) throws IOException;
+ private static native long nativeLoadEmpty(@PropertyFlags int flags,
+ @Nullable AssetsProvider asset);
private static native long nativeLoadFd(@FormatType int format, @NonNull FileDescriptor fd,
- @NonNull String friendlyName, @PropertyFlags int flags) throws IOException;
+ @NonNull String friendlyName, @PropertyFlags int flags,
+ @Nullable AssetsProvider asset) throws IOException;
private static native long nativeLoadFdOffsets(@FormatType int format,
@NonNull FileDescriptor fd, @NonNull String friendlyName, long offset, long length,
- @PropertyFlags int flags) throws IOException;
+ @PropertyFlags int flags, @Nullable AssetsProvider asset) throws IOException;
private static native void nativeDestroy(long ptr);
private static native @NonNull String nativeGetAssetPath(long ptr);
private static native long nativeGetStringBlock(long ptr);
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index 6b9613d..7b2b939 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -27,9 +27,7 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration.NativeConfig;
-import android.content.res.loader.AssetsProvider;
import android.content.res.loader.ResourcesLoader;
-import android.content.res.loader.ResourcesProvider;
import android.os.ParcelFileDescriptor;
import android.util.ArraySet;
import android.util.Log;
@@ -44,7 +42,6 @@
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
-import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -828,13 +825,6 @@
Objects.requireNonNull(fileName, "fileName");
synchronized (this) {
ensureOpenLocked();
-
- String path = Paths.get("assets", fileName).toString();
- InputStream inputStream = searchLoaders(0, path, accessMode);
- if (inputStream != null) {
- return inputStream;
- }
-
final long asset = nativeOpenAsset(mObject, fileName, accessMode);
if (asset == 0) {
throw new FileNotFoundException("Asset file: " + fileName);
@@ -859,13 +849,6 @@
Objects.requireNonNull(fileName, "fileName");
synchronized (this) {
ensureOpenLocked();
-
- String path = Paths.get("assets", fileName).toString();
- AssetFileDescriptor fileDescriptor = searchLoadersFd(0, path);
- if (fileDescriptor != null) {
- return fileDescriptor;
- }
-
final ParcelFileDescriptor pfd = nativeOpenAssetFd(mObject, fileName, mOffsets);
if (pfd == null) {
throw new FileNotFoundException("Asset file: " + fileName);
@@ -959,12 +942,6 @@
Objects.requireNonNull(fileName, "fileName");
synchronized (this) {
ensureOpenLocked();
-
- InputStream inputStream = searchLoaders(cookie, fileName, accessMode);
- if (inputStream != null) {
- return inputStream;
- }
-
final long asset = nativeOpenNonAsset(mObject, cookie, fileName, accessMode);
if (asset == 0) {
throw new FileNotFoundException("Asset absolute file: " + fileName);
@@ -1004,12 +981,6 @@
Objects.requireNonNull(fileName, "fileName");
synchronized (this) {
ensureOpenLocked();
-
- AssetFileDescriptor fileDescriptor = searchLoadersFd(cookie, fileName);
- if (fileDescriptor != null) {
- return fileDescriptor;
- }
-
final ParcelFileDescriptor pfd =
nativeOpenNonAssetFd(mObject, cookie, fileName, mOffsets);
if (pfd == null) {
@@ -1072,15 +1043,7 @@
synchronized (this) {
ensureOpenLocked();
- final long xmlBlock;
- AssetFileDescriptor fileDescriptor = searchLoadersFd(cookie, fileName);
- if (fileDescriptor != null) {
- xmlBlock = nativeOpenXmlAssetFd(mObject, cookie,
- fileDescriptor.getFileDescriptor());
- } else {
- xmlBlock = nativeOpenXmlAsset(mObject, cookie, fileName);
- }
-
+ final long xmlBlock = nativeOpenXmlAsset(mObject, cookie, fileName);
if (xmlBlock == 0) {
throw new FileNotFoundException("Asset XML file: " + fileName);
}
@@ -1090,122 +1053,6 @@
}
}
- private ResourcesProvider findResourcesProvider(int assetCookie) {
- if (mLoaders == null) {
- return null;
- }
-
- int apkAssetsIndex = assetCookie - 1;
- if (apkAssetsIndex >= mApkAssets.length || apkAssetsIndex < 0) {
- return null;
- }
-
- final ApkAssets apkAssets = mApkAssets[apkAssetsIndex];
- if (!apkAssets.isForLoader()) {
- return null;
- }
-
- for (int i = mLoaders.length - 1; i >= 0; i--) {
- final ResourcesLoader loader = mLoaders[i];
- for (int j = 0, n = loader.getProviders().size(); j < n; j++) {
- final ResourcesProvider provider = loader.getProviders().get(j);
- if (apkAssets == provider.getApkAssets()) {
- return provider;
- }
- }
- }
-
- return null;
- }
-
- private InputStream searchLoaders(int cookie, @NonNull String fileName, int accessMode)
- throws IOException {
- if (mLoaders == null) {
- return null;
- }
-
- if (cookie == 0) {
- // A cookie of 0 means no specific ApkAssets, so search everything
- for (int i = mLoaders.length - 1; i >= 0; i--) {
- final ResourcesLoader loader = mLoaders[i];
- final List<ResourcesProvider> 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 InputStream inputStream = assetsProvider.loadAsset(
- fileName, accessMode);
- if (inputStream != null) {
- return inputStream;
- }
- } catch (IOException ignored) {
- // When searching, ignore read failures
- }
- }
- }
-
- return null;
- }
-
- final ResourcesProvider provider = findResourcesProvider(cookie);
- if (provider != null && provider.getAssetsProvider() != null) {
- return provider.getAssetsProvider().loadAsset(
- fileName, accessMode);
- }
-
- return null;
- }
-
- private AssetFileDescriptor searchLoadersFd(int cookie, @NonNull String fileName)
- throws IOException {
- if (mLoaders == null) {
- return null;
- }
-
- if (cookie == 0) {
- // A cookie of 0 means no specific ApkAssets, so search everything
- for (int i = mLoaders.length - 1; i >= 0; i--) {
- final ResourcesLoader loader = mLoaders[i];
- final List<ResourcesProvider> 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 c315494..0f8f1d1 100644
--- a/core/java/android/content/res/loader/AssetsProvider.java
+++ b/core/java/android/content/res/loader/AssetsProvider.java
@@ -18,12 +18,10 @@
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 @@
* Callback that allows the value of a file-based resources or asset to be specified or
* overridden.
*
+ * <p>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.
+ *
* <p>There are two situations in which this method will be called:
* <ul>
* <li>AssetManager is queried for an InputStream of an asset using APIs like
@@ -52,17 +54,7 @@
* @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 81c2a4c..0000000
--- 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 040b369..0a698d1 100644
--- a/core/java/android/content/res/loader/ResourcesProvider.java
+++ b/core/java/android/content/res/loader/ResourcesProvider.java
@@ -50,8 +50,6 @@
@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 @@
*/
@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 @@
@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 @@
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 @@
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 @@
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 @@
}
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 @@
@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 6acb133..fbdd406 100644
--- a/core/jni/android_content_res_ApkAssets.cpp
+++ b/core/jni/android_content_res_ApkAssets.cpp
@@ -37,6 +37,21 @@
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 @@
FORMAT_DIRECTORY = 3,
};
+class LoaderAssetsProvider : public AssetsProvider {
+ public:
+ static std::unique_ptr<AssetsProvider> Create(JNIEnv* env, jobject assets_provider) {
+ return (!assets_provider) ? nullptr
+ : std::unique_ptr<AssetsProvider>(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<Asset> 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<jint>(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<off64_t>(mOffset),
+ static_cast<off64_t>(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 @@
ATRACE_NAME(base::StringPrintf("LoadApkAssets(%s)", path.c_str()).c_str());
+ auto loader_assets = LoaderAssetsProvider::Create(env, assets_provider);
std::unique_ptr<const ApkAssets> 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 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 @@
return 0;
}
+ auto loader_assets = LoaderAssetsProvider::Create(env, assets_provider);
std::unique_ptr<const ApkAssets> 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 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 @@
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<const ApkAssets> apk_assets;
switch (format) {
case FORMAT_APK:
apk_assets = ApkAssets::LoadFromFd(std::move(dup_fd), friendly_name_utf8.c_str(),
- property_flags, static_cast<off64_t>(offset),
+ property_flags, std::move(loader_assets),
+ static_cast<off64_t>(offset),
static_cast<off64_t>(length));
break;
case FORMAT_ARSC:
apk_assets = ApkAssets::LoadTableFromFd(std::move(dup_fd), friendly_name_utf8.c_str(),
- property_flags, static_cast<off64_t>(offset),
+ property_flags, std::move(loader_assets),
+ static_cast<off64_t>(offset),
static_cast<off64_t>(length));
break;
default:
@@ -199,8 +308,9 @@
return reinterpret_cast<jlong>(apk_assets.release());
}
-static jlong NativeLoadEmpty(JNIEnv* env, jclass /*clazz*/, jint flags) {
- std::unique_ptr<const ApkAssets> 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<jlong>(apk_assets.release());
}
@@ -302,11 +412,15 @@
// 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 @@
gOverlayableInfoOffsets.constructor = GetMethodIDOrDie(env, gOverlayableInfoOffsets.classObject,
"<init>", "(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 062b886..cb5a332 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -75,12 +75,6 @@
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 @@
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 0000000..8e62cc3
--- /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 0000000..0e41ffa
--- /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 0000000..bca782e
--- /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 0000000..bae8ef7
--- /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 0000000..b75d996
--- /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 da5092d..0000000
--- 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<Array<out Any?>> {
- 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 bd2bac5..4764c10 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.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 @@
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, String>): String {
+ return JSONObject(ArrayMap<String, String>().apply { putAll(m) }).toString()
+ }
+
+ /** Creates a lambda that runs multiple resources queries and concatenates the results. */
+ fun query(queries: Map<String, (Resources) -> String>): Resources.() -> String {
+ return {
+ val resultMap = ArrayMap<String, String>()
+ 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 @@
}
}
- 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.SPLIT -> {
- ResourcesProvider.loadFromSplit(context, "${this}_Split")
- }
- DataType.DIRECTORY -> {
- ResourcesProvider.loadFromDirectory(zipToDir("${this}.apk").absolutePath, null)
+ 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")
+ }
}
}
+ 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<String, FileDescriptor>()
+
+ 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 @@
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 c01db0d7..a994536 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.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 @@
private val mTestActivityRule = ActivityTestRule<TestActivity>(TestActivity::class.java)
companion object {
- /** Converts the map to a stable JSON string representation. */
- private fun mapToString(m : Map<String, String>) :String {
- return JSONObject(ArrayMap<String, String>().apply { putAll(m) }).toString()
- }
-
- /** Creates a lambda that runs multiple resources queries and concatenates the results. */
- fun query(queries : Map<String, (Resources) -> String>) :Resources.() -> String {
- return {
- val resultMap = ArrayMap<String, String>()
- 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<Any> {
@@ -109,21 +87,13 @@
// 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 @@
}
)),
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 @@
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 @@
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 @@
loader1.removeProvider(testOne)
assertEquals(valueFour, getValue())
+
+ loader2.removeProvider(testFour)
+ assertEquals(valueThree, getValue())
}
private fun createContext(context: Context, id: Int): Context {
@@ -644,15 +714,29 @@
}
data class Parameter(
- val testPrefix: String,
- val getValue: Resources.() -> String,
- val valueOriginal: Map<String, String>,
- val valueOne: Map<String, String>,
- val valueTwo: Map<String, String>,
- val valueThree: Map<String, String>,
- val valueFour: Map<String, String>,
- val dataTypes: List<DataType>
+ val testPrefix: String,
+ val getValue: Resources.() -> String,
+ val valueOriginal: Map<String, String>,
+ val valueOne: Map<String, String>,
+ val assetProviderOne: (() -> MemoryAssetsProvider)? = null,
+ val valueTwo: Map<String, String>,
+ val assetProviderTwo: (() -> MemoryAssetsProvider)? = null,
+ val valueThree: Map<String, String>,
+ val assetProviderThree: (() -> MemoryAssetsProvider)? = null,
+ val valueFour: Map<String, String>,
+ val assetProviderFour: (() -> MemoryAssetsProvider)? = null,
+ val dataTypes: List<DataType>
) {
+ constructor(testPrefix: String,
+ getValue: Resources.() -> String,
+ valueOriginal : Map<String, String>,
+ valueOne: Map<String, String>,
+ valueTwo: Map<String, String>,
+ valueThree: Map<String, String>,
+ valueFour: Map<String, String>,
+ dataTypes: List<DataType>): 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 f5bf84f..202651d 100644
--- a/libs/androidfw/ApkAssets.cpp
+++ b/libs/androidfw/ApkAssets.cpp
@@ -144,8 +144,8 @@
}
protected:
- std::unique_ptr<Asset> OpenInternal(
- const std::string& path, Asset::AccessMode mode, bool* file_exists) const override {
+ std::unique_ptr<Asset> OpenInternal(
+ const std::string& path, Asset::AccessMode mode, bool* file_exists) const override {
if (file_exists) {
*file_exists = false;
}
@@ -292,49 +292,91 @@
DISALLOW_COPY_AND_ASSIGN(EmptyAssetsProvider);
};
-std::unique_ptr<const ApkAssets> 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<const AssetsProvider> Create(
+ std::unique_ptr<const AssetsProvider> child, std::unique_ptr<const AssetsProvider> parent) {
+ CHECK(parent != nullptr) << "parent provider must not be null";
+ return (!child) ? std::move(parent)
+ : std::unique_ptr<const AssetsProvider>(new MultiAssetsProvider(
+ std::move(child), std::move(parent)));
+ }
+
+ bool ForEachFile(const std::string& root_path,
+ const std::function<void(const StringPiece&, FileType)>& 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<Asset> 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<const AssetsProvider> child,
+ std::unique_ptr<const AssetsProvider> parent)
+ : child_(std::move(child)), parent_(std::move(parent)) { }
+
+ std::unique_ptr<const AssetsProvider> child_;
+ std::unique_ptr<const AssetsProvider> parent_;
+};
+
+// Opens the archive using the file path. Calling CloseArchive on the zip handle will close the
+// file.
+std::unique_ptr<const ApkAssets> ApkAssets::Load(
+ const std::string& path, const package_property_t flags,
+ std::unique_ptr<const AssetsProvider> 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<const ApkAssets> 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<const ApkAssets> ApkAssets::LoadFromFd(
+ unique_fd fd, const std::string& friendly_name, const package_property_t flags,
+ std::unique_ptr<const AssetsProvider> 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<const ApkAssets> 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<const ApkAssets> ApkAssets::LoadTable(
+ const std::string& path, const package_property_t flags,
+ std::unique_ptr<const AssetsProvider> override_asset) {
+
+ auto assets = CreateAssetFromFile(path);
+ return (assets) ? LoadTableImpl(std::move(assets), path, flags, std::move(override_asset))
+ : nullptr;
}
-std::unique_ptr<const ApkAssets> 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<const ApkAssets> ApkAssets::LoadTableFromFd(
+ unique_fd fd, const std::string& friendly_name, const package_property_t flags,
+ std::unique_ptr<const AssetsProvider> 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<const ApkAssets> 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<Asset> idmap_asset = CreateAssetFromFile(idmap_path);
if (idmap_asset == nullptr) {
return {};
@@ -351,23 +393,28 @@
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<const ApkAssets> ApkAssets::LoadFromDir(const std::string& path,
- const package_property_t flags) {
+std::unique_ptr<const ApkAssets> ApkAssets::LoadFromDir(
+ const std::string& path, const package_property_t flags,
+ std::unique_ptr<const AssetsProvider> 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<const ApkAssets> ApkAssets::LoadEmpty(const package_property_t flags) {
- std::unique_ptr<ApkAssets> loaded_apk(new ApkAssets(
- std::unique_ptr<AssetsProvider>(new EmptyAssetsProvider()), "empty" /* path */,
- -1 /* last_mod-time */, flags));
+std::unique_ptr<const ApkAssets> ApkAssets::LoadEmpty(
+ const package_property_t flags, std::unique_ptr<const AssetsProvider> override_asset) {
+
+ auto assets = (override_asset) ? std::move(override_asset)
+ : std::unique_ptr<const AssetsProvider>(new EmptyAssetsProvider());
+ std::unique_ptr<ApkAssets> 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 @@
Asset::AccessMode::ACCESS_RANDOM);
}
-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,
- package_property_t property_flags) {
+std::unique_ptr<const ApkAssets> ApkAssets::LoadImpl(
+ std::unique_ptr<const AssetsProvider> assets, const std::string& path,
+ package_property_t property_flags, std::unique_ptr<const AssetsProvider> override_assets,
+ std::unique_ptr<Asset> idmap_asset, std::unique_ptr<const LoadedIdmap> 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<ApkAssets>
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 @@
return std::move(loaded_apk);
}
-std::unique_ptr<const ApkAssets> ApkAssets::LoadTableImpl(std::unique_ptr<Asset> resources_asset,
- const std::string& path,
- package_property_t property_flags) {
+std::unique_ptr<const ApkAssets> ApkAssets::LoadTableImpl(
+ std::unique_ptr<Asset> resources_asset, const std::string& path,
+ package_property_t property_flags, std::unique_ptr<const AssetsProvider> override_assets) {
+
const time_t last_mod_time = getFileModDate(path.c_str());
+ auto assets = (override_assets) ? std::move(override_assets)
+ : std::unique_ptr<AssetsProvider>(new EmptyAssetsProvider());
+
std::unique_ptr<ApkAssets> loaded_apk(
- new ApkAssets(std::unique_ptr<AssetsProvider>(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 9444768..879b050 100644
--- a/libs/androidfw/include/androidfw/ApkAssets.h
+++ b/libs/androidfw/include/androidfw/ApkAssets.h
@@ -75,33 +75,33 @@
// 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);
+ static std::unique_ptr<const ApkAssets> Load(
+ const std::string& path, package_property_t flags = 0U,
+ std::unique_ptr<const AssetsProvider> 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<const ApkAssets> 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<const ApkAssets> LoadFromFd(
+ base::unique_fd fd, const std::string& friendly_name, package_property_t flags = 0U,
+ std::unique_ptr<const AssetsProvider> 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<const ApkAssets> LoadTable(const std::string& path,
- package_property_t flags = 0U);
+ static std::unique_ptr<const ApkAssets> LoadTable(
+ const std::string& path, package_property_t flags = 0U,
+ std::unique_ptr<const AssetsProvider> 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<const ApkAssets> 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<const ApkAssets> LoadTableFromFd(
+ base::unique_fd fd, const std::string& friendly_name, package_property_t flags = 0U,
+ std::unique_ptr<const AssetsProvider> 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 @@
// 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);
+ static std::unique_ptr<const ApkAssets> LoadFromDir(
+ const std::string& path, package_property_t flags = 0U,
+ std::unique_ptr<const AssetsProvider> override_asset = nullptr);
// 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);
+ static std::unique_ptr<const ApkAssets> LoadEmpty(
+ package_property_t flags = 0U,
+ std::unique_ptr<const AssetsProvider> override_asset = nullptr);
inline const std::string& GetPath() const {
return path_;
@@ -158,15 +161,17 @@
private:
DISALLOW_COPY_AND_ASSIGN(ApkAssets);
- 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,
- package_property_t property_flags);
+ static std::unique_ptr<const ApkAssets> LoadImpl(
+ std::unique_ptr<const AssetsProvider> assets, const std::string& path,
+ package_property_t property_flags,
+ std::unique_ptr<const AssetsProvider> override_assets = nullptr,
+ std::unique_ptr<Asset> idmap_asset = nullptr,
+ std::unique_ptr<const LoadedIdmap> idmap = nullptr);
- static std::unique_ptr<const ApkAssets> LoadTableImpl(std::unique_ptr<Asset> resources_asset,
- const std::string& path,
- package_property_t property_flags);
+ static std::unique_ptr<const ApkAssets> LoadTableImpl(
+ std::unique_ptr<Asset> resources_asset, const std::string& path,
+ package_property_t property_flags,
+ std::unique_ptr<const AssetsProvider> override_assets = nullptr);
ApkAssets(std::unique_ptr<const AssetsProvider> assets_provider,
std::string path,
diff --git a/startop/view_compiler/apk_layout_compiler.cc b/startop/view_compiler/apk_layout_compiler.cc
index e70c688..eaa3e04 100644
--- a/startop/view_compiler/apk_layout_compiler.cc
+++ b/startop/view_compiler/apk_layout_compiler.cc
@@ -168,8 +168,7 @@
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);
}