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
This commit is contained in:
@@ -12846,14 +12846,7 @@ package android.content.res {
|
||||
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 @@ package android.content.res.loader {
|
||||
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;
|
||||
|
||||
@@ -1441,7 +1441,7 @@ public class PackageParser {
|
||||
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,
|
||||
|
||||
@@ -220,7 +220,7 @@ public class ApkLiteParseUtils {
|
||||
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(
|
||||
|
||||
@@ -20,6 +20,7 @@ import android.annotation.NonNull;
|
||||
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 @@ public final class ApkAssets {
|
||||
@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 final class ApkAssets {
|
||||
*/
|
||||
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 @@ public final class ApkAssets {
|
||||
* @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 @@ public final class ApkAssets {
|
||||
* @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 final class ApkAssets {
|
||||
*/
|
||||
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 @@ public final class ApkAssets {
|
||||
* @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 @@ public final class ApkAssets {
|
||||
* @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 @@ public final class ApkAssets {
|
||||
*
|
||||
* @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 @@ public final class ApkAssets {
|
||||
* 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
|
||||
@@ -311,6 +344,14 @@ public final class ApkAssets {
|
||||
return (mFlags & PROPERTY_LOADER) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
@@ -382,13 +423,15 @@ public final class ApkAssets {
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
@@ -27,9 +27,7 @@ import android.annotation.TestApi;
|
||||
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.FileDescriptor;
|
||||
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 @@ public final class AssetManager implements AutoCloseable {
|
||||
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 @@ public final class AssetManager implements AutoCloseable {
|
||||
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 @@ public final class AssetManager implements AutoCloseable {
|
||||
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 @@ public final class AssetManager implements AutoCloseable {
|
||||
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 @@ public final class AssetManager implements AutoCloseable {
|
||||
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 @@ public final class AssetManager implements AutoCloseable {
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
@@ -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.
|
||||
*
|
||||
* <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 @@ 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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 */
|
||||
|
||||
@@ -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<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 @@ 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<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 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<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 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<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 @@ static jlong NativeLoadFromFdOffset(JNIEnv* env, jclass /*clazz*/, const format_
|
||||
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 @@ 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,
|
||||
"<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));
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
1
core/tests/ResourceLoaderTests/assets/base_asset.txt
Normal file
1
core/tests/ResourceLoaderTests/assets/base_asset.txt
Normal file
@@ -0,0 +1 @@
|
||||
Base
|
||||
@@ -0,0 +1 @@
|
||||
LoaderOne
|
||||
@@ -0,0 +1 @@
|
||||
LoaderTwo
|
||||
@@ -0,0 +1 @@
|
||||
LoaderThree
|
||||
@@ -0,0 +1 @@
|
||||
LoaderFour
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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, 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 @@ 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<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 @@ abstract class ResourceLoaderTestBase {
|
||||
ARSC_DISK_FD_OFFSETS,
|
||||
ARSC_RAM_MEMORY,
|
||||
ARSC_RAM_MEMORY_OFFSETS,
|
||||
SPLIT,
|
||||
DIRECTORY
|
||||
EMPTY,
|
||||
DIRECTORY,
|
||||
SPLIT
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>(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 @@ 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<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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,8 +144,8 @@ class ZipAssetsProvider : public AssetsProvider {
|
||||
}
|
||||
|
||||
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 @@ class EmptyAssetsProvider : public AssetsProvider {
|
||||
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 @@ std::unique_ptr<const ApkAssets> 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<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 @@ std::unique_ptr<Asset> ApkAssets::CreateAssetFromFd(base::unique_fd fd,
|
||||
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 @@ std::unique_ptr<const ApkAssets> ApkAssets::LoadImpl(std::unique_ptr<const Asset
|
||||
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(
|
||||
|
||||
@@ -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<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 @@ 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<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 @@ class ApkAssets {
|
||||
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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user