Merge changes from topic "res_loader_dir" into rvc-dev am: b0544a733c am: c5f968d58b

Change-Id: I1a0a9b1e442ea10e34d003a3d0e0280574451bb3
This commit is contained in:
Ryan Mitchell
2020-03-20 18:27:08 +00:00
committed by Automerger Merge Worker
94 changed files with 2028 additions and 1836 deletions

View File

@@ -12872,14 +12872,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 {
@@ -12894,14 +12887,11 @@ 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 loadFromApk(@NonNull android.os.SharedMemory) throws java.io.IOException;
method @NonNull public static android.content.res.loader.ResourcesProvider loadFromApk(@NonNull android.os.SharedMemory, @Nullable android.content.res.loader.AssetsProvider) throws java.io.IOException;
method @NonNull public static android.content.res.loader.ResourcesProvider loadFromDirectory(@NonNull String, @Nullable android.content.res.loader.AssetsProvider) throws java.io.IOException;
method @NonNull public static android.content.res.loader.ResourcesProvider loadFromSplit(@NonNull android.content.Context, @NonNull String) throws java.io.IOException;
method @NonNull public static android.content.res.loader.ResourcesProvider loadFromTable(@NonNull android.os.ParcelFileDescriptor, @Nullable android.content.res.loader.AssetsProvider) throws java.io.IOException;
method @NonNull public static android.content.res.loader.ResourcesProvider loadFromTable(@NonNull android.os.SharedMemory, @Nullable android.content.res.loader.AssetsProvider) throws java.io.IOException;
}
}

View File

