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:
Ryan Mitchell
2020-03-11 13:15:28 -07:00
parent c07aa70270
commit 4ea1e42889
21 changed files with 678 additions and 768 deletions

View File

@@ -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;

View File

@@ -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,

View File

@@ -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(

View File

@@ -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);

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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 */

View File

@@ -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));
}

View File

@@ -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");

View File

@@ -0,0 +1 @@
Base

View File

@@ -0,0 +1 @@
LoaderOne

View File

@@ -0,0 +1 @@
LoaderTwo

View File

@@ -0,0 +1 @@
LoaderThree

View File

@@ -0,0 +1 @@
LoaderFour

View File

@@ -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)
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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(

View File

@@ -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,

View File

@@ -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);
}