Merge changes from topic "res_loader_dir" into rvc-dev am: b0544a733c am: c5f968d58b
Change-Id: I1a0a9b1e442ea10e34d003a3d0e0280574451bb3
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<>();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -18,12 +18,10 @@ package android.content.res.loader;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.res.AssetFileDescriptor;
|
||||
import android.content.res.AssetManager;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* Provides callbacks that allow for the value of a file-based resources or assets of a
|
||||
* {@link ResourcesProvider} to be specified or overridden.
|
||||
@@ -34,6 +32,10 @@ public interface AssetsProvider {
|
||||
* Callback that allows the value of a file-based resources or asset to be specified or
|
||||
* overridden.
|
||||
*
|
||||
* <p>The system will take ownership of the file descriptor returned from this method, so
|
||||
* {@link ParcelFileDescriptor#dup() dup} the file descriptor before returning if the system
|
||||
* should not own it.
|
||||
*
|
||||
* <p>There are two situations in which this method will be called:
|
||||
* <ul>
|
||||
* <li>AssetManager is queried for an InputStream of an asset using APIs like
|
||||
@@ -52,17 +54,7 @@ public interface AssetsProvider {
|
||||
* @see AssetManager#open
|
||||
*/
|
||||
@Nullable
|
||||
default InputStream loadAsset(@NonNull String path, int accessMode) throws IOException {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ParcelFileDescriptor} variant of {@link #loadAsset(String, int)}.
|
||||
*
|
||||
* @param path the asset path being loaded
|
||||
*/
|
||||
@Nullable
|
||||
default ParcelFileDescriptor loadAssetParcelFd(@NonNull String path) throws IOException {
|
||||
default AssetFileDescriptor loadAssetFd(@NonNull String path, int accessMode) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.content.res.loader;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* A {@link AssetsProvider} that searches a directory for assets.
|
||||
* Assumes that resource paths are resolvable child paths of the root directory passed in.
|
||||
*/
|
||||
public class DirectoryAssetsProvider implements AssetsProvider {
|
||||
|
||||
@NonNull
|
||||
private final File mDirectory;
|
||||
|
||||
/**
|
||||
* Creates a DirectoryAssetsProvider with given root directory.
|
||||
*
|
||||
* @param directory the root directory to resolve files from
|
||||
*/
|
||||
public DirectoryAssetsProvider(@NonNull File directory) {
|
||||
this.mDirectory = directory;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public InputStream loadAsset(@NonNull String path, int accessMode) throws IOException {
|
||||
final File file = findFile(path);
|
||||
if (file == null || !file.exists()) {
|
||||
return null;
|
||||
}
|
||||
return new FileInputStream(file);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public ParcelFileDescriptor loadAssetParcelFd(@NonNull String path) throws IOException {
|
||||
final File file = findFile(path);
|
||||
if (file == null || !file.exists()) {
|
||||
return null;
|
||||
}
|
||||
return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the file relative to the root directory.
|
||||
*
|
||||
* @param path the relative path of the file
|
||||
*/
|
||||
@Nullable
|
||||
public File findFile(@NonNull String path) {
|
||||
return mDirectory.toPath().resolve(path).toFile();
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public File getDirectory() {
|
||||
return mDirectory;
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
|
||||
@@ -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 */
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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/"
|
||||
}
|
||||
@@ -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">
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
Outside assets directory
|
||||
@@ -1 +0,0 @@
|
||||
In assets directory
|
||||
1
core/tests/ResourceLoaderTests/assets/asset.txt
Normal file
@@ -0,0 +1 @@
|
||||
In assets directory
|
||||
1
core/tests/ResourceLoaderTests/assets/base_asset.txt
Normal file
@@ -0,0 +1 @@
|
||||
Base
|
||||
|
Before Width: | Height: | Size: 963 B After Width: | Height: | Size: 963 B |
@@ -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>
|
||||
@@ -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>
|
||||
115
core/tests/ResourceLoaderTests/resources/Android.bp
Normal 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"]
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" />
|
||||
@@ -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" />
|
||||
@@ -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" />
|
||||
@@ -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" />
|
||||
@@ -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/
|
||||
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
@@ -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>
|
||||
@@ -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>
|
||||
@@ -0,0 +1 @@
|
||||
One
|
||||
@@ -0,0 +1 @@
|
||||
LoaderOne
|
||||
|
Before Width: | Height: | Size: 962 B After Width: | Height: | Size: 962 B |
@@ -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>
|
||||
@@ -0,0 +1 @@
|
||||
Two
|
||||
@@ -0,0 +1 @@
|
||||
LoaderTwo
|
||||
|
Before Width: | Height: | Size: 146 B After Width: | Height: | Size: 146 B |
@@ -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>
|
||||
@@ -0,0 +1 @@
|
||||
Three
|
||||
@@ -0,0 +1 @@
|
||||
LoaderThree
|
||||
|
Before Width: | Height: | Size: 146 B After Width: | Height: | Size: 146 B |
@@ -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>
|
||||
@@ -0,0 +1 @@
|
||||
Four
|
||||
@@ -0,0 +1 @@
|
||||
LoaderFour
|
||||
|
Before Width: | Height: | Size: 958 B After Width: | Height: | Size: 958 B |
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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>
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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()});
|
||||
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||