@@ -346,10 +346,9 @@ public class ResourcesManager {
// We must load this from disk.
if (overlay) {
apkAssets = ApkAssets.loadOverlayFromPath(overlayPathToIdmapPath(path),
false /*system*/);
apkAssets = ApkAssets.loadOverlayFromPath(overlayPathToIdmapPath(path), 0 /*flags*/);
} else {
apkAssets = ApkAssets.loadFromPath(path, false /*system*/, sharedLib);
apkAssets = ApkAssets.loadFromPath(path, sharedLib ? ApkAssets.PROPERTY_DYNAMIC : 0);
}
if (mLoadedApkAssets != null) {
@@ -1256,7 +1255,8 @@ public class ResourcesManager {
* instance uses.
*/
@Override
public void onLoadersChanged(Resources resources, List<ResourcesLoader> newLoader) {
public void onLoadersChanged(@NonNull Resources resources,
@NonNull List<ResourcesLoader> newLoader) {
synchronized (ResourcesManager.this) {
final ResourcesKey oldKey = findKeyForResourceImplLocked(resources.getImpl());
if (oldKey == null) {
@@ -1284,7 +1284,7 @@ public class ResourcesManager {
* {@code loader} to apply any changes of the set of {@link ApkAssets}.
**/
@Override
public void onLoaderUpdated(ResourcesLoader loader) {
public void onLoaderUpdated(@NonNull ResourcesLoader loader) {
synchronized (ResourcesManager.this) {
final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceImplKeys =
new ArrayMap<>();

View File

@@ -1443,7 +1443,7 @@ public class PackageParser {
try {
try {
apkAssets = fd != null
? ApkAssets.loadFromFd(fd, debugPathName, false, false)
? 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, false, false)
? ApkAssets.loadFromFd(fd, debugPathName, 0 /* flags */, null /* assets */)
: ApkAssets.loadFromPath(apkPath);
} catch (IOException e) {
throw new PackageParser.PackageParserException(

View File

@@ -15,17 +15,20 @@
*/
package android.content.res;
import android.annotation.IntDef;
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 android.text.TextUtils;
import com.android.internal.annotations.GuardedBy;
import java.io.FileDescriptor;
import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Objects;
/**
@@ -39,14 +42,79 @@ import java.util.Objects;
* @hide
*/
public final class ApkAssets {
@GuardedBy("this") private final long mNativePtr;
/**
* The apk assets contains framework resource values specified by the system.
* This allows some functions to filter out this package when computing what
* configurations/resources are available.
*/
public static final int PROPERTY_SYSTEM = 1 << 0;
/**
* The apk assets is a shared library or was loaded as a shared library by force.
* The package ids of dynamic apk assets are assigned at runtime instead of compile time.
*/
public static final int PROPERTY_DYNAMIC = 1 << 1;
/**
* The apk assets has been loaded dynamically using a {@link ResourcesProvider}.
* Loader apk assets overlay resources like RROs except they are not backed by an idmap.
*/
public static final int PROPERTY_LOADER = 1 << 2;
/**
* The apk assets is a RRO.
* An RRO overlays resource values of its target package.
*/
private static final int PROPERTY_OVERLAY = 1 << 3;
/** Flags that change the behavior of loaded apk assets. */
@IntDef(prefix = { "PROPERTY_" }, value = {
PROPERTY_SYSTEM,
PROPERTY_DYNAMIC,
PROPERTY_LOADER,
PROPERTY_OVERLAY,
})
@Retention(RetentionPolicy.SOURCE)
public @interface PropertyFlags {}
/** The path used to load the apk assets represents an APK file. */
private static final int FORMAT_APK = 0;
/** The path used to load the apk assets represents an idmap file. */
private static final int FORMAT_IDMAP = 1;
/** The path used to load the apk assets represents an resources.arsc file. */
private static final int FORMAT_ARSC = 2;
/** the path used to load the apk assets represents a directory. */
private static final int FORMAT_DIR = 3;
// Format types that change how the apk assets are loaded.
@IntDef(prefix = { "FORMAT_" }, value = {
FORMAT_APK,
FORMAT_IDMAP,
FORMAT_ARSC,
FORMAT_DIR
})
@Retention(RetentionPolicy.SOURCE)
public @interface FormatType {}
@GuardedBy("this")
private final long mNativePtr;
@Nullable
@GuardedBy("this") private final StringBlock mStringBlock;
@GuardedBy("this")
private final StringBlock mStringBlock;
@GuardedBy("this") private boolean mOpen = true;
@GuardedBy("this")
private boolean mOpen = true;
private final boolean mForLoader;
@PropertyFlags
private final int mFlags;
@Nullable
private final AssetsProvider mAssets;
/**
* Creates a new ApkAssets instance from the given path on disk.
@@ -56,59 +124,77 @@ public final class ApkAssets {
* @throws IOException if a disk I/O error or parsing error occurred.
*/
public static @NonNull ApkAssets loadFromPath(@NonNull String path) throws IOException {
return new ApkAssets(path, false /*system*/, false /*forceSharedLib*/, false /*overlay*/,
false /*arscOnly*/, false /*forLoader*/);
return loadFromPath(path, 0 /* flags */);
}
/**
* Creates a new ApkAssets instance from the given path on disk.
*
* @param path The path to an APK on disk.
* @param system When true, the APK is loaded as a system APK (framework).
* @param flags flags that change the behavior of loaded apk assets
* @return a new instance of ApkAssets.
* @throws IOException if a disk I/O error or parsing error occurred.
*/
public static @NonNull ApkAssets loadFromPath(@NonNull String path, boolean system)
public static @NonNull ApkAssets loadFromPath(@NonNull String path, @PropertyFlags int flags)
throws IOException {
return new ApkAssets(path, system, false /*forceSharedLib*/, false /*overlay*/,
false /*arscOnly*/, false /*forLoader*/);
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 system When true, the APK is loaded as a system APK (framework).
* @param forceSharedLibrary When true, any packages within the APK with package ID 0x7f are
* loaded as a shared library.
* @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, boolean system,
boolean forceSharedLibrary) throws IOException {
return new ApkAssets(path, system, forceSharedLibrary, false /*overlay*/,
false /*arscOnly*/, false /*forLoader*/);
public static @NonNull ApkAssets loadFromPath(@NonNull String path, @PropertyFlags int flags,
@Nullable AssetsProvider assets) throws IOException {
return new ApkAssets(FORMAT_APK, path, flags, assets);
}
/**
* Creates a new ApkAssets instance from the given file descriptor. Not for use by applications.
* Creates a new ApkAssets instance from the given file descriptor.
*
* Performs a dup of the underlying fd, so you must take care of still closing
* the FileDescriptor yourself (and can do that whenever you want).
*
* @param fd The FileDescriptor of an open, readable APK.
* @param friendlyName The friendly name used to identify this ApkAssets when logging.
* @param system When true, the APK is loaded as a system APK (framework).
* @param forceSharedLibrary When true, any packages within the APK with package ID 0x7f are
* loaded as a shared library.
* @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, boolean system, boolean forceSharedLibrary)
@NonNull String friendlyName, @PropertyFlags int flags,
@Nullable AssetsProvider assets) throws IOException {
return new ApkAssets(FORMAT_APK, fd, friendlyName, flags, assets);
}
/**
* Creates a new ApkAssets instance from the given file descriptor.
*
* Performs a dup of the underlying fd, so you must take care of still closing
* the FileDescriptor yourself (and can do that whenever you want).
*
* @param fd The FileDescriptor of an open, readable APK.
* @param friendlyName The friendly name used to identify this ApkAssets when logging.
* @param offset The location within the file that the apk starts. This must be 0 if length is
* {@link AssetFileDescriptor#UNKNOWN_LENGTH}.
* @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,
@Nullable AssetsProvider assets)
throws IOException {
return new ApkAssets(fd, friendlyName, system, forceSharedLibrary, false /*arscOnly*/,
false /*forLoader*/);
return new ApkAssets(FORMAT_APK, fd, friendlyName, offset, length, flags, assets);
}
/**
@@ -116,97 +202,124 @@ public final class ApkAssets {
* is encoded within the IDMAP.
*
* @param idmapPath Path to the IDMAP of an overlay APK.
* @param system When true, the APK is loaded as a system APK (framework).
* @param flags flags that change the behavior of loaded apk assets
* @return a new instance of ApkAssets.
* @throws IOException if a disk I/O error or parsing error occurred.
*/
public static @NonNull ApkAssets loadOverlayFromPath(@NonNull String idmapPath, boolean system)
throws IOException {
return new ApkAssets(idmapPath, system, false /*forceSharedLibrary*/, true /*overlay*/,
false /*arscOnly*/, false /*forLoader*/);
public static @NonNull ApkAssets loadOverlayFromPath(@NonNull String idmapPath,
@PropertyFlags int flags) throws IOException {
return new ApkAssets(FORMAT_IDMAP, idmapPath, flags, null /* assets */);
}
/**
* Creates a new ApkAssets instance from the given path on disk for use with a
* {@link ResourcesProvider}.
*
* @param path The path to an APK on disk.
* @return a new instance of ApkAssets.
* @throws IOException if a disk I/O error or parsing error occurred.
*/
public static @NonNull ApkAssets loadApkForLoader(@NonNull String path)
throws IOException {
return new ApkAssets(path, false /*system*/, false /*forceSharedLibrary*/,
false /*overlay*/, false /*arscOnly*/, true /*forLoader*/);
}
/**
* Creates a new ApkAssets instance from the given file descriptor for use with a
* {@link ResourcesProvider}.
*
* Performs a dup of the underlying fd, so you must take care of still closing
* the FileDescriptor yourself (and can do that whenever you want).
*
* @param fd The FileDescriptor of an open, readable APK.
* @return a new instance of ApkAssets.
* @throws IOException if a disk I/O error or parsing error occurred.
*/
@NonNull
public static ApkAssets loadApkForLoader(@NonNull FileDescriptor fd) throws IOException {
return new ApkAssets(fd, TextUtils.emptyIfNull(fd.toString()),
false /*system*/, false /*forceSharedLib*/, false /*arscOnly*/, true /*forLoader*/);
}
/**
* Creates a new ApkAssets instance from the given file descriptor representing an ARSC
* Creates a new ApkAssets instance from the given file descriptor representing a resources.arsc
* for use with a {@link ResourcesProvider}.
*
* Performs a dup of the underlying fd, so you must take care of still closing
* the FileDescriptor yourself (and can do that whenever you want).
*
* @param fd The FileDescriptor of an open, readable .arsc.
* @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 loadArscForLoader(@NonNull FileDescriptor fd)
throws IOException {
return new ApkAssets(fd, TextUtils.emptyIfNull(fd.toString()),
false /*system*/, false /*forceSharedLib*/, true /*arscOnly*/, true /*forLoader*/);
public static @NonNull ApkAssets loadTableFromFd(@NonNull FileDescriptor fd,
@NonNull String friendlyName, @PropertyFlags int flags,
@Nullable AssetsProvider assets) throws IOException {
return new ApkAssets(FORMAT_ARSC, fd, friendlyName, flags, assets);
}
/**
* Creates a new ApkAssets instance from the given file descriptor representing a resources.arsc
* for use with a {@link ResourcesProvider}.
*
* Performs a dup of the underlying fd, so you must take care of still closing
* the FileDescriptor yourself (and can do that whenever you want).
*
* @param fd The FileDescriptor of an open, readable resources.arsc.
* @param friendlyName The friendly name used to identify this ApkAssets when logging.
* @param offset The location within the file that the table starts. This must be 0 if length is
* {@link AssetFileDescriptor#UNKNOWN_LENGTH}.
* @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,
@Nullable AssetsProvider assets) throws IOException {
return new ApkAssets(FORMAT_ARSC, fd, friendlyName, offset, length, flags, assets);
}
/**
* Creates a new ApkAssets instance from the given directory path. The directory should have the
* file structure of an APK.
*
* @param path The path to a directory on disk.
* @param flags flags that change the behavior of loaded apk assets
* @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, @Nullable AssetsProvider assets) throws IOException {
return new ApkAssets(FORMAT_DIR, path, flags, assets);
}
/**
* Generates an entirely empty ApkAssets. Needed because the ApkAssets instance and presence
* is required for a lot of APIs, and it's easier to have a non-null reference rather than
* tracking a separate identifier.
*
* @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() {
return new ApkAssets(true);
public static ApkAssets loadEmptyForLoader(@PropertyFlags int flags,
@Nullable AssetsProvider assets) {
return new ApkAssets(flags, assets);
}
private ApkAssets(boolean forLoader) {
mForLoader = forLoader;
mNativePtr = nativeLoadEmpty(forLoader);
mStringBlock = null;
}
private ApkAssets(@NonNull String path, boolean system, boolean forceSharedLib, boolean overlay,
boolean arscOnly, boolean forLoader) throws IOException {
mForLoader = forLoader;
private ApkAssets(@FormatType int format, @NonNull String path, @PropertyFlags int flags,
@Nullable AssetsProvider assets) throws IOException {
Objects.requireNonNull(path, "path");
mNativePtr = arscOnly ? nativeLoadArsc(path, forLoader)
: nativeLoad(path, system, forceSharedLib, overlay, forLoader);
mFlags = flags;
mNativePtr = nativeLoad(format, path, flags, assets);
mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
mAssets = assets;
}
private ApkAssets(@NonNull FileDescriptor fd, @NonNull String friendlyName, boolean system,
boolean forceSharedLib, boolean arscOnly, boolean forLoader) throws IOException {
mForLoader = forLoader;
private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd,
@NonNull String friendlyName, @PropertyFlags int flags, @Nullable AssetsProvider assets)
throws IOException {
Objects.requireNonNull(fd, "fd");
Objects.requireNonNull(friendlyName, "friendlyName");
mNativePtr = arscOnly ? nativeLoadArscFromFd(fd, friendlyName, forLoader)
: nativeLoadFromFd(fd, friendlyName, system, forceSharedLib, forLoader);
mFlags = flags;
mNativePtr = nativeLoadFd(format, fd, friendlyName, flags, assets);
mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
mAssets = assets;
}
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 = 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
@@ -226,8 +339,17 @@ public final class ApkAssets {
}
}
/** Returns whether this apk assets was loaded using a {@link ResourcesProvider}. */
public boolean isForLoader() {
return mForLoader;
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;
}
/**
@@ -300,18 +422,16 @@ public final class ApkAssets {
}
}
private static native long nativeLoad(@NonNull String path, boolean system,
boolean forceSharedLib, boolean overlay, boolean forLoader)
throws IOException;
private static native long nativeLoadFromFd(@NonNull FileDescriptor fd,
@NonNull String friendlyName, boolean system, boolean forceSharedLib,
boolean forLoader)
throws IOException;
private static native long nativeLoadArsc(@NonNull String path, boolean forLoader)
throws IOException;
private static native long nativeLoadArscFromFd(@NonNull FileDescriptor fd,
@NonNull String friendlyName, boolean forLoader) throws IOException;
private static native long nativeLoadEmpty(boolean forLoader);
private static native long nativeLoad(@FormatType int format, @NonNull String path,
@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,
@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, @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;
@@ -151,12 +148,9 @@ public final class AssetManager implements AutoCloseable {
final List<ApkAssets> currentLoaderApkAssets = mLoaders.get(i).getApkAssets();
for (int j = currentLoaderApkAssets.size() - 1; j >= 0; j--) {
final ApkAssets apkAssets = currentLoaderApkAssets.get(j);
if (uniqueLoaderApkAssets.contains(apkAssets)) {
continue;
if (uniqueLoaderApkAssets.add(apkAssets)) {
loaderApkAssets.add(0, apkAssets);
}
uniqueLoaderApkAssets.add(apkAssets);
loaderApkAssets.add(0, apkAssets);
}
}
@@ -242,12 +236,12 @@ public final class AssetManager implements AutoCloseable {
try {
final ArrayList<ApkAssets> apkAssets = new ArrayList<>();
apkAssets.add(ApkAssets.loadFromPath(frameworkPath, true /*system*/));
apkAssets.add(ApkAssets.loadFromPath(frameworkPath, ApkAssets.PROPERTY_SYSTEM));
final String[] systemIdmapPaths =
OverlayConfig.getZygoteInstance().createImmutableFrameworkIdmapsInZygote();
for (String idmapPath : systemIdmapPaths) {
apkAssets.add(ApkAssets.loadOverlayFromPath(idmapPath, true /*system*/));
apkAssets.add(ApkAssets.loadOverlayFromPath(idmapPath, ApkAssets.PROPERTY_SYSTEM));
}
sSystemApkAssetsSet = new ArraySet<>(apkAssets);
@@ -331,6 +325,42 @@ public final class AssetManager implements AutoCloseable {
}
}
/**
* Changes the {@link ResourcesLoader ResourcesLoaders} used in this AssetManager.
* @hide
*/
void setLoaders(@NonNull List<ResourcesLoader> newLoaders) {
Objects.requireNonNull(newLoaders, "newLoaders");
final ArrayList<ApkAssets> apkAssets = new ArrayList<>();
for (int i = 0; i < mApkAssets.length; i++) {
// Filter out the previous loader apk assets.
if (!mApkAssets[i].isForLoader()) {
apkAssets.add(mApkAssets[i]);
}
}
if (!newLoaders.isEmpty()) {
// Filter so that assets provided by multiple loaders are only included once
// in the final assets list. The last appearance of the ApkAssets dictates its load
// order.
final int loaderStartIndex = apkAssets.size();
final ArraySet<ApkAssets> uniqueLoaderApkAssets = new ArraySet<>();
for (int i = newLoaders.size() - 1; i >= 0; i--) {
final List<ApkAssets> currentLoaderApkAssets = newLoaders.get(i).getApkAssets();
for (int j = currentLoaderApkAssets.size() - 1; j >= 0; j--) {
final ApkAssets loaderApkAssets = currentLoaderApkAssets.get(j);
if (uniqueLoaderApkAssets.add(loaderApkAssets)) {
apkAssets.add(loaderStartIndex, loaderApkAssets);
}
}
}
}
mLoaders = newLoaders.toArray(new ResourcesLoader[0]);
setApkAssets(apkAssets.toArray(new ApkAssets[0]), true /* invalidate_caches */);
}
/**
* Invalidates the caches in this AssetManager according to the bitmask `diff`.
*
@@ -443,9 +473,10 @@ public final class AssetManager implements AutoCloseable {
final String idmapPath = "/data/resource-cache/"
+ path.substring(1).replace('/', '@')
+ "@idmap";
assets = ApkAssets.loadOverlayFromPath(idmapPath, false /*system*/);
assets = ApkAssets.loadOverlayFromPath(idmapPath, 0 /* flags */);
} else {
assets = ApkAssets.loadFromPath(path, false /*system*/, appAsLib);
assets = ApkAssets.loadFromPath(path,
appAsLib ? ApkAssets.PROPERTY_DYNAMIC : 0);
}
} catch (IOException e) {
return 0;
@@ -827,13 +858,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);
@@ -858,13 +882,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);
@@ -958,12 +975,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);
@@ -1003,12 +1014,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) {
@@ -1071,15 +1076,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);
}
@@ -1089,122 +1086,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

@@ -66,6 +66,7 @@ import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.GrowingArrayUtils;
import com.android.internal.util.Preconditions;
import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
@@ -244,7 +245,36 @@ public class Resources {
* @param resources the instance being updated
* @param newLoaders the new set of loaders for the instance
*/
void onLoadersChanged(Resources resources, List<ResourcesLoader> newLoaders);
void onLoadersChanged(@NonNull Resources resources,
@NonNull List<ResourcesLoader> newLoaders);
}
/**
* Handler that propagates updates of the {@link Resources} instance to the underlying
* {@link AssetManager} when the Resources is not registered with a
* {@link android.app.ResourcesManager}.
* @hide
*/
public class AssetManagerUpdateHandler implements UpdateCallbacks{
@Override
public void onLoadersChanged(@NonNull Resources resources,
@NonNull List<ResourcesLoader> newLoaders) {
Preconditions.checkArgument(Resources.this == resources);
final ResourcesImpl impl = mResourcesImpl;
impl.clearAllCaches();
impl.getAssets().setLoaders(newLoaders);
}
@Override
public void onLoaderUpdated(@NonNull ResourcesLoader loader) {
final ResourcesImpl impl = mResourcesImpl;
final AssetManager assets = impl.getAssets();
if (assets.getLoaders().contains(loader)) {
impl.clearAllCaches();
assets.setLoaders(assets.getLoaders());
}
}
}
/**
@@ -2367,8 +2397,9 @@ public class Resources {
private void checkCallbacksRegistered() {
if (mCallbacks == null) {
throw new IllegalArgumentException("Cannot modify resource loaders of Resources"
+ " instances created outside of ResourcesManager");
// Fallback to updating the underlying AssetManager if the Resources is not associated
// with a ResourcesManager.
mCallbacks = new AssetManagerUpdateHandler();
}
}
@@ -2388,6 +2419,9 @@ public class Resources {
* Adds a loader to the list of loaders. If the loader is already present in the list, the list
* will not be modified.
*
* <p>This should only be called from the UI thread to avoid lock contention when propagating
* loader changes.
*
* @param loaders the loaders to add
*/
public void addLoaders(@NonNull ResourcesLoader... loaders) {
@@ -2419,6 +2453,9 @@ public class Resources {
* Removes loaders from the list of loaders. If the loader is not present in the list, the list
* will not be modified.
*
* <p>This should only be called from the UI thread to avoid lock contention when propagating
* loader changes.
*
* @param loaders the loaders to remove
*/
public void removeLoaders(@NonNull ResourcesLoader... loaders) {
@@ -2448,6 +2485,9 @@ public class Resources {
/**
* Removes all {@link ResourcesLoader ResourcesLoader(s)}.
*
* <p>This should only be called from the UI thread to avoid lock contention when propagating
* loader changes.
* @hide
*/
@VisibleForTesting

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

@@ -45,6 +45,11 @@ import java.util.List;
*
* <p>Providers retrieved with {@link #getProviders()} are listed in increasing precedence order. A
* provider will override the resources and assets of providers listed before itself.
*
* <p>Modifying the list of providers a loader contains or the list of loaders a Resources object
* contains can cause lock contention with the UI thread. APIs that modify the lists of loaders or
* providers should only be used on the UI thread. Providers can be instantiated on any thread
* without causing lock contention.
*/
public class ResourcesLoader {
private final Object mLock = new Object();
@@ -88,6 +93,9 @@ public class ResourcesLoader {
* Appends a provider to the end of the provider list. If the provider is already present in the
* loader list, the list will not be modified.
*
* <p>This should only be called from the UI thread to avoid lock contention when propagating
* provider changes.
*
* @param resourcesProvider the provider to add
*/
public void addProvider(@NonNull ResourcesProvider resourcesProvider) {
@@ -102,6 +110,9 @@ public class ResourcesLoader {
* Removes a provider from the provider list. If the provider is not present in the provider
* list, the list will not be modified.
*
* <p>This should only be called from the UI thread to avoid lock contention when propagating
* provider changes.
*
* @param resourcesProvider the provider to remove
*/
public void removeProvider(@NonNull ResourcesProvider resourcesProvider) {
@@ -115,6 +126,9 @@ public class ResourcesLoader {
/**
* Sets the list of providers.
*
* <p>This should only be called from the UI thread to avoid lock contention when propagating
* provider changes.
*
* @param resourcesProviders the new providers
*/
public void setProviders(@NonNull List<ResourcesProvider> resourcesProviders) {
@@ -124,7 +138,12 @@ public class ResourcesLoader {
}
}
/** Removes all {@link ResourcesProvider ResourcesProvider(s)}. */
/**
* Removes all {@link ResourcesProvider ResourcesProvider(s)}.
*
* <p>This should only be called from the UI thread to avoid lock contention when propagating
* provider changes.
*/
public void clearProviders() {
synchronized (mLock) {
mProviders = null;
@@ -206,7 +225,6 @@ public class ResourcesLoader {
return true;
}
/**
* Invokes registered callbacks when the list of {@link ResourcesProvider} instances this loader
* uses changes.

View File

@@ -21,19 +21,21 @@ import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.res.ApkAssets;
import android.content.res.AssetFileDescriptor;
import android.os.ParcelFileDescriptor;
import android.os.SharedMemory;
import android.util.Log;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import java.io.Closeable;
import java.io.File;
import java.io.IOException;
/**
* Provides methods to load resources data from APKs ({@code .apk}) and resources tables
* {@code .arsc} for use with {@link ResourcesLoader ResourcesLoader(s)}.
* (eg. {@code resources.arsc}) for use with {@link ResourcesLoader ResourcesLoader(s)}.
*/
public class ResourcesProvider implements AutoCloseable, Closeable {
private static final String TAG = "ResourcesProvider";
@@ -48,73 +50,111 @@ 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 assets
* that are not associated with resource identifiers.
* Creates an empty ResourcesProvider with no resource data. This is useful for loading
* file-based assets not associated with resource identifiers.
*
* @param assetsProvider the assets provider that overrides the loading of file-based resources
* @param assetsProvider the assets provider that implements the loading of file-based resources
*/
@NonNull
public static ResourcesProvider empty(@NonNull AssetsProvider assetsProvider) {
return new ResourcesProvider(ApkAssets.loadEmptyForLoader(), assetsProvider);
return new ResourcesProvider(ApkAssets.loadEmptyForLoader(ApkAssets.PROPERTY_LOADER,
assetsProvider));
}
/**
* Creates a ResourcesProvider from an APK ({@code .apk}) file descriptor.
*
* The file descriptor is duplicated and the original may be closed by the application at any
* <p>The file descriptor is duplicated and the original may be closed by the application at any
* time without affecting the ResourcesProvider.
*
* @param fileDescriptor the file descriptor of the APK to load
*
* @see ParcelFileDescriptor#open(File, int)
* @see android.system.Os#memfd_create(String, int)
*/
@NonNull
public static ResourcesProvider loadFromApk(@NonNull ParcelFileDescriptor fileDescriptor)
throws IOException {
return loadFromApk(fileDescriptor, null);
return loadFromApk(fileDescriptor, null /* assetsProvider */);
}
/**
* Creates a ResourcesProvider from an APK ({@code .apk}) file descriptor.
*
* The file descriptor is duplicated and the original may be closed by the application at any
* <p>The file descriptor is duplicated and the original may be closed by the application at any
* time without affecting the ResourcesProvider.
*
* <p>The assets provider can override the loading of files within the APK and can provide
* entirely new files that do not exist in the APK.
*
* @param fileDescriptor the file descriptor of the APK to load
* @param assetsProvider the assets provider that overrides the loading of file-based resources
*
* @see ParcelFileDescriptor#open(File, int)
* @see android.system.Os#memfd_create(String, int)
*/
@NonNull
public static ResourcesProvider loadFromApk(@NonNull ParcelFileDescriptor fileDescriptor,
@Nullable AssetsProvider assetsProvider)
throws IOException {
return new ResourcesProvider(
ApkAssets.loadApkForLoader(fileDescriptor.getFileDescriptor()), assetsProvider);
return new ResourcesProvider(ApkAssets.loadFromFd(fileDescriptor.getFileDescriptor(),
fileDescriptor.toString(), ApkAssets.PROPERTY_LOADER, assetsProvider));
}
/**
* Creates a ResourcesProvider from an {@code .apk} file representation in memory.
* Creates a ResourcesProvider from an APK ({@code .apk}) file descriptor.
*
* @param sharedMemory the shared memory containing the data of the APK to load
* <p>The file descriptor is duplicated and the original may be closed by the application at any
* time without affecting the ResourcesProvider.
*
* <p>The assets provider can override the loading of files within the APK and can provide
* entirely new files that do not exist in the APK.
*
* @param fileDescriptor the file descriptor of the APK to load
* @param offset The location within the file that the apk starts. This must be 0 if length is
* {@link AssetFileDescriptor#UNKNOWN_LENGTH}.
* @param length The number of bytes of the apk, or {@link AssetFileDescriptor#UNKNOWN_LENGTH}
* if it extends to the end of the file.
* @param assetsProvider the assets provider that overrides the loading of file-based resources
*
* @see ParcelFileDescriptor#open(File, int)
* @see android.system.Os#memfd_create(String, int)
* @hide
*/
@VisibleForTesting
@NonNull
public static ResourcesProvider loadFromApk(@NonNull SharedMemory sharedMemory)
public static ResourcesProvider loadFromApk(@NonNull ParcelFileDescriptor fileDescriptor,
long offset, long length, @Nullable AssetsProvider assetsProvider)
throws IOException {
return loadFromApk(sharedMemory, null);
return new ResourcesProvider(ApkAssets.loadFromFd(fileDescriptor.getFileDescriptor(),
fileDescriptor.toString(), offset, length, ApkAssets.PROPERTY_LOADER,
assetsProvider));
}
/**
* Creates a ResourcesProvider from an {@code .apk} file representation in memory.
* Creates a ResourcesProvider from a resources table ({@code .arsc}) file descriptor.
*
* @param sharedMemory the shared memory containing the data of the APK to load
* <p>The file descriptor is duplicated and the original may be closed by the application at any
* time without affecting the ResourcesProvider.
*
* <p>The resources table format is not an archive format and therefore cannot asset files
* within itself. The assets provider can instead provide files that are potentially referenced
* by path in the resources table.
*
* @param fileDescriptor the file descriptor of the resources table to load
* @param assetsProvider the assets provider that implements the loading of file-based resources
*
* @see ParcelFileDescriptor#open(File, int)
* @see android.system.Os#memfd_create(String, int)
*/
@NonNull
public static ResourcesProvider loadFromApk(@NonNull SharedMemory sharedMemory,
public static ResourcesProvider loadFromTable(@NonNull ParcelFileDescriptor fileDescriptor,
@Nullable AssetsProvider assetsProvider)
throws IOException {
return new ResourcesProvider(
ApkAssets.loadApkForLoader(sharedMemory.getFileDescriptor()), assetsProvider);
ApkAssets.loadTableFromFd(fileDescriptor.getFileDescriptor(),
fileDescriptor.toString(), ApkAssets.PROPERTY_LOADER, assetsProvider));
}
/**
@@ -123,30 +163,30 @@ public class ResourcesProvider implements AutoCloseable, Closeable {
* The file descriptor is duplicated and the original may be closed by the application at any
* time without affecting the ResourcesProvider.
*
* <p>The resources table format is not an archive format and therefore cannot asset files
* within itself. The assets provider can instead provide files that are potentially referenced
* by path in the resources table.
*
* @param fileDescriptor the file descriptor of the resources table to load
* @param assetsProvider the assets provider that implements the loading of file-based resources
* @param offset The location within the file that the table starts. This must be 0 if length is
* {@link AssetFileDescriptor#UNKNOWN_LENGTH}.
* @param length The number of bytes of the table, or {@link AssetFileDescriptor#UNKNOWN_LENGTH}
* if it extends to the end of the file.
* @param assetsProvider the assets provider that overrides the loading of file-based resources
*
* @see ParcelFileDescriptor#open(File, int)
* @see android.system.Os#memfd_create(String, int)
* @hide
*/
@VisibleForTesting
@NonNull
public static ResourcesProvider loadFromTable(@NonNull ParcelFileDescriptor fileDescriptor,
@Nullable AssetsProvider assetsProvider)
long offset, long length, @Nullable AssetsProvider assetsProvider)
throws IOException {
return new ResourcesProvider(
ApkAssets.loadArscForLoader(fileDescriptor.getFileDescriptor()), assetsProvider);
}
/**
* Creates a ResourcesProvider from a resources table ({@code .arsc}) file representation in
* memory.
*
* @param sharedMemory the shared memory containing the data of the resources table to load
* @param assetsProvider the assets provider that overrides the loading of file-based resources
*/
@NonNull
public static ResourcesProvider loadFromTable(@NonNull SharedMemory sharedMemory,
@Nullable AssetsProvider assetsProvider)
throws IOException {
return new ResourcesProvider(
ApkAssets.loadArscForLoader(sharedMemory.getFileDescriptor()), assetsProvider);
ApkAssets.loadTableFromFd(fileDescriptor.getFileDescriptor(),
fileDescriptor.toString(), offset, length, ApkAssets.PROPERTY_LOADER,
assetsProvider));
}
/**
@@ -166,18 +206,28 @@ public class ResourcesProvider implements AutoCloseable, Closeable {
}
String splitPath = appInfo.getSplitCodePaths()[splitIndex];
return new ResourcesProvider(ApkAssets.loadApkForLoader(splitPath), null);
return new ResourcesProvider(ApkAssets.loadFromPath(splitPath, ApkAssets.PROPERTY_LOADER,
null /* assetsProvider */));
}
private ResourcesProvider(@NonNull ApkAssets apkAssets,
@Nullable AssetsProvider assetsProvider) {
/**
* Creates a ResourcesProvider from a directory path.
*
* File-based resources will be resolved within the directory as if the directory is an APK.
*
* @param path the path of the directory to treat as an APK
* @param assetsProvider the assets provider that overrides the loading of file-based resources
*/
@NonNull
public static ResourcesProvider loadFromDirectory(@NonNull String path,
@Nullable AssetsProvider assetsProvider) throws IOException {
return new ResourcesProvider(ApkAssets.loadFromDir(path, ApkAssets.PROPERTY_LOADER,
assetsProvider));
}
private ResourcesProvider(@NonNull ApkAssets apkAssets) {
this.mApkAssets = apkAssets;
this.mAssetsProvider = assetsProvider;
}
@Nullable
public AssetsProvider getAssetsProvider() {
return mAssetsProvider;
}
/** @hide */

View File

@@ -37,8 +37,128 @@ static struct overlayableinfo_offsets_t {
jmethodID constructor;
} gOverlayableInfoOffsets;
static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, jstring java_path, jboolean system,
jboolean force_shared_lib, jboolean overlay, jboolean for_loader) {
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 {
// The path used to load the apk assets represents an APK file.
FORMAT_APK = 0,
// The path used to load the apk assets represents an idmap file.
FORMAT_IDMAP = 1,
// The path used to load the apk assets represents an resources.arsc file.
FORMAT_ARSC = 2,
// The path used to load the apk assets represents the a directory.
FORMAT_DIRECTORY = 3,
};
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, jobject assets_provider) {
ScopedUtfChars path(env, java_path);
if (path.c_str() == nullptr) {
return 0;
@@ -46,26 +166,38 @@ static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, jstring java_path, jboole
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;
if (overlay) {
apk_assets = ApkAssets::LoadOverlay(path.c_str(), system);
} else if (force_shared_lib) {
apk_assets = ApkAssets::LoadAsSharedLibrary(path.c_str(), system);
} else {
apk_assets = ApkAssets::Load(path.c_str(), system, for_loader);
switch (format) {
case FORMAT_APK:
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, std::move(loader_assets));
break;
case FORMAT_DIRECTORY:
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);
jniThrowException(env, "java/lang/IllegalArgumentException", error_msg.c_str());
return 0;
}
if (apk_assets == nullptr) {
std::string error_msg = base::StringPrintf("Failed to load asset path %s", path.c_str());
const std::string error_msg = base::StringPrintf("Failed to load asset path %s", path.c_str());
jniThrowException(env, "java/io/IOException", error_msg.c_str());
return 0;
}
return reinterpret_cast<jlong>(apk_assets.release());
}
static jlong NativeLoadFromFd(JNIEnv* env, jclass /*clazz*/, jobject file_descriptor,
jstring friendly_name, jboolean system, jboolean force_shared_lib,
jboolean for_loader) {
static jlong NativeLoadFromFd(JNIEnv* env, jclass /*clazz*/, const format_type_t format,
jobject file_descriptor, jstring friendly_name,
const jint property_flags, jobject assets_provider) {
ScopedUtfChars friendly_name_utf8(env, friendly_name);
if (friendly_name_utf8.c_str() == nullptr) {
return 0;
@@ -85,49 +217,56 @@ static jlong NativeLoadFromFd(JNIEnv* env, jclass /*clazz*/, jobject file_descri
return 0;
}
auto dup_fd_id = dup_fd.get();
std::unique_ptr<const ApkAssets> apk_assets = ApkAssets::LoadFromFd(std::move(dup_fd),
friendly_name_utf8.c_str(),
system, force_shared_lib,
for_loader);
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, std::move(loader_assets));
break;
case FORMAT_ARSC:
apk_assets = ApkAssets::LoadTableFromFd(std::move(dup_fd), friendly_name_utf8.c_str(),
property_flags, std::move(loader_assets));
break;
default:
const std::string error_msg = base::StringPrintf("Unsupported format type %d", format);
jniThrowException(env, "java/lang/IllegalArgumentException", error_msg.c_str());
return 0;
}
if (apk_assets == nullptr) {
std::string error_msg = base::StringPrintf("Failed to load asset path %s from fd %d",
friendly_name_utf8.c_str(), dup_fd_id);
friendly_name_utf8.c_str(), fd);
jniThrowException(env, "java/io/IOException", error_msg.c_str());
return 0;
}
return reinterpret_cast<jlong>(apk_assets.release());
}
static jlong NativeLoadArsc(JNIEnv* env, jclass /*clazz*/, jstring java_path,
jboolean for_loader) {
ScopedUtfChars path(env, java_path);
if (path.c_str() == nullptr) {
return 0;
}
ATRACE_NAME(base::StringPrintf("LoadApkAssetsArsc(%s)", path.c_str()).c_str());
std::unique_ptr<const ApkAssets> apk_assets = ApkAssets::LoadArsc(path.c_str(), for_loader);
if (apk_assets == nullptr) {
std::string error_msg = base::StringPrintf("Failed to load asset path %s", path.c_str());
jniThrowException(env, "java/io/IOException", error_msg.c_str());
return 0;
}
return reinterpret_cast<jlong>(apk_assets.release());
}
static jlong NativeLoadArscFromFd(JNIEnv* env, jclass /*clazz*/, jobject file_descriptor,
jstring friendly_name, jboolean for_loader) {
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, 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");
return 0;
}
if (length < 0) {
jniThrowException(env, "java/lang/IllegalArgumentException",
"length cannot be negative");
return 0;
}
int fd = jniGetFDFromFileDescriptor(env, file_descriptor);
ATRACE_NAME(base::StringPrintf("LoadApkAssetsArscFd(%d)", fd).c_str());
if (fd < 0) {
jniThrowException(env, "java/lang/IllegalArgumentException", "Bad FileDescriptor");
return 0;
@@ -139,18 +278,39 @@ static jlong NativeLoadArscFromFd(JNIEnv* env, jclass /*clazz*/, jobject file_de
return 0;
}
std::unique_ptr<const ApkAssets> apk_assets =
ApkAssets::LoadArsc(std::move(dup_fd), friendly_name_utf8.c_str(), for_loader);
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, 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, std::move(loader_assets),
static_cast<off64_t>(offset),
static_cast<off64_t>(length));
break;
default:
const std::string error_msg = base::StringPrintf("Unsupported format type %d", format);
jniThrowException(env, "java/lang/IllegalArgumentException", error_msg.c_str());
return 0;
}
if (apk_assets == nullptr) {
std::string error_msg = base::StringPrintf("Failed to load asset path from fd %d", fd);
std::string error_msg = base::StringPrintf("Failed to load asset path %s from fd %d",
friendly_name_utf8.c_str(), fd);
jniThrowException(env, "java/io/IOException", error_msg.c_str());
return 0;
}
return reinterpret_cast<jlong>(apk_assets.release());
}
static jlong NativeLoadEmpty(JNIEnv* env, jclass /*clazz*/, jboolean for_loader) {
std::unique_ptr<const ApkAssets> apk_assets = ApkAssets::LoadEmpty(for_loader);
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());
}
@@ -180,8 +340,8 @@ static jlong NativeOpenXml(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring fil
}
const ApkAssets* apk_assets = reinterpret_cast<const ApkAssets*>(ptr);
std::unique_ptr<Asset> asset = apk_assets->Open(path_utf8.c_str(),
Asset::AccessMode::ACCESS_RANDOM);
std::unique_ptr<Asset> asset = apk_assets->GetAssetsProvider()->Open(
path_utf8.c_str(),Asset::AccessMode::ACCESS_RANDOM);
if (asset == nullptr) {
jniThrowException(env, "java/io/FileNotFoundException", path_utf8.c_str());
return 0;
@@ -252,13 +412,15 @@ static jboolean NativeDefinesOverlayable(JNIEnv* env, jclass /*clazz*/, jlong pt
// JNI registration.
static const JNINativeMethod gApkAssetsMethods[] = {
{"nativeLoad", "(Ljava/lang/String;ZZZZ)J", (void*)NativeLoad},
{"nativeLoadFromFd", "(Ljava/io/FileDescriptor;Ljava/lang/String;ZZZ)J",
(void*)NativeLoadFromFd},
{"nativeLoadArsc", "(Ljava/lang/String;Z)J", (void*)NativeLoadArsc},
{"nativeLoadArscFromFd", "(Ljava/io/FileDescriptor;Ljava/lang/String;Z)J",
(void*)NativeLoadArscFromFd},
{"nativeLoadEmpty", "(Z)J", (void*)NativeLoadEmpty},
{"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},
@@ -275,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

@@ -74,12 +74,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;
@@ -1595,12 +1589,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

@@ -21,44 +21,44 @@ android_test {
],
libs: [
"android.test.runner",
"android.test.base",
"android.test.base"
],
static_libs: [
"FrameworksResourceLoaderTests_Providers",
"androidx.test.espresso.core",
"androidx.test.ext.junit",
"androidx.test.runner",
"androidx.test.rules",
"mockito-target-minus-junit4",
"truth-prebuilt",
"truth-prebuilt"
],
resource_zips: [ ":FrameworksResourceLoaderTestsAssets" ],
resource_dirs: ["res", "resources/provider_stable/res"],
platform_apis: true,
test_suites: ["device-tests"],
aaptflags: [
"--no-compress",
],
aaptflags: ["-0 .txt"],
data: [
":FrameworksResourceLoaderTestsSplitOne",
":FrameworksResourceLoaderTestsSplitTwo",
":FrameworksResourceLoaderTestsSplitThree",
":FrameworksResourceLoaderTestsSplitFour",
],
java_resources: [ "NonAsset.txt" ]
":FrameworksResourceLoaderTests_ProviderOne_Split",
":FrameworksResourceLoaderTests_ProviderTwo_Split",
":FrameworksResourceLoaderTests_ProviderThree_Split",
":FrameworksResourceLoaderTests_ProviderFour_Split"
]
}
filegroup {
name: "FrameworksResourceLoaderTestsResources",
srcs: ["resources"],
}
genrule {
name: "FrameworksResourceLoaderTestsAssets",
srcs: [
":framework-res",
":FrameworksResourceLoaderTestsResources",
java_genrule {
name: "FrameworksResourceLoaderTests_Providers",
tools: ["soong_zip"],
srcs : [
":FrameworksResourceLoaderTests_ProviderOne",
":FrameworksResourceLoaderTests_ProviderOne_ARSC",
":FrameworksResourceLoaderTests_ProviderTwo",
":FrameworksResourceLoaderTests_ProviderTwo_ARSC",
":FrameworksResourceLoaderTests_ProviderThree",
":FrameworksResourceLoaderTests_ProviderThree_ARSC",
":FrameworksResourceLoaderTests_ProviderFour",
":FrameworksResourceLoaderTests_ProviderFour_ARSC"
],
tools: [ ":aapt2", ":soong_zip" ],
tool_files: [ "resources/compileAndLink.sh" ],
cmd: "$(location resources/compileAndLink.sh) $(location :aapt2) $(location :soong_zip) $(genDir) $(in) $(in)",
out: [ "out.zip" ]
}
out: ["FrameworksResourceLoaderTests_Providers.jar"],
cmd: "mkdir -p $(genDir)/assets/ && cp $(in) $(genDir)/assets/ && " +
"$(location soong_zip) -o $(out) " +
"-L 0 -C $(genDir) -D $(genDir)/assets/"
}

View File

@@ -22,7 +22,7 @@
<option name="cleanup-apks" value="true" />
<!-- The following value cannot be multi-line as whitespace is parsed by the installer -->
<option name="split-apk-file-names"
value="FrameworksResourceLoaderTests.apk,FrameworksResourceLoaderTestsSplitOne.apk,FrameworksResourceLoaderTestsSplitTwo.apk,FrameworksResourceLoaderTestsSplitThree.apk,FrameworksResourceLoaderTestsSplitFour.apk" />
value="FrameworksResourceLoaderTests.apk,FrameworksResourceLoaderTests_ProviderOne_Split.apk,FrameworksResourceLoaderTests_ProviderTwo_Split.apk,FrameworksResourceLoaderTests_ProviderThree_Split.apk,FrameworksResourceLoaderTests_ProviderFour_Split.apk" />
</target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest">

View File

@@ -1 +0,0 @@
Outside assets directory

View File

@@ -1 +0,0 @@
In assets directory

View File

@@ -0,0 +1 @@
In assets directory

View File

@@ -0,0 +1 @@
Base

View File

@@ -1,23 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<resources>
<string name="loader_path_change_test">Not overlaid</string>
<string name="split_overlaid">Not overlaid</string>
</resources>

View File

@@ -16,5 +16,6 @@
-->
<resources>
<public type="drawable" name="non_asset_drawable" id="0x7f010001" />
<dimen name="test">0dp</dimen>
<string name="test">Not overlaid</string>
</resources>

View File

@@ -0,0 +1,115 @@
//
// 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.
//
android_test_helper_app {
name: "FrameworksResourceLoaderTests_ProviderOne",
manifest: "AndroidManifestApp.xml",
asset_dirs: ["provider1/assets"],
resource_dirs: ["provider1/res", "provider_stable/res"],
aaptflags: ["-0 .txt"]
}
android_test_helper_app {
name: "FrameworksResourceLoaderTests_ProviderTwo",
manifest: "AndroidManifestApp.xml",
asset_dirs: ["provider2/assets"],
resource_dirs: ["provider2/res", "provider_stable/res"],
aaptflags: ["-0 .txt"]
}
android_test_helper_app {
name: "FrameworksResourceLoaderTests_ProviderThree",
manifest: "AndroidManifestApp.xml",
asset_dirs: ["provider3/assets"],
resource_dirs: ["provider3/res", "provider_stable/res"],
aaptflags: ["-0 .txt"]
}
android_test_helper_app {
name: "FrameworksResourceLoaderTests_ProviderFour",
manifest: "AndroidManifestApp.xml",
asset_dirs: ["provider4/assets"],
resource_dirs: ["provider4/res", "provider_stable/res"],
aaptflags: ["-0 .txt"]
}
// Resources.arsc(s)
genrule {
name: "FrameworksResourceLoaderTests_ProviderOne_ARSC",
srcs: [":FrameworksResourceLoaderTests_ProviderOne"],
cmd: "unzip $(in) resources.arsc -d $(genDir) && "
+ " mv $(genDir)/resources.arsc $(genDir)/FrameworksResourceLoaderTests_ProviderOne.arsc",
out: ["FrameworksResourceLoaderTests_ProviderOne.arsc"]
}
genrule {
name: "FrameworksResourceLoaderTests_ProviderTwo_ARSC",
srcs: [":FrameworksResourceLoaderTests_ProviderTwo"],
cmd: "unzip $(in) resources.arsc -d $(genDir) && "
+ " mv $(genDir)/resources.arsc $(genDir)/FrameworksResourceLoaderTests_ProviderTwo.arsc",
out: ["FrameworksResourceLoaderTests_ProviderTwo.arsc"]
}
genrule {
name: "FrameworksResourceLoaderTests_ProviderThree_ARSC",
srcs: [":FrameworksResourceLoaderTests_ProviderThree"],
cmd: "unzip $(in) resources.arsc -d $(genDir) && "
+ " mv $(genDir)/resources.arsc $(genDir)/FrameworksResourceLoaderTests_ProviderThree.arsc",
out: ["FrameworksResourceLoaderTests_ProviderThree.arsc"]
}
genrule {
name: "FrameworksResourceLoaderTests_ProviderFour_ARSC",
srcs: [":FrameworksResourceLoaderTests_ProviderFour"],
cmd: "unzip $(in) resources.arsc -d $(genDir) && "
+ " mv $(genDir)/resources.arsc $(genDir)/FrameworksResourceLoaderTests_ProviderFour.arsc",
out: ["FrameworksResourceLoaderTests_ProviderFour.arsc"]
}
// Split APKs
android_test_helper_app {
name: "FrameworksResourceLoaderTests_ProviderOne_Split",
manifest: "AndroidManifestSplit1.xml",
asset_dirs: ["provider1/assets"],
resource_dirs: ["provider1/res", "provider_stable/res"],
aaptflags: ["-0 .txt"]
}
android_test_helper_app {
name: "FrameworksResourceLoaderTests_ProviderTwo_Split",
manifest: "AndroidManifestSplit2.xml",
asset_dirs: ["provider2/assets"],
resource_dirs: ["provider2/res", "provider_stable/res"],
aaptflags: ["-0 .txt"]
}
android_test_helper_app {
name: "FrameworksResourceLoaderTests_ProviderThree_Split",
manifest: "AndroidManifestSplit3.xml",
asset_dirs: ["provider3/assets"],
resource_dirs: ["provider3/res", "provider_stable/res"],
aaptflags: ["-0 .txt"]
}
android_test_helper_app {
name: "FrameworksResourceLoaderTests_ProviderFour_Split",
manifest: "AndroidManifestSplit4.xml",
asset_dirs: ["provider4/assets"],
resource_dirs: ["provider4/res", "provider_stable/res"],
aaptflags: ["-0 .txt"]
}

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2019 The Android Open Source Project
~ 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.
@@ -15,12 +15,9 @@
~ limitations under the License.
-->
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="android.content.res.loader.test"
>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.content.res.loader.test">
<uses-sdk android:minSdkVersion="1" android:targetSdkVersion="1" />
<application/>
</manifest>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2019 The Android Open Source Project
~ 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.
@@ -16,12 +16,9 @@
-->
<!-- Mocks the framework package name so that AAPT2 assigns the correct package -->
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="android"
>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android">
<uses-sdk android:minSdkVersion="1" android:targetSdkVersion="1" />
<application/>
</manifest>

View File

@@ -18,7 +18,8 @@
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="android.content.res.loader.test"
split="split_one"
split="FrameworksResourceLoaderTests_ProviderOne_Split"
android:isFeatureSplit="true"
>
<uses-sdk android:minSdkVersion="1" android:targetSdkVersion="1" />

View File

@@ -18,7 +18,8 @@
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="android.content.res.loader.test"
split="split_three"
split="FrameworksResourceLoaderTests_ProviderTwo_Split"
android:isFeatureSplit="true"
>
<uses-sdk android:minSdkVersion="1" android:targetSdkVersion="1" />

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2019 The Android Open Source Project
~ 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.
@@ -18,7 +18,8 @@
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="android.content.res.loader.test"
split="split_two"
split="FrameworksResourceLoaderTests_ProviderThree_Split"
android:isFeatureSplit="true"
>
<uses-sdk android:minSdkVersion="1" android:targetSdkVersion="1" />

View File

@@ -18,7 +18,8 @@
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="android.content.res.loader.test"
split="split_four"
split="FrameworksResourceLoaderTests_ProviderFour_Split"
android:isFeatureSplit="true"
>
<uses-sdk android:minSdkVersion="1" android:targetSdkVersion="1" />

View File

@@ -1,136 +0,0 @@
#!/bin/bash
# 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.
aapt2=$1
soong_zip=$2
genDir=$3
FRAMEWORK_RES_APK=$4
inDir=$5
# (String name, boolean retainFiles = false, String... files)
function compileAndLink {
moduleName=$1
mkdir "$genDir"/out/"$moduleName"
args=""
for arg in "${@:4}"; do
if [[ $arg == res* ]]; then
args="$args $inDir/$arg"
else
args="$args $arg"
fi
done
$aapt2 compile -o "$genDir"/out/"$moduleName" $args
$aapt2 link \
-I "$FRAMEWORK_RES_APK" \
--manifest "$inDir"/"$3" \
-o "$genDir"/out/"$moduleName"/apk.apk \
"$genDir"/out/"$moduleName"/*.flat \
--no-compress
unzip -qq "$genDir"/out/"$moduleName"/apk.apk -d "$genDir"/out/"$moduleName"/unzip
if [[ "$2" == "APK_WITHOUT_FILE" || "$2" == "BOTH_WITHOUT_FILE" ]]; then
zip -q -d "$genDir"/out/"$moduleName"/apk.apk "res/*"
cp "$genDir"/out/"$moduleName"/apk.apk "$genDir"/output/raw/"$moduleName"Apk.apk
elif [[ "$2" == "APK" || "$2" == "BOTH" ]]; then
cp "$genDir"/out/"$moduleName"/apk.apk "$genDir"/output/raw/"$moduleName"Apk.apk
fi
if [[ "$2" == "ARSC" || "$2" == "BOTH" || "$2" == "BOTH_WITHOUT_FILE" ]]; then
zip -d "$genDir"/out/"$moduleName"/apk.apk "res/*"
cp "$genDir"/out/"$moduleName"/unzip/resources.arsc "$genDir"/output/raw/"$moduleName"Arsc.arsc
fi
}
rm -r "$genDir"/out
rm -r "$genDir"/output
rm -r "$genDir"/temp
mkdir "$genDir"/out
mkdir -p "$genDir"/output/raw
mkdir -p "$genDir"/temp/res/drawable-nodpi
mkdir -p "$genDir"/temp/res/layout
compileAndLink stringOne BOTH AndroidManifestFramework.xml res/values/string_one.xml
compileAndLink stringTwo BOTH AndroidManifestFramework.xml res/values/string_two.xml
compileAndLink stringThree BOTH AndroidManifestFramework.xml res/values/string_three.xml
compileAndLink stringFour BOTH AndroidManifestFramework.xml res/values/string_four.xml
compileAndLink dimenOne BOTH AndroidManifestFramework.xml res/values/dimen_one.xml
compileAndLink dimenTwo BOTH AndroidManifestFramework.xml res/values/dimen_two.xml
compileAndLink dimenThree BOTH AndroidManifestFramework.xml res/values/dimen_three.xml
compileAndLink dimenFour BOTH AndroidManifestFramework.xml res/values/dimen_four.xml
compileAndLink drawableMdpiWithoutFile BOTH_WITHOUT_FILE AndroidManifestFramework.xml res/values/drawable_one.xml res/drawable-mdpi/ic_delete.png
compileAndLink drawableMdpiWithFile APK AndroidManifestFramework.xml res/values/drawable_one.xml res/drawable-mdpi/ic_delete.png
compileAndLink layoutWithoutFile BOTH_WITHOUT_FILE AndroidManifestFramework.xml res/values/activity_list_item_id.xml res/layout/activity_list_item.xml
compileAndLink layoutWithFile APK AndroidManifestFramework.xml res/values/activity_list_item_id.xml res/layout/activity_list_item.xml
cp -f "$inDir"/res/layout/layout_one.xml "$genDir"/temp/res/layout/layout.xml
compileAndLink layoutOne ARSC AndroidManifestApp.xml "$genDir"/temp/res/layout/layout.xml res/values/layout_id.xml
cp -f "$genDir"/out/layoutOne/unzip/res/layout/layout.xml "$genDir"/output/raw/layoutOne.xml
cp -f "$inDir"/res/layout/layout_two.xml "$genDir"/temp/res/layout/layout.xml
compileAndLink layoutTwo ARSC AndroidManifestApp.xml "$genDir"/temp/res/layout/layout.xml res/values/layout_id.xml
cp -f "$genDir"/out/layoutTwo/unzip/res/layout/layout.xml "$genDir"/output/raw/layoutTwo.xml
cp -f "$inDir"/res/layout/layout_three.xml "$genDir"/temp/res/layout/layout.xml
compileAndLink layoutThree ARSC AndroidManifestApp.xml "$genDir"/temp/res/layout/layout.xml res/values/layout_id.xml
cp -f "$genDir"/out/layoutThree/unzip/res/layout/layout.xml "$genDir"/output/raw/layoutThree.xml
cp -f "$inDir"/res/layout/layout_four.xml "$genDir"/temp/res/layout/layout.xml
compileAndLink layoutFour ARSC AndroidManifestApp.xml "$genDir"/temp/res/layout/layout.xml res/values/layout_id.xml
cp -f "$genDir"/out/layoutFour/unzip/res/layout/layout.xml "$genDir"/output/raw/layoutFour.xml
drawableNoDpi="/res/drawable-nodpi"
inDirDrawableNoDpi="$inDir$drawableNoDpi"
cp -f "$inDirDrawableNoDpi"/nonAssetDrawableOne.xml "$genDir"/temp/res/drawable-nodpi/non_asset_drawable.xml
compileAndLink nonAssetDrawableOne ARSC AndroidManifestApp.xml "$genDir"/temp/res/drawable-nodpi/non_asset_drawable.xml res/values/non_asset_drawable_id.xml
cp -f "$genDir"/out/nonAssetDrawableOne/unzip/res/drawable-nodpi-v4/non_asset_drawable.xml "$genDir"/output/raw/nonAssetDrawableOne.xml
cp -f "$inDirDrawableNoDpi"/nonAssetDrawableTwo.xml "$genDir"/temp/res/drawable-nodpi/non_asset_drawable.xml
compileAndLink nonAssetDrawableTwo ARSC AndroidManifestApp.xml "$genDir"/temp/res/drawable-nodpi/non_asset_drawable.xml res/values/non_asset_drawable_id.xml
cp -f "$genDir"/out/nonAssetDrawableTwo/unzip/res/drawable-nodpi-v4/non_asset_drawable.xml "$genDir"/output/raw/nonAssetDrawableTwo.xml
cp -f "$inDirDrawableNoDpi"/nonAssetDrawableThree.xml "$genDir"/temp/res/drawable-nodpi/non_asset_drawable.xml
compileAndLink nonAssetDrawableThree ARSC AndroidManifestApp.xml "$genDir"/temp/res/drawable-nodpi/non_asset_drawable.xml res/values/non_asset_drawable_id.xml
cp -f "$genDir"/out/nonAssetDrawableThree/unzip/res/drawable-nodpi-v4/non_asset_drawable.xml "$genDir"/output/raw/nonAssetDrawableThree.xml
cp -f "$inDirDrawableNoDpi"/nonAssetDrawableFour.xml "$genDir"/temp/res/drawable-nodpi/non_asset_drawable.xml
compileAndLink nonAssetDrawableFour ARSC AndroidManifestApp.xml "$genDir"/temp/res/drawable-nodpi/non_asset_drawable.xml res/values/non_asset_drawable_id.xml
cp -f "$genDir"/out/nonAssetDrawableFour/unzip/res/drawable-nodpi-v4/non_asset_drawable.xml "$genDir"/output/raw/nonAssetDrawableFour.xml
cp -f "$inDirDrawableNoDpi"/nonAssetBitmapRed.png "$genDir"/temp/res/drawable-nodpi/non_asset_bitmap.png
compileAndLink nonAssetBitmapRed BOTH AndroidManifestApp.xml "$genDir"/temp/res/drawable-nodpi/non_asset_bitmap.png res/values/non_asset_bitmap_id.xml
cp -f "$genDir"/out/nonAssetBitmapRed/unzip/res/drawable-nodpi-v4/non_asset_bitmap.png "$genDir"/output/raw/nonAssetBitmapRed.png
cp -f "$inDirDrawableNoDpi"/nonAssetBitmapGreen.png "$genDir"/temp/res/drawable-nodpi/non_asset_bitmap.png
compileAndLink nonAssetBitmapGreen BOTH AndroidManifestApp.xml "$genDir"/temp/res/drawable-nodpi/non_asset_bitmap.png res/values/non_asset_bitmap_id.xml
cp -f "$genDir"/out/nonAssetBitmapGreen/unzip/res/drawable-nodpi-v4/non_asset_bitmap.png "$genDir"/output/raw/nonAssetBitmapGreen.png
cp -f "$inDirDrawableNoDpi"/nonAssetBitmapBlue.png "$genDir"/temp/res/drawable-nodpi/non_asset_bitmap.png
compileAndLink nonAssetBitmapBlue ARSC AndroidManifestApp.xml "$genDir"/temp/res/drawable-nodpi/non_asset_bitmap.png res/values/non_asset_bitmap_id.xml
cp -f "$genDir"/out/nonAssetBitmapBlue/unzip/res/drawable-nodpi-v4/non_asset_bitmap.png "$genDir"/output/raw/nonAssetBitmapBlue.png
cp -f "$inDirDrawableNoDpi"/nonAssetBitmapWhite.png "$genDir"/temp/res/drawable-nodpi/non_asset_bitmap.png
compileAndLink nonAssetBitmapWhite ARSC AndroidManifestApp.xml "$genDir"/temp/res/drawable-nodpi/non_asset_bitmap.png res/values/non_asset_bitmap_id.xml
cp -f "$genDir"/out/nonAssetBitmapWhite/unzip/res/drawable-nodpi-v4/non_asset_bitmap.png "$genDir"/output/raw/nonAssetBitmapWhite.png
$soong_zip -o "$genDir"/out.zip -C "$genDir"/output/ -D "$genDir"/output/

View File

@@ -16,6 +16,7 @@
-->
<resources>
<public type="drawable" name="ic_delete" id="0x0108001d" />
<public type="layout" name="activity_list_item" id="0x01090000" />
<public type="string" name="cancel" id="0x01040000" />
<string name="cancel">SomeRidiculouslyUnlikelyStringThree</string>
</resources>
</resources>

View File

@@ -16,6 +16,5 @@
-->
<resources>
<public type="dimen" name="app_icon_size" id="0x01050000" />
<dimen name="app_icon_size">400dp</dimen>
<string name="cancel">SomeRidiculouslyUnlikelyString</string>
</resources>

View File

@@ -0,0 +1 @@
One

View File

@@ -0,0 +1 @@
LoaderOne

View File

@@ -16,6 +16,9 @@
-->
<resources>
<public type="string" name="cancel" id="0x01040000" />
<string name="cancel">SomeRidiculouslyUnlikelyStringFour</string>
</resources>
<dimen name="test">100dp</dimen>
<string name="test">One</string>
<string name="additional">One</string>
<public type="string" name="additional" id="0x7f0400fe" />
</resources>

View File

@@ -0,0 +1 @@
Two

View File

@@ -0,0 +1 @@
LoaderTwo

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<resources>
<dimen name="test">200dp</dimen>
<string name="test">Two</string>
<string name="additional">Two</string>
<public type="string" name="additional" id="0x7f0400fe" />
</resources>

View File

@@ -0,0 +1 @@
Three

View File

@@ -0,0 +1 @@
LoaderThree

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<resources>
<dimen name="test">300dp</dimen>
<string name="test">Three</string>
<string name="additional">Three</string>
<public type="string" name="additional" id="0x7f0400fe" />
</resources>

View File

@@ -0,0 +1 @@
Four

View File

@@ -0,0 +1 @@
LoaderFour

View File

@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<resources>
<dimen name="test">400dp</dimen>
<string name="test">Four</string>
<string name="additional">Four</string>
<public type="string" name="additional" id="0x7f0400fe" />
</resources>

View File

@@ -16,6 +16,5 @@
-->
<resources>
<public type="dimen" name="app_icon_size" id="0x01050000" />
<dimen name="app_icon_size">300dp</dimen>
<public type="string" name="additional" id="0x7f0400fe" />
</resources>

View File

@@ -16,6 +16,9 @@
-->
<resources>
<public type="string" name="split_overlaid" id="0x7f040001" />
<string name="split_overlaid">Split FOUR Overlaid</string>
</resources>
<public type="dimen" name="test" id="0x7f010000" />
<public type="drawable" name="drawable_png" id="0x7f020000" />
<public type="drawable" name="drawable_xml" id="0x7f020001" />
<public type="layout" name="layout" id="0x7f030000" />
<public type="string" name="test" id="0x7f040000" />
</resources>

View File

@@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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
-->
<resources>
<public type="layout" name="activity_list_item" id="0x01090000" />
</resources>

View File

@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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
-->
<resources>
<public type="dimen" name="app_icon_size" id="0x01050000" />
<dimen name="app_icon_size">100dp</dimen>
</resources>

View File

@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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
-->
<resources>
<public type="dimen" name="app_icon_size" id="0x01050000" />
<dimen name="app_icon_size">200dp</dimen>
</resources>

View File

@@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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
-->
<resources>
<public type="drawable" name="ic_delete" id="0x0108001d" />
</resources>

View File

@@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<resources>
<public type="layout" name="layout" id="0x7f020000" />
</resources>

View File

@@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<resources>
<public type="drawable" name="non_asset_bitmap" id="0x7f010000" />
</resources>

View File

@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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
-->
<resources>
<public type="string" name="cancel" id="0x01040000" />
<string name="cancel">SomeRidiculouslyUnlikelyStringOne</string>
</resources>

View File

@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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
-->
<resources>
<public type="string" name="cancel" id="0x01040000" />
<string name="cancel">SomeRidiculouslyUnlikelyStringTwo</string>
</resources>

View File

@@ -1,19 +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.
//
android_test_helper_app {
name: "FrameworksResourceLoaderTestsSplitFour"
}

View File

@@ -1,19 +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.
//
android_test_helper_app {
name: "FrameworksResourceLoaderTestsSplitOne"
}

View File

@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<resources>
<public type="string" name="split_overlaid" id="0x7f040001" />
<string name="split_overlaid">Split ONE Overlaid</string>
</resources>

View File

@@ -1,19 +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.
//
android_test_helper_app {
name: "FrameworksResourceLoaderTestsSplitThree"
}

View File

@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<resources>
<public type="string" name="split_overlaid" id="0x7f040001" />
<string name="split_overlaid">Split THREE Overlaid</string>
</resources>

View File

@@ -1,19 +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.
//
android_test_helper_app {
name: "FrameworksResourceLoaderTestsSplitTwo"
}

View File

@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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.
-->
<resources>
<public type="string" name="split_overlaid" id="0x7f040001" />
<string name="split_overlaid">Split TWO Overlaid</string>
</resources>

View File

@@ -1,105 +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.loader.AssetsProvider
import android.content.res.loader.DirectoryAssetsProvider
import android.content.res.loader.ResourcesLoader
import android.graphics.Color
import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.ColorDrawable
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TestName
import java.io.File
class DirectoryAssetsProviderTest : ResourceLoaderTestBase() {
@get:Rule
val testName = TestName()
private lateinit var testDir: File
private lateinit var assetsProvider: AssetsProvider
private lateinit var loader: ResourcesLoader
@Before
fun setUpTestDir() {
testDir = context.filesDir.resolve("DirectoryAssetsProvider_${testName.methodName}")
assetsProvider = DirectoryAssetsProvider(testDir)
loader = ResourcesLoader()
resources.addLoaders(loader)
}
@After
fun deleteTestFiles() {
testDir.deleteRecursively()
}
@Test
fun loadDrawableXml() {
"nonAssetDrawableOne" writeTo "res/drawable-nodpi-v4/non_asset_drawable.xml"
val provider = openArsc("nonAssetDrawableOne", assetsProvider)
fun getValue() = (resources.getDrawable(R.drawable.non_asset_drawable) as ColorDrawable)
.color
assertThat(getValue()).isEqualTo(Color.parseColor("#B2D2F2"))
loader.addProvider(provider)
assertThat(getValue()).isEqualTo(Color.parseColor("#000001"))
}
@Test
fun loadDrawableBitmap() {
"nonAssetBitmapGreen" writeTo "res/drawable-nodpi-v4/non_asset_bitmap.png"
val provider = openArsc("nonAssetBitmapGreen", assetsProvider)
fun getValue() = (resources.getDrawable(R.drawable.non_asset_bitmap) as BitmapDrawable)
.bitmap.getColor(0, 0).toArgb()
assertThat(getValue()).isEqualTo(Color.MAGENTA)
loader.addProvider(provider)
assertThat(getValue()).isEqualTo(Color.GREEN)
}
@Test
fun loadXml() {
"layoutOne" writeTo "res/layout/layout.xml"
val provider = openArsc("layoutOne", assetsProvider)
fun getValue() = resources.getLayout(R.layout.layout).advanceToRoot().name
assertThat(getValue()).isEqualTo("MysteryLayout")
loader.addProvider(provider)
assertThat(getValue()).isEqualTo("RelativeLayout")
}
private infix fun String.writeTo(path: String) {
val testFile = testDir.resolve(path)
testFile.parentFile!!.mkdirs()
resources.openRawResource(rawFile(this))
.copyTo(testFile.outputStream())
}
}

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

@@ -17,29 +17,58 @@
package android.content.res.loader.test
import android.content.Context
import android.content.res.AssetManager
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 org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.eq
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.mock
import java.io.Closeable
import java.io.FileOutputStream
import java.io.File
import java.io.FileDescriptor
import java.util.zip.ZipInputStream
abstract class ResourceLoaderTestBase {
protected val PROVIDER_ONE: String = "FrameworksResourceLoaderTests_ProviderOne"
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
protected lateinit var context: Context
protected open val resources: Resources
get() = context.resources
protected open val assets: AssetManager
get() = resources.assets
protected lateinit var resources: Resources
// Track opened streams and ResourcesProviders to close them after testing
private val openedObjects = mutableListOf<Closeable>()
@@ -47,6 +76,8 @@ abstract class ResourceLoaderTestBase {
@Before
fun setUpBase() {
context = InstrumentationRegistry.getTargetContext()
.createConfigurationContext(Configuration())
resources = context.resources
}
@After
@@ -61,82 +92,207 @@ abstract class ResourceLoaderTestBase {
}
}
protected fun String.openProvider(
dataType: DataType = this@ResourceLoaderTestBase.dataType
): ResourcesProvider = when (dataType) {
DataType.APK -> {
context.copiedRawFile("${this}Apk").use {
ResourcesProvider.loadFromApk(it, mock(AssetsProvider::class.java))
}.also { openedObjects += it }
protected fun String.openProvider(dataType: DataType,
assetsProvider: MemoryAssetsProvider?): ResourcesProvider {
if (assetsProvider != null) {
openedObjects += assetsProvider
}
DataType.ARSC -> {
openArsc(this, mock(AssetsProvider::class.java))
}
DataType.SPLIT -> {
ResourcesProvider.loadFromSplit(context, this)
}
DataType.ASSET -> {
val assetsProvider = mock(AssetsProvider::class.java)
doAnswer { byteInputStream() }.`when`(assetsProvider)
.loadAsset(eq("assets/Asset.txt"), anyInt())
ResourcesProvider.empty(assetsProvider)
}
DataType.ASSET_FD -> {
val assetsProvider = mock(AssetsProvider::class.java)
doAnswer {
val file = context.filesDir.resolve("Asset.txt")
file.writeText(this)
ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY)
}.`when`(assetsProvider).loadAssetParcelFd("assets/Asset.txt")
ResourcesProvider.empty(assetsProvider)
}
DataType.NON_ASSET -> {
val assetsProvider = mock(AssetsProvider::class.java)
doAnswer {
val file = context.filesDir.resolve("NonAsset.txt")
file.writeText(this)
ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY)
}.`when`(assetsProvider).loadAssetParcelFd("NonAsset.txt")
ResourcesProvider.empty(assetsProvider)
}
DataType.NON_ASSET_DRAWABLE -> {
val assetsProvider = mock(AssetsProvider::class.java)
doAnswer { context.copiedRawFile(this) }.`when`(assetsProvider)
.loadAssetParcelFd("res/drawable-nodpi-v4/non_asset_drawable.xml")
openArsc(this, assetsProvider)
}
DataType.NON_ASSET_BITMAP -> {
val assetsProvider = mock(AssetsProvider::class.java)
doAnswer { resources.openRawResource(rawFile(this)) }
.`when`(assetsProvider)
.loadAsset(eq("res/drawable-nodpi-v4/non_asset_bitmap.png"), anyInt())
openArsc(this, assetsProvider)
}
DataType.NON_ASSET_LAYOUT -> {
val assetsProvider = mock(AssetsProvider::class.java)
doAnswer { resources.openRawResource(rawFile(this)) }.`when`(assetsProvider)
.loadAsset(eq("res/layout/layout.xml"), anyInt())
doAnswer { context.copiedRawFile(this) }.`when`(assetsProvider)
.loadAssetParcelFd("res/layout/layout.xml")
openArsc(this, 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, assetsProvider).apply {
asset.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, 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,
assetsProvider).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, 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,
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")
}
}
}
protected fun openArsc(rawName: String, assetsProvider: AssetsProvider): ResourcesProvider {
return context.copiedRawFile("${rawName}Arsc")
.use { ResourcesProvider.loadFromTable(it, assetsProvider) }
.also { openedObjects += it }
class EmptyAssetsProvider : AssetsProvider
/** An AssetsProvider that reads from a zip asset. */
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): File {
val root = File(context.filesDir, name.split('.')[0])
if (root.exists()) {
return root
}
root.mkdir()
ZipInputStream(context.assets.open(name)).use { zis ->
while (true) {
val entry = zis.nextEntry ?: break
val file = File(root, entry.name)
if (entry.isDirectory) {
continue
}
file.parentFile.mkdirs()
file.outputStream().use { output ->
var b = zis.read()
while (b != -1) {
output.write(b)
b = zis.read()
}
}
}
}
return root
}
/** Loads the asset into a temporary file stored in RAM. */
private fun loadAssetIntoMemory(
asset: AssetFileDescriptor,
leadingGarbageSize: Int = 0,
trailingGarbageSize: Int = 0
): ParcelFileDescriptor {
val originalFd = Os.memfd_create(asset.toString(), 0 /* flags */)
val fd = ParcelFileDescriptor.dup(originalFd)
Os.close(originalFd)
val input = asset.createInputStream()
FileOutputStream(fd.fileDescriptor).use { output ->
// Add garbage before the APK data
for (i in 0 until leadingGarbageSize) {
output.write(Math.random().toInt())
}
for (i in 0 until asset.length.toInt()) {
output.write(input.read())
}
// Add garbage after the APK data
for (i in 0 until trailingGarbageSize) {
output.write(Math.random().toInt())
}
}
return fd
}
enum class DataType {
APK,
ARSC,
SPLIT,
ASSET,
ASSET_FD,
NON_ASSET,
NON_ASSET_DRAWABLE,
NON_ASSET_BITMAP,
NON_ASSET_LAYOUT,
APK_DISK_FD,
APK_DISK_FD_OFFSETS,
APK_RAM_FD,
APK_RAM_OFFSETS,
ARSC_DISK_FD,
ARSC_DISK_FD_OFFSETS,
ARSC_RAM_MEMORY,
ARSC_RAM_MEMORY_OFFSETS,
EMPTY,
DIRECTORY,
SPLIT
}
}

View File

@@ -19,6 +19,7 @@ package android.content.res.loader.test
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.res.AssetManager
import android.content.res.Configuration
import android.content.res.Resources
import android.content.res.loader.ResourcesLoader
@@ -54,98 +55,177 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() {
fun parameters(): Array<Any> {
val parameters = mutableListOf<Parameter>()
// R.string
// Test resolution of resources encoded within the resources.arsc.
parameters += Parameter(
{ getString(android.R.string.cancel) },
"stringOne", { "SomeRidiculouslyUnlikelyStringOne" },
"stringTwo", { "SomeRidiculouslyUnlikelyStringTwo" },
"stringThree", { "SomeRidiculouslyUnlikelyStringThree" },
"stringFour", { "SomeRidiculouslyUnlikelyStringFour" },
listOf(DataType.APK, DataType.ARSC)
"tableBased",
query(mapOf(
"getOverlaid" to { res ->
res.getString(R.string.test)
},
"getAdditional" to { res ->
res.getString(0x7f0400fe /* R.string.additional */)
},
"getIdentifier" to { res ->
res.getString(res.getIdentifier("test", "string",
"android.content.res.loader.test"))
},
"getIdentifierAdditional" to { res ->
res.getString(res.getIdentifier("additional", "string",
"android.content.res.loader.test"))
}
)),
mapOf("getOverlaid" to "Not overlaid",
"getAdditional" to "NotFoundException",
"getIdentifier" to "Not overlaid",
"getIdentifierAdditional" to "NotFoundException"),
mapOf("getOverlaid" to "One",
"getAdditional" to "One",
"getIdentifier" to "One",
"getIdentifierAdditional" to "One"),
mapOf("getOverlaid" to "Two",
"getAdditional" to "Two",
"getIdentifier" to "Two",
"getIdentifierAdditional" to "Two"),
mapOf("getOverlaid" to "Three",
"getAdditional" to "Three",
"getIdentifier" to "Three",
"getIdentifierAdditional" to "Three"),
mapOf("getOverlaid" to "Four",
"getAdditional" to "Four",
"getIdentifier" to "Four",
"getIdentifierAdditional" to "Four"),
listOf(DataType.APK_DISK_FD, DataType.APK_DISK_FD_OFFSETS, DataType.APK_RAM_FD,
DataType.APK_RAM_OFFSETS, DataType.ARSC_DISK_FD,
DataType.ARSC_DISK_FD_OFFSETS, DataType.ARSC_RAM_MEMORY,
DataType.ARSC_RAM_MEMORY_OFFSETS, DataType.SPLIT, DataType.DIRECTORY)
)
// R.dimen
// Test resolution of file-based resources and assets with no assets provider.
parameters += Parameter(
{ getDimensionPixelSize(android.R.dimen.app_icon_size) },
"dimenOne", { 100.dpToPx(resources) },
"dimenTwo", { 200.dpToPx(resources) },
"dimenThree", { 300.dpToPx(resources) },
"dimenFour", { 400.dpToPx(resources) },
listOf(DataType.APK, DataType.ARSC)
"tableFileBased",
query(mapOf(
// Drawable xml in res directory
"drawableXml" to { res ->
(res.getDrawable(R.drawable.drawable_xml) as ColorDrawable)
.color.toString()
},
// Asset as compiled XML layout in res directory
"layout" to { res ->
res.getLayout(R.layout.layout).advanceToRoot().name
},
// Bitmap drawable in res directory
"drawablePng" to { res ->
(res.getDrawable(R.drawable.drawable_png) as BitmapDrawable)
.bitmap.getColor(0, 0).toArgb().toString()
}
)),
mapOf("drawableXml" to Color.parseColor("#B2D2F2").toString(),
"layout" to "MysteryLayout",
"drawablePng" to Color.parseColor("#FF00FF").toString()),
mapOf("drawableXml" to Color.parseColor("#000001").toString(),
"layout" to "RelativeLayout",
"drawablePng" to Color.RED.toString()),
mapOf("drawableXml" to Color.parseColor("#000002").toString(),
"layout" to "LinearLayout",
"drawablePng" to Color.GREEN.toString()),
mapOf("drawableXml" to Color.parseColor("#000003").toString(),
"layout" to "FrameLayout",
"drawablePng" to Color.BLUE.toString()),
mapOf("drawableXml" to Color.parseColor("#000004").toString(),
"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)
)
// File in the assets directory
// Test resolution of assets.
parameters += Parameter(
{ assets.open("Asset.txt").reader().readText() },
"assetOne", { "assetOne" },
"assetTwo", { "assetTwo" },
"assetFour", { "assetFour" },
"assetThree", { "assetThree" },
listOf(DataType.ASSET)
"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)
)
// From assets directory returning file descriptor
// Test assets from apk and provider
parameters += Parameter(
{ assets.openFd("Asset.txt").readText() },
"assetOne", { "assetOne" },
"assetTwo", { "assetTwo" },
"assetFour", { "assetFour" },
"assetThree", { "assetThree" },
listOf(DataType.ASSET_FD)
"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)
)
// From root directory returning file descriptor
parameters += Parameter(
{ assets.openNonAssetFd("NonAsset.txt").readText() },
"NonAssetOne", { "NonAssetOne" },
"NonAssetTwo", { "NonAssetTwo" },
"NonAssetThree", { "NonAssetThree" },
"NonAssetFour", { "NonAssetFour" },
listOf(DataType.NON_ASSET)
)
// Asset as compiled XML drawable
parameters += Parameter(
{ (getDrawable(R.drawable.non_asset_drawable) as ColorDrawable).color },
"nonAssetDrawableOne", { Color.parseColor("#000001") },
"nonAssetDrawableTwo", { Color.parseColor("#000002") },
"nonAssetDrawableThree", { Color.parseColor("#000003") },
"nonAssetDrawableFour", { Color.parseColor("#000004") },
listOf(DataType.NON_ASSET_DRAWABLE)
)
// Asset as compiled bitmap drawable
parameters += Parameter(
{
(getDrawable(R.drawable.non_asset_bitmap) as BitmapDrawable)
.bitmap.getColor(0, 0).toArgb()
},
"nonAssetBitmapRed", { Color.RED },
"nonAssetBitmapGreen", { Color.GREEN },
"nonAssetBitmapBlue", { Color.BLUE },
"nonAssetBitmapWhite", { Color.WHITE },
listOf(DataType.NON_ASSET_BITMAP)
)
// Asset as compiled XML layout
parameters += Parameter(
{ getLayout(R.layout.layout).advanceToRoot().name },
"layoutOne", { "RelativeLayout" },
"layoutTwo", { "LinearLayout" },
"layoutThree", { "FrameLayout" },
"layoutFour", { "TableLayout" },
listOf(DataType.NON_ASSET_LAYOUT)
)
// Isolated resource split
parameters += Parameter(
{ getString(R.string.split_overlaid) },
"split_one", { "Split ONE Overlaid" },
"split_two", { "Split TWO Overlaid" },
"split_three", { "Split THREE Overlaid" },
"split_four", { "Split FOUR Overlaid" },
listOf(DataType.SPLIT)
)
// 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 ->
@@ -162,23 +242,31 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() {
@field:Parameterized.Parameter(1)
lateinit var parameter: Parameter
private val valueOne by lazy { parameter.valueOne(this) }
private val valueTwo by lazy { parameter.valueTwo(this) }
private val valueThree by lazy { parameter.valueThree(this) }
private val valueFour by lazy { parameter.valueFour(this) }
private val valueOriginal by lazy { mapToString(parameter.valueOriginal) }
private val valueOne by lazy { mapToString(parameter.valueOne) }
private val valueTwo by lazy { mapToString(parameter.valueTwo) }
private val valueThree by lazy { mapToString(parameter.valueThree) }
private val valueFour by lazy { mapToString(parameter.valueFour) }
private fun openOne() = parameter.providerOne.openProvider()
private fun openTwo() = parameter.providerTwo.openProvider()
private fun openThree() = parameter.providerThree.openProvider()
private fun openFour() = parameter.providerFour.openProvider()
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)
private fun getValue(r: Resources) = parameter.getValue(r)
@Test
fun assertValueUniqueness() {
// Ensure the parameters are valid in case of coding errors
val original = getValue()
assertEquals(valueOriginal, original)
assertNotEquals(valueOne, original)
assertNotEquals(valueTwo, original)
assertNotEquals(valueThree, original)
@@ -193,7 +281,6 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() {
@Test
fun addProvidersRepeatedly() {
val originalValue = getValue()
val testOne = openOne()
val testTwo = openTwo()
val loader = ResourcesLoader()
@@ -209,12 +296,11 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() {
assertEquals(valueTwo, getValue())
loader.removeProvider(testTwo)
assertEquals(originalValue, getValue())
assertEquals(valueOriginal, getValue())
}
@Test
fun addLoadersRepeatedly() {
val originalValue = getValue()
val testOne = openOne()
val testTwo = openTwo()
val loader1 = ResourcesLoader()
@@ -232,12 +318,11 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() {
assertEquals(valueTwo, getValue())
resources.removeLoaders(loader2)
assertEquals(originalValue, getValue())
assertEquals(valueOriginal, getValue())
}
@Test
fun setMultipleProviders() {
val originalValue = getValue()
val testOne = openOne()
val testTwo = openTwo()
val loader = ResourcesLoader()
@@ -250,12 +335,11 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() {
assertEquals(valueOne, getValue())
loader.providers = Collections.emptyList()
assertEquals(originalValue, getValue())
assertEquals(valueOriginal, getValue())
}
@Test
fun addMultipleLoaders() {
val originalValue = getValue()
val loader1 = ResourcesLoader()
loader1.addProvider(openOne())
val loader2 = ResourcesLoader()
@@ -268,7 +352,28 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() {
assertEquals(valueOne, getValue())
resources.removeLoaders(loader1)
assertEquals(originalValue, getValue())
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)
@@ -385,7 +490,6 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() {
@Test
fun reorderProviders() {
val originalValue = getValue()
val testOne = openOne()
val testTwo = openTwo()
val loader = ResourcesLoader()
@@ -405,12 +509,11 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() {
assertEquals(valueOne, getValue())
loader.removeProvider(testOne)
assertEquals(originalValue, getValue())
assertEquals(valueOriginal, getValue())
}
@Test
fun reorderLoaders() {
val originalValue = getValue()
val testOne = openOne()
val testTwo = openTwo()
val loader1 = ResourcesLoader()
@@ -432,7 +535,7 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() {
assertEquals(valueOne, getValue())
resources.removeLoaders(loader1)
assertEquals(originalValue, getValue())
assertEquals(valueOriginal, getValue())
}
@Test
@@ -460,6 +563,9 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() {
loader1.removeProvider(testOne)
assertEquals(valueFour, getValue())
loader2.removeProvider(testFour)
assertEquals(valueThree, getValue())
}
private fun createContext(context: Context, id: Int): Context {
@@ -470,7 +576,6 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() {
@Test
fun copyContextLoaders() {
val originalValue = getValue()
val loader1 = ResourcesLoader()
loader1.addProvider(openOne())
val loader2 = ResourcesLoader()
@@ -490,13 +595,13 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() {
// Changing the loaders of the original context should not affect the child context.
resources.removeLoaders(loader1)
assertEquals(originalValue, getValue())
assertEquals(valueOriginal, getValue())
assertEquals(valueTwo, getValue(childContext))
// A new context created from the original after an update to the original's loaders should
// have the updated loaders.
val originalPrime = createContext(context, 2)
assertEquals(originalValue, getValue(originalPrime))
assertEquals(valueOriginal, getValue(originalPrime))
// A new context created from the child context after an update to the child's loaders
// should have the updated loaders.
@@ -506,7 +611,6 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() {
@Test
fun loaderUpdatesAffectContexts() {
val originalValue = getValue()
val testOne = openOne()
val testTwo = openTwo()
val loader = ResourcesLoader()
@@ -525,15 +629,15 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() {
// Changes to the loaders for a context do not affect providers.
resources.clearLoaders()
assertEquals(originalValue, getValue())
assertEquals(valueOriginal, getValue())
assertEquals(valueTwo, getValue(childContext))
val childContext2 = createContext(context, 1)
assertEquals(originalValue, getValue())
assertEquals(originalValue, getValue(childContext2))
assertEquals(valueOriginal, getValue())
assertEquals(valueOriginal, getValue(childContext2))
childContext2.resources.addLoaders(loader)
assertEquals(originalValue, getValue())
assertEquals(valueOriginal, getValue())
assertEquals(valueTwo, getValue(childContext))
assertEquals(valueTwo, getValue(childContext2))
}
@@ -629,22 +733,82 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() {
provider.close()
}
@Test
fun addLoadersRepeatedlyCustomResources() {
val res = Resources(AssetManager::class.java.newInstance(), resources.displayMetrics,
resources.configuration!!)
val originalValue = getValue(res)
val testOne = openOne()
val testTwo = openTwo()
val loader1 = ResourcesLoader()
val loader2 = ResourcesLoader()
res.addLoaders(loader1)
loader1.addProvider(testOne)
assertEquals(valueOne, getValue(res))
res.addLoaders(loader2)
loader2.addProvider(testTwo)
assertEquals(valueTwo, getValue(res))
res.removeLoaders(loader1)
res.addLoaders(loader1)
assertEquals(valueOne, getValue(res))
res.removeLoaders(loader1)
assertEquals(valueTwo, getValue(res))
res.removeLoaders(loader2)
assertEquals(originalValue, getValue(res))
}
@Test
fun setMultipleProvidersCustomResources() {
val res = Resources(AssetManager::class.java.newInstance(), resources.displayMetrics,
resources.configuration!!)
val originalValue = getValue(res)
val testOne = openOne()
val testTwo = openTwo()
val loader = ResourcesLoader()
res.addLoaders(loader)
loader.providers = listOf(testOne, testTwo)
assertEquals(valueTwo, getValue(res))
loader.removeProvider(testTwo)
assertEquals(valueOne, getValue(res))
loader.providers = Collections.emptyList()
assertEquals(originalValue, getValue(res))
}
data class Parameter(
val getValue: Resources.() -> Any,
val providerOne: String,
val valueOne: ResourceLoaderValuesTest.() -> Any,
val providerTwo: String,
val valueTwo: ResourceLoaderValuesTest.() -> Any,
val providerThree: String,
val valueThree: ResourceLoaderValuesTest.() -> Any,
val providerFour: String,
val valueFour: ResourceLoaderValuesTest.() -> Any,
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>
) {
override fun toString(): String {
val prefix = providerOne.commonPrefixWith(providerTwo)
return "$prefix${providerOne.removePrefix(prefix)}|${providerTwo.removePrefix(prefix)}"
}
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

@@ -44,25 +44,20 @@ fun Int.dpToPx(resources: Resources) = TypedValue.applyDimension(
fun AssetFileDescriptor.readText() = createInputStream().reader().readText()
fun rawFile(fileName: String) = R.raw::class.java.getDeclaredField(fileName).getInt(null)
fun XmlPullParser.advanceToRoot() = apply {
while (next() != XmlPullParser.START_TAG) {
// Empty
}
}
fun Context.copiedRawFile(fileName: String): ParcelFileDescriptor {
return resources.openRawResourceFd(rawFile(fileName)).use { asset ->
fun Context.copiedAssetFile(fileName: String): ParcelFileDescriptor {
return resources.assets.open(fileName).use { input ->
// AssetManager doesn't expose a direct file descriptor to the asset, so copy it to
// an individual file so one can be created manually.
val copiedFile = File(filesDir, fileName)
asset.createInputStream().use { input ->
copiedFile.outputStream().use { output ->
input.copyTo(output)
}
copiedFile.outputStream().use { output ->
input.copyTo(output)
}
ParcelFileDescriptor.open(copiedFile, ParcelFileDescriptor.MODE_READ_WRITE)
}
}

View File

@@ -21,6 +21,7 @@
#include "android-base/errors.h"
#include "android-base/file.h"
#include "android-base/logging.h"
#include "android-base/stringprintf.h"
#include "android-base/unique_fd.h"
#include "android-base/utf8.h"
#include "utils/Compat.h"
@@ -40,29 +41,342 @@ using base::unique_fd;
static const std::string kResourcesArsc("resources.arsc");
ApkAssets::ApkAssets(ZipArchiveHandle unmanaged_handle,
const std::string& path,
ApkAssets::ApkAssets(std::unique_ptr<const AssetsProvider> assets_provider,
std::string path,
time_t last_mod_time,
package_property_t property_flags)
: zip_handle_(unmanaged_handle, ::CloseArchive), path_(path), last_mod_time_(last_mod_time),
: assets_provider_(std::move(assets_provider)),
path_(std::move(path)),
last_mod_time_(last_mod_time),
property_flags_(property_flags) {
}
std::unique_ptr<const ApkAssets> ApkAssets::Load(const std::string& path, bool system,
bool for_loader) {
package_property_t flags = (system ? PROPERTY_SYSTEM : 0U) |
(for_loader ? PROPERTY_LOADER : 0U);
return LoadImpl({} /*fd*/, path, nullptr, nullptr, flags);
// Provides asset files from a zip file.
class ZipAssetsProvider : public AssetsProvider {
public:
~ZipAssetsProvider() override = default;
static std::unique_ptr<const AssetsProvider> Create(const std::string& path) {
::ZipArchiveHandle unmanaged_handle;
const int32_t result = ::OpenArchive(path.c_str(), &unmanaged_handle);
if (result != 0) {
LOG(ERROR) << "Failed to open APK '" << path << "' " << ::ErrorCodeString(result);
::CloseArchive(unmanaged_handle);
return {};
}
return std::unique_ptr<AssetsProvider>(new ZipAssetsProvider(path, path, unmanaged_handle));
}
static std::unique_ptr<const AssetsProvider> Create(
unique_fd fd, const std::string& friendly_name, const off64_t offset = 0,
const off64_t length = ApkAssets::kUnknownLength) {
::ZipArchiveHandle unmanaged_handle;
const int32_t result = (length == ApkAssets::kUnknownLength)
? ::OpenArchiveFd(fd.release(), friendly_name.c_str(), &unmanaged_handle)
: ::OpenArchiveFdRange(fd.release(), friendly_name.c_str(), &unmanaged_handle, length,
offset);
if (result != 0) {
LOG(ERROR) << "Failed to open APK '" << friendly_name << "' through FD with offset " << offset
<< " and length " << length << ": " << ::ErrorCodeString(result);
::CloseArchive(unmanaged_handle);
return {};
}
return std::unique_ptr<AssetsProvider>(new ZipAssetsProvider({}, friendly_name,
unmanaged_handle));
}
// Iterate over all files and directories within the zip. The order of iteration is not
// guaranteed to be the same as the order of elements in the central directory but is stable for a
// given zip file.
bool ForEachFile(const std::string& root_path,
const std::function<void(const StringPiece&, FileType)>& f) const override {
// If this is a resource loader from an .arsc, there will be no zip handle
if (zip_handle_ == nullptr) {
return false;
}
std::string root_path_full = root_path;
if (root_path_full.back() != '/') {
root_path_full += '/';
}
void* cookie;
if (::StartIteration(zip_handle_.get(), &cookie, root_path_full, "") != 0) {
return false;
}
std::string name;
::ZipEntry entry{};
// We need to hold back directories because many paths will contain them and we want to only
// surface one.
std::set<std::string> dirs{};
int32_t result;
while ((result = ::Next(cookie, &entry, &name)) == 0) {
StringPiece full_file_path(name);
StringPiece leaf_file_path = full_file_path.substr(root_path_full.size());
if (!leaf_file_path.empty()) {
auto iter = std::find(leaf_file_path.begin(), leaf_file_path.end(), '/');
if (iter != leaf_file_path.end()) {
std::string dir =
leaf_file_path.substr(0, std::distance(leaf_file_path.begin(), iter)).to_string();
dirs.insert(std::move(dir));
} else {
f(leaf_file_path, kFileTypeRegular);
}
}
}
::EndIteration(cookie);
// Now present the unique directories.
for (const std::string& dir : dirs) {
f(dir, kFileTypeDirectory);
}
// -1 is end of iteration, anything else is an error.
return result == -1;
}
protected:
std::unique_ptr<Asset> OpenInternal(
const std::string& path, Asset::AccessMode mode, bool* file_exists) const override {
if (file_exists) {
*file_exists = false;
}
::ZipEntry entry;
int32_t result = ::FindEntry(zip_handle_.get(), path, &entry);
if (result != 0) {
return {};
}
if (file_exists) {
*file_exists = true;
}
const int fd = ::GetFileDescriptor(zip_handle_.get());
const off64_t fd_offset = ::GetFileDescriptorOffset(zip_handle_.get());
if (entry.method == kCompressDeflated) {
std::unique_ptr<FileMap> map = util::make_unique<FileMap>();
if (!map->create(GetPath(), fd, entry.offset + fd_offset, entry.compressed_length,
true /*readOnly*/)) {
LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << friendly_name_ << "'";
return {};
}
std::unique_ptr<Asset> asset =
Asset::createFromCompressedMap(std::move(map), entry.uncompressed_length, mode);
if (asset == nullptr) {
LOG(ERROR) << "Failed to decompress '" << path << "' in APK '" << friendly_name_ << "'";
return {};
}
return asset;
} else {
std::unique_ptr<FileMap> map = util::make_unique<FileMap>();
if (!map->create(GetPath(), fd, entry.offset + fd_offset, entry.uncompressed_length,
true /*readOnly*/)) {
LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << friendly_name_ << "'";
return {};
}
unique_fd ufd;
if (!GetPath()) {
// If the `path` is not set, create a new `fd` for the new Asset to own in order to create
// new file descriptors using Asset::openFileDescriptor. If the path is set, it will be used
// to create new file descriptors.
ufd = unique_fd(dup(fd));
if (!ufd.ok()) {
LOG(ERROR) << "Unable to dup fd '" << path << "' in APK '" << friendly_name_ << "'";
return {};
}
}
std::unique_ptr<Asset> asset = Asset::createFromUncompressedMap(std::move(map),
std::move(ufd), mode);
if (asset == nullptr) {
LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << friendly_name_ << "'";
return {};
}
return asset;
}
}
private:
DISALLOW_COPY_AND_ASSIGN(ZipAssetsProvider);
explicit ZipAssetsProvider(std::string path,
std::string friendly_name,
ZipArchiveHandle unmanaged_handle)
: zip_handle_(unmanaged_handle, ::CloseArchive),
path_(std::move(path)),
friendly_name_(std::move(friendly_name)) { }
const char* GetPath() const {
return path_.empty() ? nullptr : path_.c_str();
}
using ZipArchivePtr = std::unique_ptr<ZipArchive, void (*)(ZipArchiveHandle)>;
ZipArchivePtr zip_handle_;
std::string path_;
std::string friendly_name_;
};
class DirectoryAssetsProvider : AssetsProvider {
public:
~DirectoryAssetsProvider() override = default;
static std::unique_ptr<const AssetsProvider> Create(const std::string& path) {
struct stat sb{};
const int result = stat(path.c_str(), &sb);
if (result == -1) {
LOG(ERROR) << "Failed to find directory '" << path << "'.";
return nullptr;
}
if (!S_ISDIR(sb.st_mode)) {
LOG(ERROR) << "Path '" << path << "' is not a directory.";
return nullptr;
}
return std::unique_ptr<AssetsProvider>(new DirectoryAssetsProvider(path));
}
protected:
std::unique_ptr<Asset> OpenInternal(
const std::string& path, Asset::AccessMode /* mode */, bool* file_exists) const override {
const std::string resolved_path = ResolvePath(path);
if (file_exists) {
struct stat sb{};
const int result = stat(resolved_path.c_str(), &sb);
*file_exists = result != -1 && S_ISREG(sb.st_mode);
}
return ApkAssets::CreateAssetFromFile(resolved_path);
}
private:
DISALLOW_COPY_AND_ASSIGN(DirectoryAssetsProvider);
explicit DirectoryAssetsProvider(std::string path) : path_(std::move(path)) { }
inline std::string ResolvePath(const std::string& path) const {
return base::StringPrintf("%s%c%s", path_.c_str(), OS_PATH_SEPARATOR, path.c_str());
}
const std::string path_;
};
// AssetProvider implementation that does not provide any assets. Used for ApkAssets::LoadEmpty.
class EmptyAssetsProvider : public AssetsProvider {
public:
EmptyAssetsProvider() = default;
~EmptyAssetsProvider() override = default;
protected:
std::unique_ptr<Asset> OpenInternal(const std::string& /*path */,
Asset::AccessMode /* mode */,
bool* file_exists) const override {
if (file_exists) {
*file_exists = false;
}
return nullptr;
}
private:
DISALLOW_COPY_AND_ASSIGN(EmptyAssetsProvider);
};
// 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, flags, std::move(override_asset))
: nullptr;
}
std::unique_ptr<const ApkAssets> ApkAssets::LoadAsSharedLibrary(const std::string& path,
bool system) {
package_property_t flags = PROPERTY_DYNAMIC | (system ? PROPERTY_SYSTEM : 0U);
return LoadImpl({} /*fd*/, path, nullptr, nullptr, flags);
// 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, flags, std::move(override_asset))
: 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,
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,
bool system) {
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 {};
@@ -76,111 +390,115 @@ std::unique_ptr<const ApkAssets> ApkAssets::LoadOverlay(const std::string& idmap
LOG(ERROR) << "failed to load IDMAP " << idmap_path;
return {};
}
auto apkPath = loaded_idmap->OverlayApkPath();
return LoadImpl({} /*fd*/, apkPath,
std::move(idmap_asset),
std::move(loaded_idmap),
PROPERTY_OVERLAY | (system ? PROPERTY_SYSTEM : 0U));
auto overlay_path = loaded_idmap->OverlayApkPath();
auto assets = ZipAssetsProvider::Create(overlay_path);
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::LoadFromFd(unique_fd fd,
const std::string& friendly_name,
bool system, bool force_shared_lib,
bool for_loader) {
package_property_t flags = (system ? PROPERTY_SYSTEM : 0U) |
(force_shared_lib ? PROPERTY_DYNAMIC : 0U) |
(for_loader ? PROPERTY_LOADER : 0U);
return LoadImpl(std::move(fd), friendly_name, nullptr /*idmap_asset*/, nullptr /*loaded_idmap*/,
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, flags, std::move(override_asset))
: nullptr;
}
std::unique_ptr<const ApkAssets> ApkAssets::LoadArsc(const std::string& path,
bool for_loader) {
return LoadArscImpl({} /*fd*/, path, for_loader ? PROPERTY_LOADER : 0U);
}
std::unique_ptr<const ApkAssets> ApkAssets::LoadEmpty(
const package_property_t flags, std::unique_ptr<const AssetsProvider> override_asset) {
std::unique_ptr<const ApkAssets> ApkAssets::LoadArsc(unique_fd fd,
const std::string& friendly_name,
bool for_loader) {
return LoadArscImpl(std::move(fd), friendly_name, for_loader ? PROPERTY_LOADER : 0U);
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);
}
std::unique_ptr<Asset> ApkAssets::CreateAssetFromFile(const std::string& path) {
unique_fd fd(base::utf8::open(path.c_str(), O_RDONLY | O_BINARY | O_CLOEXEC));
if (fd == -1) {
if (!fd.ok()) {
LOG(ERROR) << "Failed to open file '" << path << "': " << SystemErrorCodeToString(errno);
return {};
}
const off64_t file_len = lseek64(fd, 0, SEEK_END);
if (file_len < 0) {
LOG(ERROR) << "Failed to get size of file '" << path << "': " << SystemErrorCodeToString(errno);
return {};
return CreateAssetFromFd(std::move(fd), path.c_str());
}
std::unique_ptr<Asset> ApkAssets::CreateAssetFromFd(base::unique_fd fd,
const char* path,
off64_t offset,
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;
if (length == kUnknownLength) {
length = lseek64(fd, 0, SEEK_END);
if (length < 0) {
LOG(ERROR) << "Failed to get size of file '" << ((path) ? path : "anon") << "': "
<< SystemErrorCodeToString(errno);
return {};
}
}
std::unique_ptr<FileMap> file_map = util::make_unique<FileMap>();
if (!file_map->create(path.c_str(), fd, 0, static_cast<size_t>(file_len), true /*readOnly*/)) {
LOG(ERROR) << "Failed to mmap file '" << path << "': " << SystemErrorCodeToString(errno);
if (!file_map->create(path, fd, offset, static_cast<size_t>(length), true /*readOnly*/)) {
LOG(ERROR) << "Failed to mmap file '" << ((path) ? path : "anon") << "': "
<< SystemErrorCodeToString(errno);
return {};
}
return Asset::createFromUncompressedMap(std::move(file_map), Asset::AccessMode::ACCESS_RANDOM);
// If `path` is set, do not pass ownership of the `fd` to the new Asset since
// Asset::openFileDescriptor can use `path` to create new file descriptors.
return Asset::createFromUncompressedMap(std::move(file_map),
(path) ? base::unique_fd(-1) : std::move(fd),
Asset::AccessMode::ACCESS_RANDOM);
}
std::unique_ptr<const ApkAssets> ApkAssets::LoadImpl(
unique_fd fd, const std::string& path, std::unique_ptr<Asset> idmap_asset,
std::unique_ptr<const LoadedIdmap> loaded_idmap, package_property_t property_flags) {
::ZipArchiveHandle unmanaged_handle;
int32_t result;
if (fd >= 0) {
result =
::OpenArchiveFd(fd.release(), path.c_str(), &unmanaged_handle, true /*assume_ownership*/);
} else {
result = ::OpenArchive(path.c_str(), &unmanaged_handle);
}
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) {
if (result != 0) {
LOG(ERROR) << "Failed to open APK '" << path << "' " << ::ErrorCodeString(result);
::CloseArchive(unmanaged_handle);
return {};
}
const time_t last_mod_time = getFileModDate(path.c_str());
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(unmanaged_handle, path, last_mod_time, property_flags));
loaded_apk(new ApkAssets(std::move(assets), path, last_mod_time, property_flags));
// Find the resource table.
::ZipEntry entry;
result = ::FindEntry(loaded_apk->zip_handle_.get(), kResourcesArsc, &entry);
if (result != 0) {
// There is no resources.arsc, so create an empty LoadedArsc and return.
if (!resources_asset_exists) {
loaded_apk->loaded_arsc_ = LoadedArsc::CreateEmpty();
return std::move(loaded_apk);
}
if (entry.method == kCompressDeflated) {
ANDROID_LOG(WARNING) << kResourcesArsc << " in APK '" << path << "' is compressed.";
}
// Open the resource table via mmap unless it is compressed. This logic is taken care of by Open.
loaded_apk->resources_asset_ = loaded_apk->Open(kResourcesArsc, Asset::AccessMode::ACCESS_BUFFER);
if (loaded_apk->resources_asset_ == nullptr) {
loaded_apk->resources_asset_ = std::move(resources_asset_);
if (!loaded_apk->resources_asset_) {
LOG(ERROR) << "Failed to open '" << kResourcesArsc << "' in APK '" << path << "'.";
return {};
}
// Must retain ownership of the IDMAP Asset so that all pointers to its mmapped data remain valid.
loaded_apk->idmap_asset_ = std::move(idmap_asset);
loaded_apk->loaded_idmap_ = std::move(loaded_idmap);
loaded_apk->loaded_idmap_ = std::move(idmap);
const StringPiece data(
reinterpret_cast<const char*>(loaded_apk->resources_asset_->getBuffer(true /*wordAligned*/)),
loaded_apk->resources_asset_->getLength());
loaded_apk->loaded_arsc_ = LoadedArsc::Load(data, loaded_apk->loaded_idmap_.get(),
property_flags);
if (loaded_apk->loaded_arsc_ == nullptr) {
if (!loaded_apk->loaded_arsc_) {
LOG(ERROR) << "Failed to load '" << kResourcesArsc << "' in APK '" << path << "'.";
return {};
}
@@ -189,27 +507,17 @@ std::unique_ptr<const ApkAssets> ApkAssets::LoadImpl(
return std::move(loaded_apk);
}
std::unique_ptr<const ApkAssets> ApkAssets::LoadArscImpl(unique_fd fd,
const std::string& path,
package_property_t property_flags) {
std::unique_ptr<Asset> resources_asset;
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) {
if (fd >= 0) {
resources_asset = std::unique_ptr<Asset>(Asset::createFromFd(fd.release(), nullptr,
Asset::AccessMode::ACCESS_BUFFER));
} else {
resources_asset = CreateAssetFromFile(path);
}
const time_t last_mod_time = getFileModDate(path.c_str());
if (resources_asset == nullptr) {
LOG(ERROR) << "Failed to open ARSC '" << path;
return {};
}
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(nullptr, 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(
@@ -225,111 +533,9 @@ std::unique_ptr<const ApkAssets> ApkAssets::LoadArscImpl(unique_fd fd,
return std::move(loaded_apk);
}
std::unique_ptr<const ApkAssets> ApkAssets::LoadEmpty(bool for_loader) {
std::unique_ptr<ApkAssets> loaded_apk(new ApkAssets(nullptr, "", -1, for_loader));
loaded_apk->loaded_arsc_ = LoadedArsc::CreateEmpty();
// Need to force a move for mingw32.
return std::move(loaded_apk);
}
std::unique_ptr<Asset> ApkAssets::Open(const std::string& path, Asset::AccessMode mode) const {
// If this is a resource loader from an .arsc, there will be no zip handle
if (zip_handle_ == nullptr) {
return {};
}
::ZipEntry entry;
int32_t result = ::FindEntry(zip_handle_.get(), path, &entry);
if (result != 0) {
return {};
}
if (entry.method == kCompressDeflated) {
std::unique_ptr<FileMap> map = util::make_unique<FileMap>();
if (!map->create(path_.c_str(), ::GetFileDescriptor(zip_handle_.get()), entry.offset,
entry.compressed_length, true /*readOnly*/)) {
LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << path_ << "'";
return {};
}
std::unique_ptr<Asset> asset =
Asset::createFromCompressedMap(std::move(map), entry.uncompressed_length, mode);
if (asset == nullptr) {
LOG(ERROR) << "Failed to decompress '" << path << "'.";
return {};
}
return asset;
} else {
std::unique_ptr<FileMap> map = util::make_unique<FileMap>();
if (!map->create(path_.c_str(), ::GetFileDescriptor(zip_handle_.get()), entry.offset,
entry.uncompressed_length, true /*readOnly*/)) {
LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << path_ << "'";
return {};
}
std::unique_ptr<Asset> asset = Asset::createFromUncompressedMap(std::move(map), mode);
if (asset == nullptr) {
LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << path_ << "'";
return {};
}
return asset;
}
}
bool ApkAssets::ForEachFile(const std::string& root_path,
const std::function<void(const StringPiece&, FileType)>& f) const {
// If this is a resource loader from an .arsc, there will be no zip handle
if (zip_handle_ == nullptr) {
return false;
}
std::string root_path_full = root_path;
if (root_path_full.back() != '/') {
root_path_full += '/';
}
void* cookie;
if (::StartIteration(zip_handle_.get(), &cookie, root_path_full, "") != 0) {
return false;
}
std::string name;
::ZipEntry entry;
// We need to hold back directories because many paths will contain them and we want to only
// surface one.
std::set<std::string> dirs;
int32_t result;
while ((result = ::Next(cookie, &entry, &name)) == 0) {
StringPiece full_file_path(name);
StringPiece leaf_file_path = full_file_path.substr(root_path_full.size());
if (!leaf_file_path.empty()) {
auto iter = std::find(leaf_file_path.begin(), leaf_file_path.end(), '/');
if (iter != leaf_file_path.end()) {
std::string dir =
leaf_file_path.substr(0, std::distance(leaf_file_path.begin(), iter)).to_string();
dirs.insert(std::move(dir));
} else {
f(leaf_file_path, kFileTypeRegular);
}
}
}
::EndIteration(cookie);
// Now present the unique directories.
for (const std::string& dir : dirs) {
f(dir, kFileTypeDirectory);
}
// -1 is end of iteration, anything else is an error.
return result == -1;
}
bool ApkAssets::IsUpToDate() const {
if (IsLoader()) {
// Loaders are invalidated by the app, not the system, so assume up to date.
// Loaders are invalidated by the app, not the system, so assume they are up to date.
return true;
}

View File

@@ -298,14 +298,13 @@ Asset::Asset(void)
/*
* Create a new Asset from a memory mapping.
*/
/*static*/ Asset* Asset::createFromUncompressedMap(FileMap* dataMap,
AccessMode mode)
/*static*/ Asset* Asset::createFromUncompressedMap(FileMap* dataMap, AccessMode mode)
{
_FileAsset* pAsset;
status_t result;
pAsset = new _FileAsset;
result = pAsset->openChunk(dataMap);
result = pAsset->openChunk(dataMap, base::unique_fd(-1));
if (result != NO_ERROR) {
delete pAsset;
return NULL;
@@ -316,11 +315,11 @@ Asset::Asset(void)
}
/*static*/ std::unique_ptr<Asset> Asset::createFromUncompressedMap(std::unique_ptr<FileMap> dataMap,
AccessMode mode)
base::unique_fd fd, AccessMode mode)
{
std::unique_ptr<_FileAsset> pAsset = util::make_unique<_FileAsset>();
status_t result = pAsset->openChunk(dataMap.get());
status_t result = pAsset->openChunk(dataMap.get(), std::move(fd));
if (result != NO_ERROR) {
return NULL;
}
@@ -415,7 +414,7 @@ off64_t Asset::handleSeek(off64_t offset, int whence, off64_t curPosn, off64_t m
* Constructor.
*/
_FileAsset::_FileAsset(void)
: mStart(0), mLength(0), mOffset(0), mFp(NULL), mFileName(NULL), mMap(NULL), mBuf(NULL)
: mStart(0), mLength(0), mOffset(0), mFp(NULL), mFileName(NULL), mFd(-1), mMap(NULL), mBuf(NULL)
{
// Register the Asset with the global list here after it is fully constructed and its
// vtable pointer points to this concrete type. b/31113965
@@ -485,7 +484,7 @@ status_t _FileAsset::openChunk(const char* fileName, int fd, off64_t offset, siz
/*
* Create the chunk from the map.
*/
status_t _FileAsset::openChunk(FileMap* dataMap)
status_t _FileAsset::openChunk(FileMap* dataMap, base::unique_fd fd)
{
assert(mFp == NULL); // no reopen
assert(mMap == NULL);
@@ -494,6 +493,7 @@ status_t _FileAsset::openChunk(FileMap* dataMap)
mMap = dataMap;
mStart = -1; // not used
mLength = dataMap->getDataLength();
mFd = std::move(fd);
assert(mOffset == 0);
return NO_ERROR;
@@ -692,6 +692,17 @@ const void* _FileAsset::getBuffer(bool wordAligned)
int _FileAsset::openFileDescriptor(off64_t* outStart, off64_t* outLength) const
{
if (mMap != NULL) {
if (mFd.ok()) {
*outStart = mMap->getDataOffset();
*outLength = mMap->getDataLength();
const int fd = dup(mFd);
if (fd < 0) {
ALOGE("Unable to dup fd (%d).", mFd.get());
return -1;
}
lseek64(fd, 0, SEEK_SET);
return fd;
}
const char* fname = mMap->getFileName();
if (fname == NULL) {
fname = mFileName;

View File

@@ -463,7 +463,7 @@ std::unique_ptr<AssetDir> AssetManager2::OpenDir(const std::string& dirname) con
files->add(info);
};
if (!apk_assets->ForEachFile(full_path, func)) {
if (!apk_assets->GetAssetsProvider()->ForEachFile(full_path, func)) {
return {};
}
}
@@ -487,7 +487,7 @@ std::unique_ptr<Asset> AssetManager2::OpenNonAsset(const std::string& filename,
continue;
}
std::unique_ptr<Asset> asset = apk_assets_[i]->Open(filename, mode);
std::unique_ptr<Asset> asset = apk_assets_[i]->GetAssetsProvider()->Open(filename, mode);
if (asset) {
if (out_cookie != nullptr) {
*out_cookie = i;
@@ -508,7 +508,7 @@ std::unique_ptr<Asset> AssetManager2::OpenNonAsset(const std::string& filename,
if (cookie < 0 || static_cast<size_t>(cookie) >= apk_assets_.size()) {
return {};
}
return apk_assets_[cookie]->Open(filename, mode);
return apk_assets_[cookie]->GetAssetsProvider()->Open(filename, mode);
}
ApkAssetsCookie AssetManager2::FindEntry(uint32_t resid, uint16_t density_override,

View File

@@ -749,7 +749,7 @@ bool LoadedArsc::LoadTable(const Chunk& chunk, const LoadedIdmap* loaded_idmap,
std::unique_ptr<const LoadedArsc> LoadedArsc::Load(const StringPiece& data,
const LoadedIdmap* loaded_idmap,
package_property_t property_flags) {
const package_property_t property_flags) {
ATRACE_NAME("LoadedArsc::Load");
// Not using make_unique because the constructor is private.

View File

@@ -35,62 +35,98 @@ namespace android {
class LoadedIdmap;
// Interface for retrieving assets provided by an ApkAssets.
class AssetsProvider {
public:
virtual ~AssetsProvider() = default;
// Opens a file for reading.
std::unique_ptr<Asset> Open(const std::string& path,
Asset::AccessMode mode = Asset::AccessMode::ACCESS_RANDOM,
bool* file_exists = nullptr) const {
return OpenInternal(path, mode, file_exists);
}
// Iterate over all files and directories provided by the zip. The order of iteration is stable.
virtual bool ForEachFile(const std::string& /* path */,
const std::function<void(const StringPiece&, FileType)>& /* f */) const {
return true;
}
protected:
AssetsProvider() = default;
virtual std::unique_ptr<Asset> OpenInternal(const std::string& path,
Asset::AccessMode mode,
bool* file_exists) const = 0;
private:
DISALLOW_COPY_AND_ASSIGN(AssetsProvider);
};
class ZipAssetsProvider;
// Holds an APK.
class ApkAssets {
public:
// This means the data extends to the end of the file.
static constexpr off64_t kUnknownLength = -1;
// 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, bool system = false,
bool for_loader = false);
// Creates an ApkAssets, but forces any package with ID 0x7f to be loaded as a shared library.
// 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> LoadAsSharedLibrary(const std::string& path,
bool system = false);
// Creates an ApkAssets from an IDMAP, which contains the original APK path, and the overlay
// data.
// 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> LoadOverlay(const std::string& idmap_path,
bool system = false);
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 `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.
// If `force_shared_lib` is true, any package with ID 0x7f is loaded as a shared library.
static std::unique_ptr<const ApkAssets> LoadFromFd(base::unique_fd fd,
const std::string& friendly_name, bool system,
bool force_shared_lib,
bool for_loader = false);
// 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,
std::unique_ptr<const AssetsProvider> override_asset = nullptr, off64_t offset = 0,
off64_t length = kUnknownLength);
// Creates an empty wrapper ApkAssets from the given path which points to an .arsc.
static std::unique_ptr<const ApkAssets> LoadArsc(const std::string& path,
bool for_loader = false);
// 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,
std::unique_ptr<const AssetsProvider> override_asset = nullptr);
// Creates an empty wrapper ApkAssets from the given file descriptor which points to an .arsc,
// Takes ownership of the file descriptor.
static std::unique_ptr<const ApkAssets> LoadArsc(base::unique_fd fd,
const std::string& friendly_name,
bool for_loader = false);
// 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,
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.
static std::unique_ptr<const ApkAssets> LoadOverlay(const std::string& idmap_path,
package_property_t flags = 0U);
// Creates an ApkAssets from the directory path. File-based resources are read within the
// directory as if the directory is an APK.
static std::unique_ptr<const ApkAssets> LoadFromDir(
const std::string& path, package_property_t flags = 0U,
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(bool for_loader = false);
std::unique_ptr<Asset> Open(const std::string& path,
Asset::AccessMode mode = Asset::AccessMode::ACCESS_RANDOM) const;
bool ForEachFile(const std::string& path,
const std::function<void(const StringPiece&, FileType)>& f) const;
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_;
}
inline const AssetsProvider* GetAssetsProvider() const {
return assets_provider_.get();
}
// This is never nullptr.
inline const LoadedArsc* GetLoadedArsc() const {
return loaded_arsc_.get();
@@ -105,34 +141,44 @@ class ApkAssets {
}
inline bool IsOverlay() const {
return (property_flags_ & PROPERTY_OVERLAY) != 0;
return loaded_idmap_ != nullptr;
}
bool IsUpToDate() const;
// Creates an Asset from any file on the file system.
// Creates an Asset from a file on disk.
static std::unique_ptr<Asset> CreateAssetFromFile(const std::string& path);
// Creates an Asset from a file descriptor.
//
// The asset takes ownership of the file descriptor. If `length` equals kUnknownLength, offset
// must equal 0; otherwise, the asset data will be read using the `offset` into the file
// descriptor and will be `length` bytes long.
static std::unique_ptr<Asset> CreateAssetFromFd(base::unique_fd fd,
const char* path,
off64_t offset = 0,
off64_t length = kUnknownLength);
private:
DISALLOW_COPY_AND_ASSIGN(ApkAssets);
static std::unique_ptr<const ApkAssets> LoadImpl(base::unique_fd fd, const std::string& path,
std::unique_ptr<Asset> idmap_asset,
std::unique_ptr<const LoadedIdmap> loaded_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> LoadArscImpl(base::unique_fd fd,
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(ZipArchiveHandle unmanaged_handle,
const std::string& path,
ApkAssets(std::unique_ptr<const AssetsProvider> assets_provider,
std::string path,
time_t last_mod_time,
package_property_t property_flags);
using ZipArchivePtr = std::unique_ptr<ZipArchive, void (*)(ZipArchiveHandle)>;
ZipArchivePtr zip_handle_;
std::unique_ptr<const AssetsProvider> assets_provider_;
const std::string path_;
time_t last_mod_time_;
package_property_t property_flags_ = 0U;

View File

@@ -26,6 +26,7 @@
#include <memory>
#include <android-base/unique_fd.h>
#include <utils/Compat.h>
#include <utils/Errors.h>
#include <utils/String8.h>
@@ -158,6 +159,7 @@ private:
/* AssetManager needs access to our "create" functions */
friend class AssetManager;
friend class ApkAssets;
friend class ZipAssetsProvider;
/*
* Create the asset from a named file on disk.
@@ -202,8 +204,14 @@ private:
*/
static Asset* createFromUncompressedMap(FileMap* dataMap, AccessMode mode);
/*
* Create the asset from a memory-mapped file segment.
*
* The asset takes ownership of the FileMap and the file descriptor "fd". The file descriptor is
* used to request new file descriptors using "openFileDescriptor".
*/
static std::unique_ptr<Asset> createFromUncompressedMap(std::unique_ptr<FileMap> dataMap,
AccessMode mode);
base::unique_fd fd, AccessMode mode);
/*
* Create the asset from a memory-mapped file segment with compressed
@@ -256,9 +264,9 @@ public:
/*
* Use a memory-mapped region.
*
* On success, the object takes ownership of "dataMap".
* On success, the object takes ownership of "dataMap" and "fd".
*/
status_t openChunk(FileMap* dataMap);
status_t openChunk(FileMap* dataMap, base::unique_fd fd);
/*
* Standard Asset interfaces.
@@ -273,11 +281,12 @@ public:
virtual bool isAllocated(void) const { return mBuf != NULL; }
private:
off64_t mStart; // absolute file offset of start of chunk
off64_t mLength; // length of the chunk
off64_t mOffset; // current local offset, 0 == mStart
FILE* mFp; // for read/seek
char* mFileName; // for opening
off64_t mStart; // absolute file offset of start of chunk
off64_t mLength; // length of the chunk
off64_t mOffset; // current local offset, 0 == mStart
FILE* mFp; // for read/seek
char* mFileName; // for opening
base::unique_fd mFd; // for opening file descriptors
/*
* To support getBuffer() we either need to read the entire thing into

View File

@@ -69,12 +69,24 @@ struct TypeSpec {
}
};
// Flags that change the behavior of loaded packages.
// Keep in sync with f/b/android/content/res/ApkAssets.java
using package_property_t = uint32_t;
enum : package_property_t {
PROPERTY_DYNAMIC = 1,
PROPERTY_LOADER = 2,
PROPERTY_OVERLAY = 4,
PROPERTY_SYSTEM = 8,
// The package contains framework resource values specified by the system.
// This allows some functions to filter out this package when computing
// what configurations/resources are available.
PROPERTY_SYSTEM = 1U << 0U,
// The package is a shared library or has a package id of 7f and is loaded as a shared library by
// force.
PROPERTY_DYNAMIC = 1U << 1U,
// The package has been loaded dynamically using a ResourcesProvider.
PROPERTY_LOADER = 1U << 2U,
// The package is a RRO.
PROPERTY_OVERLAY = 1U << 3U,
};
// TypeSpecPtr points to a block of memory that holds a TypeSpec struct, followed by an array of

View File

@@ -42,7 +42,7 @@ TEST(ApkAssetsTest, LoadApk) {
const LoadedArsc* loaded_arsc = loaded_apk->GetLoadedArsc();
ASSERT_THAT(loaded_arsc, NotNull());
ASSERT_THAT(loaded_arsc->GetPackageById(0x7fu), NotNull());
ASSERT_THAT(loaded_apk->Open("res/layout/main.xml"), NotNull());
ASSERT_THAT(loaded_apk->GetAssetsProvider()->Open("res/layout/main.xml"), NotNull());
}
TEST(ApkAssetsTest, LoadApkFromFd) {
@@ -50,14 +50,13 @@ TEST(ApkAssetsTest, LoadApkFromFd) {
unique_fd fd(::open(path.c_str(), O_RDONLY | O_BINARY));
ASSERT_THAT(fd.get(), Ge(0));
std::unique_ptr<const ApkAssets> loaded_apk =
ApkAssets::LoadFromFd(std::move(fd), path, false /*system*/, false /*force_shared_lib*/);
std::unique_ptr<const ApkAssets> loaded_apk = ApkAssets::LoadFromFd(std::move(fd), path);
ASSERT_THAT(loaded_apk, NotNull());
const LoadedArsc* loaded_arsc = loaded_apk->GetLoadedArsc();
ASSERT_THAT(loaded_arsc, NotNull());
ASSERT_THAT(loaded_arsc->GetPackageById(0x7fu), NotNull());
ASSERT_THAT(loaded_apk->Open("res/layout/main.xml"), NotNull());
ASSERT_THAT(loaded_apk->GetAssetsProvider()->Open("res/layout/main.xml"), NotNull());
}
TEST(ApkAssetsTest, LoadApkAsSharedLibrary) {
@@ -70,7 +69,7 @@ TEST(ApkAssetsTest, LoadApkAsSharedLibrary) {
ASSERT_THAT(loaded_arsc->GetPackages(), SizeIs(1u));
EXPECT_FALSE(loaded_arsc->GetPackages()[0]->IsDynamic());
loaded_apk = ApkAssets::LoadAsSharedLibrary(GetTestDataPath() + "/appaslib/appaslib.apk");
loaded_apk = ApkAssets::Load(GetTestDataPath() + "/appaslib/appaslib.apk", PROPERTY_DYNAMIC);
ASSERT_THAT(loaded_apk, NotNull());
loaded_arsc = loaded_apk->GetLoadedArsc();
@@ -84,9 +83,11 @@ TEST(ApkAssetsTest, CreateAndDestroyAssetKeepsApkAssetsOpen) {
ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk");
ASSERT_THAT(loaded_apk, NotNull());
{ ASSERT_THAT(loaded_apk->Open("res/layout/main.xml", Asset::ACCESS_BUFFER), NotNull()); }
{ ASSERT_THAT(loaded_apk->GetAssetsProvider()->Open("res/layout/main.xml",
Asset::ACCESS_BUFFER), NotNull()); }
{ ASSERT_THAT(loaded_apk->Open("res/layout/main.xml", Asset::ACCESS_BUFFER), NotNull()); }
{ ASSERT_THAT(loaded_apk->GetAssetsProvider()->Open("res/layout/main.xml",
Asset::ACCESS_BUFFER), NotNull()); }
}
TEST(ApkAssetsTest, OpenUncompressedAssetFd) {
@@ -94,7 +95,8 @@ TEST(ApkAssetsTest, OpenUncompressedAssetFd) {
ApkAssets::Load(GetTestDataPath() + "/basic/basic.apk");
ASSERT_THAT(loaded_apk, NotNull());
auto asset = loaded_apk->Open("assets/uncompressed.txt", Asset::ACCESS_UNKNOWN);
auto asset = loaded_apk->GetAssetsProvider()->Open("assets/uncompressed.txt",
Asset::ACCESS_UNKNOWN);
ASSERT_THAT(asset, NotNull());
off64_t start, length;

View File

@@ -63,10 +63,12 @@ class AssetManager2Test : public ::testing::Test {
libclient_assets_ = ApkAssets::Load(GetTestDataPath() + "/libclient/libclient.apk");
ASSERT_NE(nullptr, libclient_assets_);
appaslib_assets_ = ApkAssets::LoadAsSharedLibrary(GetTestDataPath() + "/appaslib/appaslib.apk");
appaslib_assets_ = ApkAssets::Load(GetTestDataPath() + "/appaslib/appaslib.apk",
PROPERTY_DYNAMIC);
ASSERT_NE(nullptr, appaslib_assets_);
system_assets_ = ApkAssets::Load(GetTestDataPath() + "/system/system.apk", true /*system*/);
system_assets_ = ApkAssets::Load(GetTestDataPath() + "/system/system.apk",
PROPERTY_SYSTEM);
ASSERT_NE(nullptr, system_assets_);
app_assets_ = ApkAssets::Load(GetTestDataPath() + "/app/app.apk");

View File

@@ -67,7 +67,7 @@ class AttributeResolutionXmlTest : public AttributeResolutionTest {
TEST(AttributeResolutionLibraryTest, ApplyStyleWithDefaultStyleResId) {
AssetManager2 assetmanager;
auto apk_assets = ApkAssets::LoadAsSharedLibrary(GetTestDataPath() + "/styles/styles.apk");
auto apk_assets = ApkAssets::Load(GetTestDataPath() + "/styles/styles.apk", PROPERTY_DYNAMIC);
ASSERT_NE(nullptr, apk_assets);
assetmanager.SetApkAssets({apk_assets.get()});

View File

@@ -221,8 +221,8 @@ TEST_F(IdmapTest, OverlaidResourceHasSameName) {
TEST_F(IdmapTest, OverlayLoaderInterop) {
std::string contents;
auto loader_assets = ApkAssets::LoadArsc(GetTestDataPath() + "/loader/resources.arsc",
/* for_loader */ true);
auto loader_assets = ApkAssets::LoadTable(GetTestDataPath() + "/loader/resources.arsc",
PROPERTY_LOADER);
AssetManager2 asset_manager;
asset_manager.SetApkAssets({overlayable_assets_.get(), loader_assets.get(),

View File

@@ -36,7 +36,7 @@ namespace android {
class ThemeTest : public ::testing::Test {
public:
void SetUp() override {
system_assets_ = ApkAssets::Load(GetTestDataPath() + "/system/system.apk", true /*system*/);
system_assets_ = ApkAssets::Load(GetTestDataPath() + "/system/system.apk", PROPERTY_SYSTEM);
ASSERT_NE(nullptr, system_assets_);
style_assets_ = ApkAssets::Load(GetTestDataPath() + "/styles/styles.apk");

View File

@@ -100,10 +100,12 @@ void CompileApkAssetsLayouts(const std::unique_ptr<const android::ApkAssets>& as
dex_file.MakeClass(StringPrintf("%s.CompiledView", package_name.c_str()))};
std::vector<dex::MethodBuilder> methods;
assets->ForEachFile("res/", [&](const android::StringPiece& s, android::FileType) {
assets->GetAssetsProvider()->ForEachFile("res/", [&](const android::StringPiece& s,
android::FileType) {
if (s == "layout") {
auto path = StringPrintf("res/%s/", s.to_string().c_str());
assets->ForEachFile(path, [&](const android::StringPiece& layout_file, android::FileType) {
assets->GetAssetsProvider()->ForEachFile(path, [&](const android::StringPiece& layout_file,
android::FileType) {
auto layout_path = StringPrintf("%s%s", path.c_str(), layout_file.to_string().c_str());
android::ApkAssetsCookie cookie = android::kInvalidCookie;
auto asset = resources.OpenNonAsset(layout_path, android::Asset::ACCESS_RANDOM, &cookie);
@@ -166,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);
}