diff options
author | Ryan Mitchell <rtmitchell@google.com> | 2020-03-20 18:21:20 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2020-03-20 18:21:20 +0000 |
commit | c5f968d58b839115c649374deb16190ee80151f6 (patch) | |
tree | 46a755cc01078200cd5f02f618237e203328acca | |
parent | 85ded38f799020756024bce6ef07fe10dde8ffa5 (diff) | |
parent | b0544a733cc73522c7cef2a114eefe65552627f0 (diff) |
Merge changes from topic "res_loader_dir" into rvc-dev am: b0544a733c
Change-Id: I18c28c7293a24d712940c9e80e7817f9547512f8
94 files changed, 2000 insertions, 1808 deletions
diff --git a/api/current.txt b/api/current.txt index 4db6b33c9ccc..523d00c7cc43 100644 --- a/api/current.txt +++ b/api/current.txt @@ -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; } } diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index 9f5dee98acb7..495bb6b43718 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -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<>(); diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 18f13431c09a..20ddba41bda1 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -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, diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java index 4c6da03b4db3..27399e4b39bc 100644 --- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java +++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java @@ -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( diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java index 8db278579cc1..bc418061e1d1 100644 --- a/core/java/android/content/res/ApkAssets.java +++ b/core/java/android/content/res/ApkAssets.java @@ -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,157 +124,202 @@ 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) - throws IOException { - return new ApkAssets(fd, friendlyName, system, forceSharedLibrary, false /*arscOnly*/, - false /*forLoader*/); + @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 IDMAP at idmapPath. The overlay APK path - * is encoded within the IDMAP. + * Creates a new ApkAssets instance from the given file descriptor. * - * @param idmapPath Path to the IDMAP of an overlay APK. - * @param system When true, the APK is loaded as a system APK (framework). + * 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 loadOverlayFromPath(@NonNull String idmapPath, boolean system) + 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(idmapPath, system, false /*forceSharedLibrary*/, true /*overlay*/, - false /*arscOnly*/, false /*forLoader*/); + return new ApkAssets(FORMAT_APK, fd, friendlyName, offset, length, flags, assets); } /** - * Creates a new ApkAssets instance from the given path on disk for use with a - * {@link ResourcesProvider}. + * Creates a new ApkAssets instance from the IDMAP at idmapPath. The overlay APK path + * is encoded within the IDMAP. * - * @param path The path to an APK on disk. + * @param idmapPath Path to the IDMAP of an overlay APK. + * @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 loadApkForLoader(@NonNull String path) - throws IOException { - return new ApkAssets(path, false /*system*/, false /*forceSharedLibrary*/, - false /*overlay*/, false /*arscOnly*/, true /*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 file descriptor for use with a - * {@link ResourcesProvider}. + * 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 APK. + * @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. */ - @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*/); + 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 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 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 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 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(@FormatType int format, @NonNull String path, @PropertyFlags int flags, + @Nullable AssetsProvider assets) throws IOException { + Objects.requireNonNull(path, "path"); + mFlags = flags; + mNativePtr = nativeLoad(format, path, flags, assets); + mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/); + mAssets = assets; } - private ApkAssets(@NonNull String path, boolean system, boolean forceSharedLib, boolean overlay, - boolean arscOnly, boolean forLoader) throws IOException { - mForLoader = forLoader; - Objects.requireNonNull(path, "path"); - mNativePtr = arscOnly ? nativeLoadArsc(path, forLoader) - : nativeLoad(path, system, forceSharedLib, overlay, 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"); + mFlags = flags; + mNativePtr = nativeLoadFd(format, fd, friendlyName, 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, long offset, long length, @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 = 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); diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java index f295f8c531e9..d2103af1d247 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -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); @@ -332,6 +326,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`. * * @param diff The bitmask of changes generated by {@link Configuration#diff(Configuration)}. @@ -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); diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index cb809da3b867..d6a9f6990abe 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -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 diff --git a/core/java/android/content/res/loader/AssetsProvider.java b/core/java/android/content/res/loader/AssetsProvider.java index c315494cf728..0f8f1d1da8d2 100644 --- a/core/java/android/content/res/loader/AssetsProvider.java +++ b/core/java/android/content/res/loader/AssetsProvider.java @@ -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; } } diff --git a/core/java/android/content/res/loader/DirectoryAssetsProvider.java b/core/java/android/content/res/loader/DirectoryAssetsProvider.java deleted file mode 100644 index 81c2a4c1b4d6..000000000000 --- a/core/java/android/content/res/loader/DirectoryAssetsProvider.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.content.res.loader; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.os.ParcelFileDescriptor; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; - -/** - * A {@link AssetsProvider} that searches a directory for assets. - * Assumes that resource paths are resolvable child paths of the root directory passed in. - */ -public class DirectoryAssetsProvider implements AssetsProvider { - - @NonNull - private final File mDirectory; - - /** - * Creates a DirectoryAssetsProvider with given root directory. - * - * @param directory the root directory to resolve files from - */ - public DirectoryAssetsProvider(@NonNull File directory) { - this.mDirectory = directory; - } - - @Nullable - @Override - public InputStream loadAsset(@NonNull String path, int accessMode) throws IOException { - final File file = findFile(path); - if (file == null || !file.exists()) { - return null; - } - return new FileInputStream(file); - } - - @Nullable - @Override - public ParcelFileDescriptor loadAssetParcelFd(@NonNull String path) throws IOException { - final File file = findFile(path); - if (file == null || !file.exists()) { - return null; - } - return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); - } - - /** - * Finds the file relative to the root directory. - * - * @param path the relative path of the file - */ - @Nullable - public File findFile(@NonNull String path) { - return mDirectory.toPath().resolve(path).toFile(); - } - - @NonNull - public File getDirectory() { - return mDirectory; - } -} diff --git a/core/java/android/content/res/loader/ResourcesLoader.java b/core/java/android/content/res/loader/ResourcesLoader.java index 58fec603a2d5..c3084003c304 100644 --- a/core/java/android/content/res/loader/ResourcesLoader.java +++ b/core/java/android/content/res/loader/ResourcesLoader.java @@ -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. diff --git a/core/java/android/content/res/loader/ResourcesProvider.java b/core/java/android/content/res/loader/ResourcesProvider.java index 419ec7882f3d..0a698d18682b 100644 --- a/core/java/android/content/res/loader/ResourcesProvider.java +++ b/core/java/android/content/res/loader/ResourcesProvider.java @@ -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,105 +50,143 @@ 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 - */ - @NonNull - public static ResourcesProvider loadFromApk(@NonNull SharedMemory sharedMemory) - throws IOException { - return loadFromApk(sharedMemory, null); - } - - /** - * Creates a ResourcesProvider from an {@code .apk} file representation in memory. + * <p>The file descriptor is duplicated and the original may be closed by the application at any + * time without affecting the ResourcesProvider. * - * @param sharedMemory the shared memory containing the data of the APK to load - * @param assetsProvider the assets provider that implements the loading of file-based resources + * <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, - @Nullable AssetsProvider assetsProvider) + public static ResourcesProvider loadFromApk(@NonNull ParcelFileDescriptor fileDescriptor, + long offset, long length, @Nullable AssetsProvider assetsProvider) throws IOException { - return new ResourcesProvider( - ApkAssets.loadApkForLoader(sharedMemory.getFileDescriptor()), assetsProvider); + return new ResourcesProvider(ApkAssets.loadFromFd(fileDescriptor.getFileDescriptor(), + fileDescriptor.toString(), offset, length, ApkAssets.PROPERTY_LOADER, + assetsProvider)); } /** * Creates a ResourcesProvider from a resources table ({@code .arsc}) 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 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 loadFromTable(@NonNull ParcelFileDescriptor fileDescriptor, @Nullable AssetsProvider assetsProvider) throws IOException { return new ResourcesProvider( - ApkAssets.loadArscForLoader(fileDescriptor.getFileDescriptor()), assetsProvider); + ApkAssets.loadTableFromFd(fileDescriptor.getFileDescriptor(), + fileDescriptor.toString(), ApkAssets.PROPERTY_LOADER, assetsProvider)); } /** - * Creates a ResourcesProvider from a resources table ({@code .arsc}) 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 resources table to load + * 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 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 SharedMemory sharedMemory, - @Nullable AssetsProvider assetsProvider) + public static ResourcesProvider loadFromTable(@NonNull ParcelFileDescriptor fileDescriptor, + long offset, long length, @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) { - this.mApkAssets = apkAssets; - this.mAssetsProvider = 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)); } - @Nullable - public AssetsProvider getAssetsProvider() { - return mAssetsProvider; + + private ResourcesProvider(@NonNull ApkAssets apkAssets) { + this.mApkAssets = apkAssets; } /** @hide */ diff --git a/core/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp index 491d4a61cee4..fbdd4060d7f2 100644 --- a/core/jni/android_content_res_ApkAssets.cpp +++ b/core/jni/android_content_res_ApkAssets.cpp @@ -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) { +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("LoadApkAssetsArsc(%s)", path.c_str()).c_str()); - - std::unique_ptr<const ApkAssets> apk_assets = ApkAssets::LoadArsc(path.c_str(), for_loader); + ATRACE_NAME(base::StringPrintf("LoadApkAssetsFd(%s)", friendly_name_utf8.c_str()).c_str()); - 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()); + if (offset < 0) { + jniThrowException(env, "java/lang/IllegalArgumentException", + "offset cannot be negative"); 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) { - ScopedUtfChars friendly_name_utf8(env, friendly_name); - if (friendly_name_utf8.c_str() == nullptr) { + 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)); } diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp index 289453042099..d1d3cf25a4b5 100644 --- a/core/jni/android_util_AssetManager.cpp +++ b/core/jni/android_util_AssetManager.cpp @@ -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"); diff --git a/core/tests/ResourceLoaderTests/Android.bp b/core/tests/ResourceLoaderTests/Android.bp index fec4628106a5..2b14bca1f7c1 100644 --- a/core/tests/ResourceLoaderTests/Android.bp +++ b/core/tests/ResourceLoaderTests/Android.bp @@ -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" ] -} - -filegroup { - name: "FrameworksResourceLoaderTestsResources", - srcs: ["resources"], + ":FrameworksResourceLoaderTests_ProviderOne_Split", + ":FrameworksResourceLoaderTests_ProviderTwo_Split", + ":FrameworksResourceLoaderTests_ProviderThree_Split", + ":FrameworksResourceLoaderTests_ProviderFour_Split" + ] } -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/" +}
\ No newline at end of file diff --git a/core/tests/ResourceLoaderTests/AndroidTest.xml b/core/tests/ResourceLoaderTests/AndroidTest.xml index d732132bef02..800e7a7124ac 100644 --- a/core/tests/ResourceLoaderTests/AndroidTest.xml +++ b/core/tests/ResourceLoaderTests/AndroidTest.xml @@ -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"> diff --git a/core/tests/ResourceLoaderTests/NonAsset.txt b/core/tests/ResourceLoaderTests/NonAsset.txt deleted file mode 100644 index 5c0b2cc98d64..000000000000 --- a/core/tests/ResourceLoaderTests/NonAsset.txt +++ /dev/null @@ -1 +0,0 @@ -Outside assets directory diff --git a/core/tests/ResourceLoaderTests/assets/Asset.txt b/core/tests/ResourceLoaderTests/assets/Asset.txt deleted file mode 100644 index 03f9a0fd146a..000000000000 --- a/core/tests/ResourceLoaderTests/assets/Asset.txt +++ /dev/null @@ -1 +0,0 @@ -In assets directory diff --git a/core/tests/ResourceLoaderTests/assets/asset.txt b/core/tests/ResourceLoaderTests/assets/asset.txt new file mode 100644 index 000000000000..271704bdc1b5 --- /dev/null +++ b/core/tests/ResourceLoaderTests/assets/asset.txt @@ -0,0 +1 @@ +In assets directory
\ No newline at end of file diff --git a/core/tests/ResourceLoaderTests/assets/base_asset.txt b/core/tests/ResourceLoaderTests/assets/base_asset.txt new file mode 100644 index 000000000000..8e62cc346238 --- /dev/null +++ b/core/tests/ResourceLoaderTests/assets/base_asset.txt @@ -0,0 +1 @@ +Base
\ No newline at end of file diff --git a/core/tests/ResourceLoaderTests/res/drawable-nodpi/non_asset_bitmap.png b/core/tests/ResourceLoaderTests/res/drawable-nodpi/drawable_png.png Binary files differindex 8102d1539d53..8102d1539d53 100644 --- a/core/tests/ResourceLoaderTests/res/drawable-nodpi/non_asset_bitmap.png +++ b/core/tests/ResourceLoaderTests/res/drawable-nodpi/drawable_png.png diff --git a/core/tests/ResourceLoaderTests/res/drawable-nodpi/non_asset_drawable.xml b/core/tests/ResourceLoaderTests/res/drawable-nodpi/drawable_xml.xml index d1211c50a203..d1211c50a203 100644 --- a/core/tests/ResourceLoaderTests/res/drawable-nodpi/non_asset_drawable.xml +++ b/core/tests/ResourceLoaderTests/res/drawable-nodpi/drawable_xml.xml diff --git a/core/tests/ResourceLoaderTests/res/values/strings.xml b/core/tests/ResourceLoaderTests/res/values/strings.xml deleted file mode 100644 index 28b8f73d45a6..000000000000 --- a/core/tests/ResourceLoaderTests/res/values/strings.xml +++ /dev/null @@ -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> diff --git a/core/tests/ResourceLoaderTests/resources/res/values/non_asset_bitmap_id.xml b/core/tests/ResourceLoaderTests/res/values/values.xml index 38b152beb76f..ad785322fcc9 100644 --- a/core/tests/ResourceLoaderTests/resources/res/values/non_asset_bitmap_id.xml +++ b/core/tests/ResourceLoaderTests/res/values/values.xml @@ -16,5 +16,6 @@ --> <resources> - <public type="drawable" name="non_asset_bitmap" id="0x7f010000" /> + <dimen name="test">0dp</dimen> + <string name="test">Not overlaid</string> </resources> diff --git a/core/tests/ResourceLoaderTests/resources/Android.bp b/core/tests/ResourceLoaderTests/resources/Android.bp new file mode 100644 index 000000000000..18ef64b70927 --- /dev/null +++ b/core/tests/ResourceLoaderTests/resources/Android.bp @@ -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"] +}
\ No newline at end of file diff --git a/core/tests/ResourceLoaderTests/resources/AndroidManifestApp.xml b/core/tests/ResourceLoaderTests/resources/AndroidManifestApp.xml index 5dd8a966e2b7..c8a3590aaa62 100644 --- a/core/tests/ResourceLoaderTests/resources/AndroidManifestApp.xml +++ b/core/tests/ResourceLoaderTests/resources/AndroidManifestApp.xml @@ -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> diff --git a/core/tests/ResourceLoaderTests/resources/AndroidManifestFramework.xml b/core/tests/ResourceLoaderTests/resources/AndroidManifestFramework.xml index 5a92ae9e662b..d5fa83f59546 100644 --- a/core/tests/ResourceLoaderTests/resources/AndroidManifestFramework.xml +++ b/core/tests/ResourceLoaderTests/resources/AndroidManifestFramework.xml @@ -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> diff --git a/core/tests/ResourceLoaderTests/splits/SplitOne/AndroidManifest.xml b/core/tests/ResourceLoaderTests/resources/AndroidManifestSplit1.xml index b14bd8600f31..5cd4227286a2 100644 --- a/core/tests/ResourceLoaderTests/splits/SplitOne/AndroidManifest.xml +++ b/core/tests/ResourceLoaderTests/resources/AndroidManifestSplit1.xml @@ -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" /> diff --git a/core/tests/ResourceLoaderTests/splits/SplitThree/AndroidManifest.xml b/core/tests/ResourceLoaderTests/resources/AndroidManifestSplit2.xml index ae1579b178f3..b5180e66b3a1 100644 --- a/core/tests/ResourceLoaderTests/splits/SplitThree/AndroidManifest.xml +++ b/core/tests/ResourceLoaderTests/resources/AndroidManifestSplit2.xml @@ -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" /> diff --git a/core/tests/ResourceLoaderTests/splits/SplitTwo/AndroidManifest.xml b/core/tests/ResourceLoaderTests/resources/AndroidManifestSplit3.xml index aad8c27a1a3b..8ddb89280d60 100644 --- a/core/tests/ResourceLoaderTests/splits/SplitTwo/AndroidManifest.xml +++ b/core/tests/ResourceLoaderTests/resources/AndroidManifestSplit3.xml @@ -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" /> diff --git a/core/tests/ResourceLoaderTests/splits/SplitFour/AndroidManifest.xml b/core/tests/ResourceLoaderTests/resources/AndroidManifestSplit4.xml index 24a0a2a64afb..b6bf552c9892 100644 --- a/core/tests/ResourceLoaderTests/splits/SplitFour/AndroidManifest.xml +++ b/core/tests/ResourceLoaderTests/resources/AndroidManifestSplit4.xml @@ -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" /> diff --git a/core/tests/ResourceLoaderTests/resources/compileAndLink.sh b/core/tests/ResourceLoaderTests/resources/compileAndLink.sh deleted file mode 100755 index 8e05aefccd39..000000000000 --- a/core/tests/ResourceLoaderTests/resources/compileAndLink.sh +++ /dev/null @@ -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/ diff --git a/core/tests/ResourceLoaderTests/resources/res/drawable-mdpi/ic_delete.png b/core/tests/ResourceLoaderTests/resources/framework/res/drawable-mdpi/ic_delete.png Binary files differindex f3e53d7596c1..f3e53d7596c1 100644 --- a/core/tests/ResourceLoaderTests/resources/res/drawable-mdpi/ic_delete.png +++ b/core/tests/ResourceLoaderTests/resources/framework/res/drawable-mdpi/ic_delete.png diff --git a/core/tests/ResourceLoaderTests/resources/res/layout/activity_list_item.xml b/core/tests/ResourceLoaderTests/resources/framework/res/layout/activity_list_item.xml index d59059b453d6..d59059b453d6 100644 --- a/core/tests/ResourceLoaderTests/resources/res/layout/activity_list_item.xml +++ b/core/tests/ResourceLoaderTests/resources/framework/res/layout/activity_list_item.xml diff --git a/core/tests/ResourceLoaderTests/resources/res/values/string_four.xml b/core/tests/ResourceLoaderTests/resources/framework/res/values/public.xml index 8789bcdb066c..2e501826e00a 100644 --- a/core/tests/ResourceLoaderTests/resources/res/values/string_four.xml +++ b/core/tests/ResourceLoaderTests/resources/framework/res/values/public.xml @@ -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">SomeRidiculouslyUnlikelyStringFour</string> -</resources> +</resources>
\ No newline at end of file diff --git a/core/tests/ResourceLoaderTests/resources/res/values/dimen_four.xml b/core/tests/ResourceLoaderTests/resources/framework/res/values/values.xml index 5b30eba5953b..5f6e90cf9e0d 100644 --- a/core/tests/ResourceLoaderTests/resources/res/values/dimen_four.xml +++ b/core/tests/ResourceLoaderTests/resources/framework/res/values/values.xml @@ -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> diff --git a/core/tests/ResourceLoaderTests/resources/provider1/assets/asset.txt b/core/tests/ResourceLoaderTests/resources/provider1/assets/asset.txt new file mode 100644 index 000000000000..6dcd8e419a8c --- /dev/null +++ b/core/tests/ResourceLoaderTests/resources/provider1/assets/asset.txt @@ -0,0 +1 @@ +One
\ No newline at end of file diff --git a/core/tests/ResourceLoaderTests/resources/provider1/assets/loader_asset.txt b/core/tests/ResourceLoaderTests/resources/provider1/assets/loader_asset.txt new file mode 100644 index 000000000000..0e41ffa475af --- /dev/null +++ b/core/tests/ResourceLoaderTests/resources/provider1/assets/loader_asset.txt @@ -0,0 +1 @@ +LoaderOne
\ No newline at end of file diff --git a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapRed.png b/core/tests/ResourceLoaderTests/resources/provider1/res/drawable-nodpi/drawable_png.png Binary files differindex 4eb8ca3537ea..4eb8ca3537ea 100644 --- a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapRed.png +++ b/core/tests/ResourceLoaderTests/resources/provider1/res/drawable-nodpi/drawable_png.png diff --git a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableOne.xml b/core/tests/ResourceLoaderTests/resources/provider1/res/drawable-nodpi/drawable_xml.xml index 57a8cf1b86de..57a8cf1b86de 100644 --- a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableOne.xml +++ b/core/tests/ResourceLoaderTests/resources/provider1/res/drawable-nodpi/drawable_xml.xml diff --git a/core/tests/ResourceLoaderTests/resources/res/layout/layout_one.xml b/core/tests/ResourceLoaderTests/resources/provider1/res/layout/layout.xml index ede3838be8de..ede3838be8de 100644 --- a/core/tests/ResourceLoaderTests/resources/res/layout/layout_one.xml +++ b/core/tests/ResourceLoaderTests/resources/provider1/res/layout/layout.xml diff --git a/core/tests/ResourceLoaderTests/resources/res/values/string_three.xml b/core/tests/ResourceLoaderTests/resources/provider1/res/values/values.xml index 82cd6ec7270f..5ef75d5426a0 100644 --- a/core/tests/ResourceLoaderTests/resources/res/values/string_three.xml +++ b/core/tests/ResourceLoaderTests/resources/provider1/res/values/values.xml @@ -16,6 +16,9 @@ --> <resources> - <public type="string" name="cancel" id="0x01040000" /> - <string name="cancel">SomeRidiculouslyUnlikelyStringThree</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>
\ No newline at end of file diff --git a/core/tests/ResourceLoaderTests/resources/provider2/assets/asset.txt b/core/tests/ResourceLoaderTests/resources/provider2/assets/asset.txt new file mode 100644 index 000000000000..5673baa5b53d --- /dev/null +++ b/core/tests/ResourceLoaderTests/resources/provider2/assets/asset.txt @@ -0,0 +1 @@ +Two
\ No newline at end of file diff --git a/core/tests/ResourceLoaderTests/resources/provider2/assets/loader_asset.txt b/core/tests/ResourceLoaderTests/resources/provider2/assets/loader_asset.txt new file mode 100644 index 000000000000..bca782ec1b2b --- /dev/null +++ b/core/tests/ResourceLoaderTests/resources/provider2/assets/loader_asset.txt @@ -0,0 +1 @@ +LoaderTwo
\ No newline at end of file diff --git a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapGreen.png b/core/tests/ResourceLoaderTests/resources/provider2/res/drawable-nodpi/drawable_png.png Binary files differindex 671d6d00be31..671d6d00be31 100644 --- a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapGreen.png +++ b/core/tests/ResourceLoaderTests/resources/provider2/res/drawable-nodpi/drawable_png.png diff --git a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableTwo.xml b/core/tests/ResourceLoaderTests/resources/provider2/res/drawable-nodpi/drawable_xml.xml index 333fe346998c..333fe346998c 100644 --- a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableTwo.xml +++ b/core/tests/ResourceLoaderTests/resources/provider2/res/drawable-nodpi/drawable_xml.xml diff --git a/core/tests/ResourceLoaderTests/resources/res/layout/layout_two.xml b/core/tests/ResourceLoaderTests/resources/provider2/res/layout/layout.xml index d8bff90d56d8..d8bff90d56d8 100644 --- a/core/tests/ResourceLoaderTests/resources/res/layout/layout_two.xml +++ b/core/tests/ResourceLoaderTests/resources/provider2/res/layout/layout.xml diff --git a/core/tests/ResourceLoaderTests/resources/provider2/res/values/values.xml b/core/tests/ResourceLoaderTests/resources/provider2/res/values/values.xml new file mode 100644 index 000000000000..387c51905d8a --- /dev/null +++ b/core/tests/ResourceLoaderTests/resources/provider2/res/values/values.xml @@ -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>
\ No newline at end of file diff --git a/core/tests/ResourceLoaderTests/resources/provider3/assets/asset.txt b/core/tests/ResourceLoaderTests/resources/provider3/assets/asset.txt new file mode 100644 index 000000000000..368c34d3ba04 --- /dev/null +++ b/core/tests/ResourceLoaderTests/resources/provider3/assets/asset.txt @@ -0,0 +1 @@ +Three
\ No newline at end of file diff --git a/core/tests/ResourceLoaderTests/resources/provider3/assets/loader_asset.txt b/core/tests/ResourceLoaderTests/resources/provider3/assets/loader_asset.txt new file mode 100644 index 000000000000..bae8ef79a2ce --- /dev/null +++ b/core/tests/ResourceLoaderTests/resources/provider3/assets/loader_asset.txt @@ -0,0 +1 @@ +LoaderThree
\ No newline at end of file diff --git a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapBlue.png b/core/tests/ResourceLoaderTests/resources/provider3/res/drawable-nodpi/drawable_png.png Binary files differindex 5231d175569e..5231d175569e 100644 --- a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapBlue.png +++ b/core/tests/ResourceLoaderTests/resources/provider3/res/drawable-nodpi/drawable_png.png diff --git a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableThree.xml b/core/tests/ResourceLoaderTests/resources/provider3/res/drawable-nodpi/drawable_xml.xml index 41095d4a158b..41095d4a158b 100644 --- a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableThree.xml +++ b/core/tests/ResourceLoaderTests/resources/provider3/res/drawable-nodpi/drawable_xml.xml diff --git a/core/tests/ResourceLoaderTests/resources/res/layout/layout_three.xml b/core/tests/ResourceLoaderTests/resources/provider3/res/layout/layout.xml index d58d3db12ad4..d58d3db12ad4 100644 --- a/core/tests/ResourceLoaderTests/resources/res/layout/layout_three.xml +++ b/core/tests/ResourceLoaderTests/resources/provider3/res/layout/layout.xml diff --git a/core/tests/ResourceLoaderTests/resources/provider3/res/values/values.xml b/core/tests/ResourceLoaderTests/resources/provider3/res/values/values.xml new file mode 100644 index 000000000000..ab75bfac29c6 --- /dev/null +++ b/core/tests/ResourceLoaderTests/resources/provider3/res/values/values.xml @@ -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>
\ No newline at end of file diff --git a/core/tests/ResourceLoaderTests/resources/provider4/assets/asset.txt b/core/tests/ResourceLoaderTests/resources/provider4/assets/asset.txt new file mode 100644 index 000000000000..ad70cdd4ab64 --- /dev/null +++ b/core/tests/ResourceLoaderTests/resources/provider4/assets/asset.txt @@ -0,0 +1 @@ +Four
\ No newline at end of file diff --git a/core/tests/ResourceLoaderTests/resources/provider4/assets/loader_asset.txt b/core/tests/ResourceLoaderTests/resources/provider4/assets/loader_asset.txt new file mode 100644 index 000000000000..b75d9963575b --- /dev/null +++ b/core/tests/ResourceLoaderTests/resources/provider4/assets/loader_asset.txt @@ -0,0 +1 @@ +LoaderFour
\ No newline at end of file diff --git a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapWhite.png b/core/tests/ResourceLoaderTests/resources/provider4/res/drawable-nodpi/drawable_png.png Binary files differindex e9a4cfcef316..e9a4cfcef316 100644 --- a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapWhite.png +++ b/core/tests/ResourceLoaderTests/resources/provider4/res/drawable-nodpi/drawable_png.png diff --git a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableFour.xml b/core/tests/ResourceLoaderTests/resources/provider4/res/drawable-nodpi/drawable_xml.xml index 0623245c6152..0623245c6152 100644 --- a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableFour.xml +++ b/core/tests/ResourceLoaderTests/resources/provider4/res/drawable-nodpi/drawable_xml.xml diff --git a/core/tests/ResourceLoaderTests/resources/res/layout/layout_four.xml b/core/tests/ResourceLoaderTests/resources/provider4/res/layout/layout.xml index ab9e26529fe7..ab9e26529fe7 100644 --- a/core/tests/ResourceLoaderTests/resources/res/layout/layout_four.xml +++ b/core/tests/ResourceLoaderTests/resources/provider4/res/layout/layout.xml diff --git a/core/tests/ResourceLoaderTests/resources/provider4/res/values/values.xml b/core/tests/ResourceLoaderTests/resources/provider4/res/values/values.xml new file mode 100644 index 000000000000..896993e9d9a6 --- /dev/null +++ b/core/tests/ResourceLoaderTests/resources/provider4/res/values/values.xml @@ -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>
\ No newline at end of file diff --git a/core/tests/ResourceLoaderTests/resources/res/values/dimen_three.xml b/core/tests/ResourceLoaderTests/resources/provider_additional/res/values/values.xml index 07a35cedd886..29918d7105ef 100644 --- a/core/tests/ResourceLoaderTests/resources/res/values/dimen_three.xml +++ b/core/tests/ResourceLoaderTests/resources/provider_additional/res/values/values.xml @@ -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> diff --git a/core/tests/ResourceLoaderTests/splits/SplitFour/res/values/string_split.xml b/core/tests/ResourceLoaderTests/resources/provider_stable/res/values/public.xml index 4759db978dcb..269c40fc2a38 100644 --- a/core/tests/ResourceLoaderTests/splits/SplitFour/res/values/string_split.xml +++ b/core/tests/ResourceLoaderTests/resources/provider_stable/res/values/public.xml @@ -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>
\ No newline at end of file diff --git a/core/tests/ResourceLoaderTests/resources/res/values/activity_list_item_id.xml b/core/tests/ResourceLoaderTests/resources/res/values/activity_list_item_id.xml deleted file mode 100644 index a552431e23be..000000000000 --- a/core/tests/ResourceLoaderTests/resources/res/values/activity_list_item_id.xml +++ /dev/null @@ -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> diff --git a/core/tests/ResourceLoaderTests/resources/res/values/dimen_one.xml b/core/tests/ResourceLoaderTests/resources/res/values/dimen_one.xml deleted file mode 100644 index b17ec1c66717..000000000000 --- a/core/tests/ResourceLoaderTests/resources/res/values/dimen_one.xml +++ /dev/null @@ -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> diff --git a/core/tests/ResourceLoaderTests/resources/res/values/dimen_two.xml b/core/tests/ResourceLoaderTests/resources/res/values/dimen_two.xml deleted file mode 100644 index 570b40aa7a7a..000000000000 --- a/core/tests/ResourceLoaderTests/resources/res/values/dimen_two.xml +++ /dev/null @@ -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> diff --git a/core/tests/ResourceLoaderTests/resources/res/values/drawable_one.xml b/core/tests/ResourceLoaderTests/resources/res/values/drawable_one.xml deleted file mode 100644 index b5b4dfd22231..000000000000 --- a/core/tests/ResourceLoaderTests/resources/res/values/drawable_one.xml +++ /dev/null @@ -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> diff --git a/core/tests/ResourceLoaderTests/resources/res/values/layout_id.xml b/core/tests/ResourceLoaderTests/resources/res/values/layout_id.xml deleted file mode 100644 index 4962a07bc8c7..000000000000 --- a/core/tests/ResourceLoaderTests/resources/res/values/layout_id.xml +++ /dev/null @@ -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> diff --git a/core/tests/ResourceLoaderTests/resources/res/values/non_asset_drawable_id.xml b/core/tests/ResourceLoaderTests/resources/res/values/non_asset_drawable_id.xml deleted file mode 100644 index bdd6f58e5824..000000000000 --- a/core/tests/ResourceLoaderTests/resources/res/values/non_asset_drawable_id.xml +++ /dev/null @@ -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_drawable" id="0x7f010001" /> -</resources> diff --git a/core/tests/ResourceLoaderTests/resources/res/values/string_one.xml b/core/tests/ResourceLoaderTests/resources/res/values/string_one.xml deleted file mode 100644 index 4fc52723946e..000000000000 --- a/core/tests/ResourceLoaderTests/resources/res/values/string_one.xml +++ /dev/null @@ -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> diff --git a/core/tests/ResourceLoaderTests/resources/res/values/string_two.xml b/core/tests/ResourceLoaderTests/resources/res/values/string_two.xml deleted file mode 100644 index 3604d7b21cf5..000000000000 --- a/core/tests/ResourceLoaderTests/resources/res/values/string_two.xml +++ /dev/null @@ -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> diff --git a/core/tests/ResourceLoaderTests/splits/SplitFour/Android.bp b/core/tests/ResourceLoaderTests/splits/SplitFour/Android.bp deleted file mode 100644 index eb4d8e1ac7f2..000000000000 --- a/core/tests/ResourceLoaderTests/splits/SplitFour/Android.bp +++ /dev/null @@ -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" -} diff --git a/core/tests/ResourceLoaderTests/splits/SplitOne/Android.bp b/core/tests/ResourceLoaderTests/splits/SplitOne/Android.bp deleted file mode 100644 index 897897fbf254..000000000000 --- a/core/tests/ResourceLoaderTests/splits/SplitOne/Android.bp +++ /dev/null @@ -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" -} diff --git a/core/tests/ResourceLoaderTests/splits/SplitOne/res/values/string_split.xml b/core/tests/ResourceLoaderTests/splits/SplitOne/res/values/string_split.xml deleted file mode 100644 index 3c215ebc287c..000000000000 --- a/core/tests/ResourceLoaderTests/splits/SplitOne/res/values/string_split.xml +++ /dev/null @@ -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> diff --git a/core/tests/ResourceLoaderTests/splits/SplitThree/Android.bp b/core/tests/ResourceLoaderTests/splits/SplitThree/Android.bp deleted file mode 100644 index bf98a740cd88..000000000000 --- a/core/tests/ResourceLoaderTests/splits/SplitThree/Android.bp +++ /dev/null @@ -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" -} diff --git a/core/tests/ResourceLoaderTests/splits/SplitThree/res/values/string_spli.xml b/core/tests/ResourceLoaderTests/splits/SplitThree/res/values/string_spli.xml deleted file mode 100644 index 97682aa1b5cf..000000000000 --- a/core/tests/ResourceLoaderTests/splits/SplitThree/res/values/string_spli.xml +++ /dev/null @@ -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> diff --git a/core/tests/ResourceLoaderTests/splits/SplitTwo/Android.bp b/core/tests/ResourceLoaderTests/splits/SplitTwo/Android.bp deleted file mode 100644 index 4582808934df..000000000000 --- a/core/tests/ResourceLoaderTests/splits/SplitTwo/Android.bp +++ /dev/null @@ -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" -} diff --git a/core/tests/ResourceLoaderTests/splits/SplitTwo/res/values/string_split.xml b/core/tests/ResourceLoaderTests/splits/SplitTwo/res/values/string_split.xml deleted file mode 100644 index a367063dd43e..000000000000 --- a/core/tests/ResourceLoaderTests/splits/SplitTwo/res/values/string_split.xml +++ /dev/null @@ -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> diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryAssetsProviderTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryAssetsProviderTest.kt deleted file mode 100644 index afe9d7f19f0d..000000000000 --- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryAssetsProviderTest.kt +++ /dev/null @@ -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()) - } -} diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetsTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetsTest.kt deleted file mode 100644 index da5092de0627..000000000000 --- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetsTest.kt +++ /dev/null @@ -1,220 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.content.res.loader.test - -import android.content.res.AssetManager -import android.content.res.loader.AssetsProvider -import android.content.res.loader.DirectoryAssetsProvider -import android.content.res.loader.ResourcesLoader -import android.content.res.loader.ResourcesProvider -import com.google.common.truth.Truth.assertThat -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.rules.TestName -import org.junit.runner.RunWith -import org.junit.runners.Parameterized -import org.mockito.Mockito.anyInt -import org.mockito.Mockito.anyString -import org.mockito.Mockito.doAnswer -import org.mockito.Mockito.doReturn -import org.mockito.Mockito.eq -import org.mockito.Mockito.inOrder -import org.mockito.Mockito.mock -import java.io.File -import java.io.FileNotFoundException -import java.io.IOException -import java.nio.file.Paths - -@RunWith(Parameterized::class) -class ResourceLoaderAssetsTest : ResourceLoaderTestBase() { - - companion object { - private const val BASE_TEST_PATH = "android/content/res/loader/test/file.txt" - private const val TEST_TEXT = "some text" - - @JvmStatic - @Parameterized.Parameters(name = "{0}") - fun parameters(): Array<Array<out Any?>> { - val fromInputStream: AssetsProvider.(String) -> Any? = { - loadAsset(eq(it), anyInt()) - } - - val fromFileDescriptor: AssetsProvider.(String) -> Any? = { - loadAssetParcelFd(eq(it)) - } - - val openAsset: AssetManager.() -> String? = { - open(BASE_TEST_PATH).reader().readText() - } - - val openNonAsset: AssetManager.() -> String? = { - openNonAssetFd(BASE_TEST_PATH).readText() - } - - return arrayOf( - arrayOf("assets", fromInputStream, openAsset), - arrayOf("", fromFileDescriptor, openNonAsset) - ) - } - } - - @get:Rule - val testName = TestName() - - @JvmField - @field:Parameterized.Parameter(0) - var prefix: String? = null - - @field:Parameterized.Parameter(1) - lateinit var loadAssetFunction: AssetsProvider.(String) -> Any? - - @field:Parameterized.Parameter(2) - lateinit var openAssetFunction: AssetManager.() -> String? - - private val testPath: String - get() = Paths.get(prefix.orEmpty(), BASE_TEST_PATH).toString() - - private fun AssetsProvider.loadAsset() = loadAssetFunction(testPath) - - private fun AssetManager.openAsset() = openAssetFunction() - - private lateinit var testDir: File - - @Before - fun setUpTestDir() { - testDir = context.filesDir.resolve("DirectoryAssetsProvider_${testName.methodName}") - testDir.resolve(testPath).apply { parentFile!!.mkdirs() }.writeText(TEST_TEXT) - } - - @Test - fun multipleProvidersSearchesBackwards() { - // DirectoryResourceLoader relies on a private field and can't be spied directly, so wrap it - val assetsProvider = DirectoryAssetsProvider(testDir) - val assetProviderWrapper = mock(AssetsProvider::class.java).apply { - doAnswer { assetsProvider.loadAsset(it.arguments[0] as String, it.arguments[1] as Int) } - .`when`(this).loadAsset(anyString(), anyInt()) - doAnswer { assetsProvider.loadAssetParcelFd(it.arguments[0] as String) } - .`when`(this).loadAssetParcelFd(anyString()) - } - - val one = ResourcesProvider.empty(assetProviderWrapper) - val two = mockProvider { - doReturn(null).`when`(it).loadAsset() - } - - val loader = ResourcesLoader() - loader.providers = listOf(one, two) - resources.addLoaders(loader) - - assertOpenedAsset() - inOrder(two.assetsProvider, one.assetsProvider).apply { - verify(two.assetsProvider)?.loadAsset() - verify(one.assetsProvider)?.loadAsset() - } - } - - @Test - fun multipleLoadersSearchesBackwards() { - // DirectoryResourceLoader relies on a private field and can't be spied directly, so wrap it - val assetsProvider = DirectoryAssetsProvider(testDir) - val assetProviderWrapper = mock(AssetsProvider::class.java).apply { - doAnswer { assetsProvider.loadAsset(it.arguments[0] as String, it.arguments[1] as Int) } - .`when`(this).loadAsset(anyString(), anyInt()) - doAnswer { assetsProvider.loadAssetParcelFd(it.arguments[0] as String) } - .`when`(this).loadAssetParcelFd(anyString()) - } - - val one = ResourcesProvider.empty(assetProviderWrapper) - val two = mockProvider { - doReturn(null).`when`(it).loadAsset() - } - - val loader1 = ResourcesLoader() - loader1.addProvider(one) - val loader2 = ResourcesLoader() - loader2.addProvider(two) - - resources.addLoaders(loader1, loader2) - - assertOpenedAsset() - inOrder(two.assetsProvider, one.assetsProvider).apply { - verify(two.assetsProvider)?.loadAsset() - verify(one.assetsProvider)?.loadAsset() - } - } - - @Test(expected = FileNotFoundException::class) - fun failToFindThrowsFileNotFound() { - val assetsProvider1 = mock(AssetsProvider::class.java).apply { - doReturn(null).`when`(this).loadAsset() - } - val assetsProvider2 = mock(AssetsProvider::class.java).apply { - doReturn(null).`when`(this).loadAsset() - } - - val loader = ResourcesLoader() - val one = ResourcesProvider.empty(assetsProvider1) - val two = ResourcesProvider.empty(assetsProvider2) - resources.addLoaders(loader) - loader.providers = listOf(one, two) - - assertOpenedAsset() - } - - @Test - fun throwingIOExceptionIsSkipped() { - val assetsProvider1 = DirectoryAssetsProvider(testDir) - val assetsProvider2 = mock(AssetsProvider::class.java).apply { - doAnswer { throw IOException() }.`when`(this).loadAsset() - } - - val loader = ResourcesLoader() - val one = ResourcesProvider.empty(assetsProvider1) - val two = ResourcesProvider.empty(assetsProvider2) - resources.addLoaders(loader) - loader.providers = listOf(one, two) - - assertOpenedAsset() - } - - @Test(expected = IllegalStateException::class) - fun throwingNonIOExceptionCausesFailure() { - val assetsProvider1 = DirectoryAssetsProvider(testDir) - val assetsProvider2 = mock(AssetsProvider::class.java).apply { - doAnswer { throw IllegalStateException() }.`when`(this).loadAsset() - } - - val loader = ResourcesLoader() - val one = ResourcesProvider.empty(assetsProvider1) - val two = ResourcesProvider.empty(assetsProvider2) - resources.addLoaders(loader) - loader.providers = listOf(one, two) - - assertOpenedAsset() - } - - private fun mockProvider(block: (AssetsProvider) -> Unit = {}): ResourcesProvider { - return ResourcesProvider.empty(mock(AssetsProvider::class.java).apply { - block.invoke(this) - }) - } - - private fun assertOpenedAsset() { - assertThat(resources.assets.openAsset()).isEqualTo(TEST_TEXT) - } -} diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderTestBase.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderTestBase.kt index 4c62955e41a5..ec6a605340ae 100644 --- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderTestBase.kt +++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderTestBase.kt @@ -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) + 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") + } } - DataType.ASSET -> { - val assetsProvider = mock(AssetsProvider::class.java) - doAnswer { byteInputStream() }.`when`(assetsProvider) - .loadAsset(eq("assets/Asset.txt"), anyInt()) - ResourcesProvider.empty(assetsProvider) + } + + 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 } - 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) + } + + /** 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 } - 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) + + override fun loadAssetFd(path: String, accessMode: Int): AssetFileDescriptor? { + return if (loadAssetResults.containsKey(path)) AssetFileDescriptor( + ParcelFileDescriptor.dup(loadAssetResults[path]), 0, + AssetFileDescriptor.UNKNOWN_LENGTH) else null } - 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) + + override fun close() { + for (f in loadAssetResults.values) { + Os.close(f) + } } - 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) + } + + /** 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 } - 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) + + 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 } - protected fun openArsc(rawName: String, assetsProvider: AssetsProvider): ResourcesProvider { - return context.copiedRawFile("${rawName}Arsc") - .use { ResourcesProvider.loadFromTable(it, assetsProvider) } - .also { openedObjects += it } + /** 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 } } diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt index 16eafcd451c2..5aa8814c7481 100644 --- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt +++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt @@ -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) - ) - - // 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) - ) + "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) - // 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 fun openOne() = parameter.providerOne.openProvider() - private fun openTwo() = parameter.providerTwo.openProvider() - private fun openThree() = parameter.providerThree.openProvider() - private fun openFour() = parameter.providerFour.openProvider() + 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() = 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 } } diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/Utils.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/Utils.kt index 4e8ee5cf3c3b..526160d04000 100644 --- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/Utils.kt +++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/Utils.kt @@ -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) } } diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp index b2b0ec2a54f8..918e7af12d31 100644 --- a/libs/androidfw/ApkAssets.cpp +++ b/libs/androidfw/ApkAssets.cpp @@ -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) { -std::unique_ptr<const ApkAssets> ApkAssets::LoadArsc(const std::string& path, - bool for_loader) { - return LoadArscImpl({} /*fd*/, path, for_loader ? PROPERTY_LOADER : 0U); + 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(unique_fd fd, - const std::string& friendly_name, - bool for_loader) { - return LoadArscImpl(std::move(fd), friendly_name, for_loader ? PROPERTY_LOADER : 0U); +std::unique_ptr<const ApkAssets> ApkAssets::LoadEmpty( + const package_property_t flags, std::unique_ptr<const AssetsProvider> override_asset) { + + auto assets = (override_asset) ? std::move(override_asset) + : std::unique_ptr<const AssetsProvider>(new EmptyAssetsProvider()); + std::unique_ptr<ApkAssets> loaded_apk(new ApkAssets(std::move(assets), "empty" /* path */, + -1 /* last_mod-time */, flags)); + loaded_apk->loaded_arsc_ = LoadedArsc::CreateEmpty(); + // Need to force a move for mingw32. + return std::move(loaded_apk); } 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()); + + // 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); - time_t last_mod_time = getFileModDate(path.c_str()); + 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); - } - - if (resources_asset == nullptr) { - LOG(ERROR) << "Failed to open ARSC '" << path; - return {}; - } + const time_t last_mod_time = getFileModDate(path.c_str()); - 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; } diff --git a/libs/androidfw/Asset.cpp b/libs/androidfw/Asset.cpp index c132f343713f..cd30c184d5a4 100644 --- a/libs/androidfw/Asset.cpp +++ b/libs/androidfw/Asset.cpp @@ -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; diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp index 32086625a726..f20e18453f8b 100644 --- a/libs/androidfw/AssetManager2.cpp +++ b/libs/androidfw/AssetManager2.cpp @@ -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, diff --git a/libs/androidfw/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp index e35c0249fbdf..70bb441f94cb 100644 --- a/libs/androidfw/LoadedArsc.cpp +++ b/libs/androidfw/LoadedArsc.cpp @@ -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. diff --git a/libs/androidfw/include/androidfw/ApkAssets.h b/libs/androidfw/include/androidfw/ApkAssets.h index af802b0e50b9..879b050b65bd 100644 --- a/libs/androidfw/include/androidfw/ApkAssets.h +++ b/libs/androidfw/include/androidfw/ApkAssets.h @@ -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); + 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, 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 the given file descriptor, and takes ownership of the file + // descriptor. The `friendly_name` is some name that will be used to identify the source of + // this ApkAssets in log messages and other debug scenarios. + // If `length` equals kUnknownLength, offset must equal 0; otherwise, the apk data will be read + // using the `offset` into the file descriptor and will be `length` bytes long. + static std::unique_ptr<const ApkAssets> LoadFromFd( + base::unique_fd fd, const std::string& friendly_name, package_property_t flags = 0U, + std::unique_ptr<const AssetsProvider> override_asset = nullptr, off64_t offset = 0, + off64_t length = kUnknownLength); + + // Creates an ApkAssets from the given path which points to a resources.arsc. + static std::unique_ptr<const ApkAssets> LoadTable( + const std::string& path, package_property_t flags = 0U, + std::unique_ptr<const AssetsProvider> override_asset = nullptr); + + // Creates an ApkAssets from the given file descriptor which points to an resources.arsc, and + // takes ownership of the file descriptor. + // If `length` equals kUnknownLength, offset must equal 0; otherwise, the .arsc data will be read + // using the `offset` into the file descriptor and will be `length` bytes long. + static std::unique_ptr<const ApkAssets> LoadTableFromFd( + base::unique_fd fd, const std::string& friendly_name, package_property_t flags = 0U, + 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. - // 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); + package_property_t flags = 0U); - // 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); - - // 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 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 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; diff --git a/libs/androidfw/include/androidfw/Asset.h b/libs/androidfw/include/androidfw/Asset.h index 053dbb7864c6..298509eb37a1 100644 --- a/libs/androidfw/include/androidfw/Asset.h +++ b/libs/androidfw/include/androidfw/Asset.h @@ -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 diff --git a/libs/androidfw/include/androidfw/LoadedArsc.h b/libs/androidfw/include/androidfw/LoadedArsc.h index b5d3a1fc6c1f..89ff9f52125d 100644 --- a/libs/androidfw/include/androidfw/LoadedArsc.h +++ b/libs/androidfw/include/androidfw/LoadedArsc.h @@ -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 diff --git a/libs/androidfw/tests/ApkAssets_test.cpp b/libs/androidfw/tests/ApkAssets_test.cpp index 0f2ee6fb968e..19db25ce8811 100644 --- a/libs/androidfw/tests/ApkAssets_test.cpp +++ b/libs/androidfw/tests/ApkAssets_test.cpp @@ -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; diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp index 35fea7ab86cb..ac32699c6dfd 100644 --- a/libs/androidfw/tests/AssetManager2_test.cpp +++ b/libs/androidfw/tests/AssetManager2_test.cpp @@ -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"); diff --git a/libs/androidfw/tests/AttributeResolution_test.cpp b/libs/androidfw/tests/AttributeResolution_test.cpp index c8dbe205fee2..24361b5817f4 100644 --- a/libs/androidfw/tests/AttributeResolution_test.cpp +++ b/libs/androidfw/tests/AttributeResolution_test.cpp @@ -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()}); diff --git a/libs/androidfw/tests/Idmap_test.cpp b/libs/androidfw/tests/Idmap_test.cpp index b679672ab34e..41ba637da5d7 100644 --- a/libs/androidfw/tests/Idmap_test.cpp +++ b/libs/androidfw/tests/Idmap_test.cpp @@ -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(), diff --git a/libs/androidfw/tests/Theme_test.cpp b/libs/androidfw/tests/Theme_test.cpp index be5ecd94a588..16b9c75982fb 100644 --- a/libs/androidfw/tests/Theme_test.cpp +++ b/libs/androidfw/tests/Theme_test.cpp @@ -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"); diff --git a/startop/view_compiler/apk_layout_compiler.cc b/startop/view_compiler/apk_layout_compiler.cc index 09cdbd5fee58..eaa3e04cc814 100644 --- a/startop/view_compiler/apk_layout_compiler.cc +++ b/startop/view_compiler/apk_layout_compiler.cc @@ -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); } |