diff --git a/api/current.txt b/api/current.txt index db721f5f4a3cd..59d847e83232d 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 9f5dee98acb7d..495bb6b437185 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 newLoader) { + public void onLoadersChanged(@NonNull Resources resources, + @NonNull List 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 updatedResourceImplKeys = new ArrayMap<>(); diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 18f13431c09a3..20ddba41bda1d 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 4c6da03b4db30..27399e4b39bc6 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 8db278579cc1e..bc418061e1d11 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,59 +124,77 @@ public final class ApkAssets { * @throws IOException if a disk I/O error or parsing error occurred. */ public static @NonNull ApkAssets loadFromPath(@NonNull String path) throws IOException { - return new ApkAssets(path, false /*system*/, false /*forceSharedLib*/, false /*overlay*/, - false /*arscOnly*/, false /*forLoader*/); + return loadFromPath(path, 0 /* flags */); } /** * Creates a new ApkAssets instance from the given path on disk. * * @param path The path to an APK on disk. - * @param system When true, the APK is loaded as a system APK (framework). + * @param flags flags that change the behavior of loaded apk assets * @return a new instance of ApkAssets. * @throws IOException if a disk I/O error or parsing error occurred. */ - public static @NonNull ApkAssets loadFromPath(@NonNull String path, boolean system) + public static @NonNull ApkAssets loadFromPath(@NonNull String path, @PropertyFlags int flags) throws IOException { - return new ApkAssets(path, system, false /*forceSharedLib*/, false /*overlay*/, - false /*arscOnly*/, false /*forLoader*/); + return new ApkAssets(FORMAT_APK, path, flags, null /* assets */); } /** * Creates a new ApkAssets instance from the given path on disk. * * @param path The path to an APK on disk. - * @param system When true, the APK is loaded as a system APK (framework). - * @param forceSharedLibrary When true, any packages within the APK with package ID 0x7f are - * loaded as a shared library. + * @param flags flags that change the behavior of loaded apk assets + * @param assets The assets provider that overrides the loading of file-based resources * @return a new instance of ApkAssets. * @throws IOException if a disk I/O error or parsing error occurred. */ - public static @NonNull ApkAssets loadFromPath(@NonNull String path, boolean system, - boolean forceSharedLibrary) throws IOException { - return new ApkAssets(path, system, forceSharedLibrary, false /*overlay*/, - false /*arscOnly*/, false /*forLoader*/); + public static @NonNull ApkAssets loadFromPath(@NonNull String path, @PropertyFlags int flags, + @Nullable AssetsProvider assets) throws IOException { + return new ApkAssets(FORMAT_APK, path, flags, assets); } /** - * Creates a new ApkAssets instance from the given file descriptor. Not for use by applications. + * Creates a new ApkAssets instance from the given file descriptor. * * Performs a dup of the underlying fd, so you must take care of still closing * the FileDescriptor yourself (and can do that whenever you want). * * @param fd The FileDescriptor of an open, readable APK. * @param friendlyName The friendly name used to identify this ApkAssets when logging. - * @param system When true, the APK is loaded as a system APK (framework). - * @param forceSharedLibrary When true, any packages within the APK with package ID 0x7f are - * loaded as a shared library. + * @param flags flags that change the behavior of loaded apk assets + * @param assets The assets provider that overrides the loading of file-based resources * @return a new instance of ApkAssets. * @throws IOException if a disk I/O error or parsing error occurred. */ public static @NonNull ApkAssets loadFromFd(@NonNull FileDescriptor fd, - @NonNull String friendlyName, boolean system, boolean forceSharedLibrary) + @NonNull String friendlyName, @PropertyFlags int flags, + @Nullable AssetsProvider assets) throws IOException { + return new ApkAssets(FORMAT_APK, fd, friendlyName, flags, assets); + } + + /** + * Creates a new ApkAssets instance from the given file descriptor. + * + * Performs a dup of the underlying fd, so you must take care of still closing + * the FileDescriptor yourself (and can do that whenever you want). + * + * @param fd The FileDescriptor of an open, readable APK. + * @param friendlyName The friendly name used to identify this ApkAssets when logging. + * @param offset The location within the file that the apk starts. This must be 0 if length is + * {@link AssetFileDescriptor#UNKNOWN_LENGTH}. + * @param length The number of bytes of the apk, or {@link AssetFileDescriptor#UNKNOWN_LENGTH} + * if it extends to the end of the file. + * @param flags flags that change the behavior of loaded apk assets + * @param assets The assets provider that overrides the loading of file-based resources + * @return a new instance of ApkAssets. + * @throws IOException if a disk I/O error or parsing error occurred. + */ + public static @NonNull ApkAssets loadFromFd(@NonNull FileDescriptor fd, + @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags, + @Nullable AssetsProvider assets) throws IOException { - return new ApkAssets(fd, friendlyName, system, forceSharedLibrary, false /*arscOnly*/, - false /*forLoader*/); + return new ApkAssets(FORMAT_APK, fd, friendlyName, offset, length, flags, assets); } /** @@ -116,97 +202,124 @@ public final class ApkAssets { * is encoded within the IDMAP. * * @param idmapPath Path to the IDMAP of an overlay APK. - * @param system When true, the APK is loaded as a system APK (framework). + * @param flags flags that change the behavior of loaded apk assets * @return a new instance of ApkAssets. * @throws IOException if a disk I/O error or parsing error occurred. */ - public static @NonNull ApkAssets loadOverlayFromPath(@NonNull String idmapPath, boolean system) - throws IOException { - return new ApkAssets(idmapPath, system, false /*forceSharedLibrary*/, true /*overlay*/, - false /*arscOnly*/, false /*forLoader*/); + public static @NonNull ApkAssets loadOverlayFromPath(@NonNull String idmapPath, + @PropertyFlags int flags) throws IOException { + return new ApkAssets(FORMAT_IDMAP, idmapPath, flags, null /* assets */); } /** - * Creates a new ApkAssets instance from the given path on disk for use with a - * {@link ResourcesProvider}. - * - * @param path The path to an APK on disk. - * @return a new instance of ApkAssets. - * @throws IOException if a disk I/O error or parsing error occurred. - */ - public static @NonNull ApkAssets loadApkForLoader(@NonNull String path) - throws IOException { - return new ApkAssets(path, false /*system*/, false /*forceSharedLibrary*/, - false /*overlay*/, false /*arscOnly*/, true /*forLoader*/); - } - - /** - * Creates a new ApkAssets instance from the given file descriptor for use with a - * {@link ResourcesProvider}. - * - * Performs a dup of the underlying fd, so you must take care of still closing - * the FileDescriptor yourself (and can do that whenever you want). - * - * @param fd The FileDescriptor of an open, readable APK. - * @return a new instance of ApkAssets. - * @throws IOException if a disk I/O error or parsing error occurred. - */ - @NonNull - public static ApkAssets loadApkForLoader(@NonNull FileDescriptor fd) throws IOException { - return new ApkAssets(fd, TextUtils.emptyIfNull(fd.toString()), - false /*system*/, false /*forceSharedLib*/, false /*arscOnly*/, true /*forLoader*/); - } - - /** - * Creates a new ApkAssets instance from the given file descriptor representing an ARSC + * Creates a new ApkAssets instance from the given file descriptor representing a resources.arsc * for use with a {@link ResourcesProvider}. * * Performs a dup of the underlying fd, so you must take care of still closing * the FileDescriptor yourself (and can do that whenever you want). * - * @param fd The FileDescriptor of an open, readable .arsc. + * @param fd The FileDescriptor of an open, readable resources.arsc. + * @param friendlyName The friendly name used to identify this ApkAssets when logging. + * @param flags flags that change the behavior of loaded apk assets + * @param assets The assets provider that overrides the loading of file-based resources * @return a new instance of ApkAssets. * @throws IOException if a disk I/O error or parsing error occurred. */ - public static @NonNull ApkAssets loadArscForLoader(@NonNull FileDescriptor fd) - throws IOException { - return new ApkAssets(fd, TextUtils.emptyIfNull(fd.toString()), - false /*system*/, false /*forceSharedLib*/, true /*arscOnly*/, true /*forLoader*/); + public static @NonNull ApkAssets loadTableFromFd(@NonNull FileDescriptor fd, + @NonNull String friendlyName, @PropertyFlags int flags, + @Nullable AssetsProvider assets) throws IOException { + return new ApkAssets(FORMAT_ARSC, fd, friendlyName, flags, assets); + } + + /** + * Creates a new ApkAssets instance from the given file descriptor representing a resources.arsc + * for use with a {@link ResourcesProvider}. + * + * Performs a dup of the underlying fd, so you must take care of still closing + * the FileDescriptor yourself (and can do that whenever you want). + * + * @param fd The FileDescriptor of an open, readable resources.arsc. + * @param friendlyName The friendly name used to identify this ApkAssets when logging. + * @param offset The location within the file that the table starts. This must be 0 if length is + * {@link AssetFileDescriptor#UNKNOWN_LENGTH}. + * @param length The number of bytes of the table, or {@link AssetFileDescriptor#UNKNOWN_LENGTH} + * if it extends to the end of the file. + * @param flags flags that change the behavior of loaded apk assets + * @param assets The assets provider that overrides the loading of file-based resources + * @return a new instance of ApkAssets. + * @throws IOException if a disk I/O error or parsing error occurred. + */ + public static @NonNull ApkAssets loadTableFromFd(@NonNull FileDescriptor fd, + @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags, + @Nullable AssetsProvider assets) throws IOException { + return new ApkAssets(FORMAT_ARSC, fd, friendlyName, offset, length, flags, assets); + } + + /** + * Creates a new ApkAssets instance from the given directory path. The directory should have the + * file structure of an APK. + * + * @param path The path to a directory on disk. + * @param flags flags that change the behavior of loaded apk assets + * @param assets The assets provider that overrides the loading of file-based resources + * @return a new instance of ApkAssets. + * @throws IOException if a disk I/O error or parsing error occurred. + */ + public static @NonNull ApkAssets loadFromDir(@NonNull String path, + @PropertyFlags int flags, @Nullable AssetsProvider assets) throws IOException { + return new ApkAssets(FORMAT_DIR, path, flags, assets); } /** * Generates an entirely empty ApkAssets. Needed because the ApkAssets instance and presence * is required for a lot of APIs, and it's easier to have a non-null reference rather than * tracking a separate identifier. + * + * @param flags flags that change the behavior of loaded apk assets + * @param assets The assets provider that overrides the loading of file-based resources */ @NonNull - public static ApkAssets loadEmptyForLoader() { - return new ApkAssets(true); + public static ApkAssets loadEmptyForLoader(@PropertyFlags int flags, + @Nullable AssetsProvider assets) { + return new ApkAssets(flags, assets); } - private ApkAssets(boolean forLoader) { - mForLoader = forLoader; - mNativePtr = nativeLoadEmpty(forLoader); - mStringBlock = null; - } - - private ApkAssets(@NonNull String path, boolean system, boolean forceSharedLib, boolean overlay, - boolean arscOnly, boolean forLoader) throws IOException { - mForLoader = forLoader; + private ApkAssets(@FormatType int format, @NonNull String path, @PropertyFlags int flags, + @Nullable AssetsProvider assets) throws IOException { Objects.requireNonNull(path, "path"); - mNativePtr = arscOnly ? nativeLoadArsc(path, forLoader) - : nativeLoad(path, system, forceSharedLib, overlay, forLoader); + mFlags = flags; + mNativePtr = nativeLoad(format, path, flags, assets); mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/); + mAssets = assets; } - private ApkAssets(@NonNull FileDescriptor fd, @NonNull String friendlyName, boolean system, - boolean forceSharedLib, boolean arscOnly, boolean forLoader) throws IOException { - mForLoader = forLoader; + private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd, + @NonNull String friendlyName, @PropertyFlags int flags, @Nullable AssetsProvider assets) + throws IOException { Objects.requireNonNull(fd, "fd"); Objects.requireNonNull(friendlyName, "friendlyName"); - mNativePtr = arscOnly ? nativeLoadArscFromFd(fd, friendlyName, forLoader) - : nativeLoadFromFd(fd, friendlyName, system, forceSharedLib, forLoader); + mFlags = flags; + mNativePtr = nativeLoadFd(format, fd, friendlyName, flags, assets); mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/); + mAssets = assets; + } + + private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd, + @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags, + @Nullable AssetsProvider assets) throws IOException { + Objects.requireNonNull(fd, "fd"); + Objects.requireNonNull(friendlyName, "friendlyName"); + mFlags = flags; + mNativePtr = nativeLoadFdOffsets(format, fd, friendlyName, offset, length, flags, assets); + mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/); + mAssets = assets; + } + + private ApkAssets(@PropertyFlags int flags, @Nullable AssetsProvider assets) { + mFlags = flags; + mNativePtr = nativeLoadEmpty(flags, assets); + mStringBlock = null; + mAssets = assets; } @UnsupportedAppUsage @@ -226,8 +339,17 @@ public final class ApkAssets { } } + /** Returns whether this apk assets was loaded using a {@link ResourcesProvider}. */ public boolean isForLoader() { - return mForLoader; + return (mFlags & PROPERTY_LOADER) != 0; + } + + /** + * Returns the assets provider that overrides the loading of assets present in this apk assets. + */ + @Nullable + public AssetsProvider getAssetsProvider() { + return mAssets; } /** @@ -300,18 +422,16 @@ public final class ApkAssets { } } - private static native long nativeLoad(@NonNull String path, boolean system, - boolean forceSharedLib, boolean overlay, boolean forLoader) - throws IOException; - private static native long nativeLoadFromFd(@NonNull FileDescriptor fd, - @NonNull String friendlyName, boolean system, boolean forceSharedLib, - boolean forLoader) - throws IOException; - private static native long nativeLoadArsc(@NonNull String path, boolean forLoader) - throws IOException; - private static native long nativeLoadArscFromFd(@NonNull FileDescriptor fd, - @NonNull String friendlyName, boolean forLoader) throws IOException; - private static native long nativeLoadEmpty(boolean forLoader); + private static native long nativeLoad(@FormatType int format, @NonNull String path, + @PropertyFlags int flags, @Nullable AssetsProvider asset) throws IOException; + private static native long nativeLoadEmpty(@PropertyFlags int flags, + @Nullable AssetsProvider asset); + private static native long nativeLoadFd(@FormatType int format, @NonNull FileDescriptor fd, + @NonNull String friendlyName, @PropertyFlags int flags, + @Nullable AssetsProvider asset) throws IOException; + private static native long nativeLoadFdOffsets(@FormatType int format, + @NonNull FileDescriptor fd, @NonNull String friendlyName, long offset, long length, + @PropertyFlags int flags, @Nullable AssetsProvider asset) throws IOException; private static native void nativeDestroy(long ptr); private static native @NonNull String nativeGetAssetPath(long ptr); private static native long nativeGetStringBlock(long ptr); diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java index f295f8c531e90..d2103af1d247b 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 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 = new ArrayList<>(); - apkAssets.add(ApkAssets.loadFromPath(frameworkPath, true /*system*/)); + apkAssets.add(ApkAssets.loadFromPath(frameworkPath, ApkAssets.PROPERTY_SYSTEM)); final String[] systemIdmapPaths = OverlayConfig.getZygoteInstance().createImmutableFrameworkIdmapsInZygote(); for (String idmapPath : systemIdmapPaths) { - apkAssets.add(ApkAssets.loadOverlayFromPath(idmapPath, true /*system*/)); + apkAssets.add(ApkAssets.loadOverlayFromPath(idmapPath, ApkAssets.PROPERTY_SYSTEM)); } sSystemApkAssetsSet = new ArraySet<>(apkAssets); @@ -331,6 +325,42 @@ public final class AssetManager implements AutoCloseable { } } + /** + * Changes the {@link ResourcesLoader ResourcesLoaders} used in this AssetManager. + * @hide + */ + void setLoaders(@NonNull List newLoaders) { + Objects.requireNonNull(newLoaders, "newLoaders"); + + final ArrayList 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 uniqueLoaderApkAssets = new ArraySet<>(); + for (int i = newLoaders.size() - 1; i >= 0; i--) { + final List currentLoaderApkAssets = newLoaders.get(i).getApkAssets(); + for (int j = currentLoaderApkAssets.size() - 1; j >= 0; j--) { + final ApkAssets loaderApkAssets = currentLoaderApkAssets.get(j); + if (uniqueLoaderApkAssets.add(loaderApkAssets)) { + apkAssets.add(loaderStartIndex, loaderApkAssets); + } + } + } + } + + mLoaders = newLoaders.toArray(new ResourcesLoader[0]); + setApkAssets(apkAssets.toArray(new ApkAssets[0]), true /* invalidate_caches */); + } + /** * Invalidates the caches in this AssetManager according to the bitmask `diff`. * @@ -443,9 +473,10 @@ public final class AssetManager implements AutoCloseable { final String idmapPath = "/data/resource-cache/" + path.substring(1).replace('/', '@') + "@idmap"; - assets = ApkAssets.loadOverlayFromPath(idmapPath, false /*system*/); + assets = ApkAssets.loadOverlayFromPath(idmapPath, 0 /* flags */); } else { - assets = ApkAssets.loadFromPath(path, false /*system*/, appAsLib); + assets = ApkAssets.loadFromPath(path, + appAsLib ? ApkAssets.PROPERTY_DYNAMIC : 0); } } catch (IOException e) { return 0; @@ -827,13 +858,6 @@ public final class AssetManager implements AutoCloseable { Objects.requireNonNull(fileName, "fileName"); synchronized (this) { ensureOpenLocked(); - - String path = Paths.get("assets", fileName).toString(); - InputStream inputStream = searchLoaders(0, path, accessMode); - if (inputStream != null) { - return inputStream; - } - final long asset = nativeOpenAsset(mObject, fileName, accessMode); if (asset == 0) { throw new FileNotFoundException("Asset file: " + fileName); @@ -858,13 +882,6 @@ public final class AssetManager implements AutoCloseable { Objects.requireNonNull(fileName, "fileName"); synchronized (this) { ensureOpenLocked(); - - String path = Paths.get("assets", fileName).toString(); - AssetFileDescriptor fileDescriptor = searchLoadersFd(0, path); - if (fileDescriptor != null) { - return fileDescriptor; - } - final ParcelFileDescriptor pfd = nativeOpenAssetFd(mObject, fileName, mOffsets); if (pfd == null) { throw new FileNotFoundException("Asset file: " + fileName); @@ -958,12 +975,6 @@ public final class AssetManager implements AutoCloseable { Objects.requireNonNull(fileName, "fileName"); synchronized (this) { ensureOpenLocked(); - - InputStream inputStream = searchLoaders(cookie, fileName, accessMode); - if (inputStream != null) { - return inputStream; - } - final long asset = nativeOpenNonAsset(mObject, cookie, fileName, accessMode); if (asset == 0) { throw new FileNotFoundException("Asset absolute file: " + fileName); @@ -1003,12 +1014,6 @@ public final class AssetManager implements AutoCloseable { Objects.requireNonNull(fileName, "fileName"); synchronized (this) { ensureOpenLocked(); - - AssetFileDescriptor fileDescriptor = searchLoadersFd(cookie, fileName); - if (fileDescriptor != null) { - return fileDescriptor; - } - final ParcelFileDescriptor pfd = nativeOpenNonAssetFd(mObject, cookie, fileName, mOffsets); if (pfd == null) { @@ -1071,15 +1076,7 @@ public final class AssetManager implements AutoCloseable { synchronized (this) { ensureOpenLocked(); - final long xmlBlock; - AssetFileDescriptor fileDescriptor = searchLoadersFd(cookie, fileName); - if (fileDescriptor != null) { - xmlBlock = nativeOpenXmlAssetFd(mObject, cookie, - fileDescriptor.getFileDescriptor()); - } else { - xmlBlock = nativeOpenXmlAsset(mObject, cookie, fileName); - } - + final long xmlBlock = nativeOpenXmlAsset(mObject, cookie, fileName); if (xmlBlock == 0) { throw new FileNotFoundException("Asset XML file: " + fileName); } @@ -1089,122 +1086,6 @@ public final class AssetManager implements AutoCloseable { } } - private ResourcesProvider findResourcesProvider(int assetCookie) { - if (mLoaders == null) { - return null; - } - - int apkAssetsIndex = assetCookie - 1; - if (apkAssetsIndex >= mApkAssets.length || apkAssetsIndex < 0) { - return null; - } - - final ApkAssets apkAssets = mApkAssets[apkAssetsIndex]; - if (!apkAssets.isForLoader()) { - return null; - } - - for (int i = mLoaders.length - 1; i >= 0; i--) { - final ResourcesLoader loader = mLoaders[i]; - for (int j = 0, n = loader.getProviders().size(); j < n; j++) { - final ResourcesProvider provider = loader.getProviders().get(j); - if (apkAssets == provider.getApkAssets()) { - return provider; - } - } - } - - return null; - } - - private InputStream searchLoaders(int cookie, @NonNull String fileName, int accessMode) - throws IOException { - if (mLoaders == null) { - return null; - } - - if (cookie == 0) { - // A cookie of 0 means no specific ApkAssets, so search everything - for (int i = mLoaders.length - 1; i >= 0; i--) { - final ResourcesLoader loader = mLoaders[i]; - final List 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 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 cb809da3b8679..d6a9f6990abee 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 newLoaders); + void onLoadersChanged(@NonNull Resources resources, + @NonNull List 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 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. * + *

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. * + *

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)}. + * + *

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 c315494cf7281..0f8f1d1da8d24 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. * + *

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. + * *

There are two situations in which this method will be called: *

    *
  • 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 81c2a4c1b4d6b..0000000000000 --- 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 58fec603a2d5d..c3084003c3046 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; * *

    Providers retrieved with {@link #getProviders()} are listed in increasing precedence order. A * provider will override the resources and assets of providers listed before itself. + * + *

    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. * + *

    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. * + *

    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. * + *

    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 resourcesProviders) { @@ -124,7 +138,12 @@ public class ResourcesLoader { } } - /** Removes all {@link ResourcesProvider ResourcesProvider(s)}. */ + /** + * Removes all {@link ResourcesProvider ResourcesProvider(s)}. + * + *

    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 419ec7882f3d7..0a698d18682b9 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,73 +50,111 @@ public class ResourcesProvider implements AutoCloseable, Closeable { @GuardedBy("mLock") private final ApkAssets mApkAssets; - private final AssetsProvider mAssetsProvider; - /** - * Creates an empty ResourcesProvider with no resource data. This is useful for loading assets - * that are not associated with resource identifiers. + * Creates an empty ResourcesProvider with no resource data. This is useful for loading + * file-based assets not associated with resource identifiers. * - * @param assetsProvider the assets provider that overrides the loading of file-based resources + * @param assetsProvider the assets provider that implements the loading of file-based resources */ @NonNull public static ResourcesProvider empty(@NonNull AssetsProvider assetsProvider) { - return new ResourcesProvider(ApkAssets.loadEmptyForLoader(), assetsProvider); + return new ResourcesProvider(ApkAssets.loadEmptyForLoader(ApkAssets.PROPERTY_LOADER, + assetsProvider)); } /** * Creates a ResourcesProvider from an APK ({@code .apk}) file descriptor. * - * The file descriptor is duplicated and the original may be closed by the application at any + *

    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 + *

    The file descriptor is duplicated and the original may be closed by the application at any * time without affecting the ResourcesProvider. * + *

    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 + *

    The file descriptor is duplicated and the original may be closed by the application at any + * time without affecting the ResourcesProvider. + * + *

    The assets provider can override the loading of files within the APK and can provide + * entirely new files that do not exist in the APK. + * + * @param fileDescriptor the file descriptor of the APK to load + * @param offset The location within the file that the apk starts. This must be 0 if length is + * {@link AssetFileDescriptor#UNKNOWN_LENGTH}. + * @param length The number of bytes of the apk, or {@link AssetFileDescriptor#UNKNOWN_LENGTH} + * if it extends to the end of the file. + * @param assetsProvider the assets provider that overrides the loading of file-based resources + * + * @see ParcelFileDescriptor#open(File, int) + * @see android.system.Os#memfd_create(String, int) + * @hide */ + @VisibleForTesting @NonNull - public static ResourcesProvider loadFromApk(@NonNull SharedMemory sharedMemory) + public static ResourcesProvider loadFromApk(@NonNull ParcelFileDescriptor fileDescriptor, + long offset, long length, @Nullable AssetsProvider assetsProvider) throws IOException { - return loadFromApk(sharedMemory, null); + return new ResourcesProvider(ApkAssets.loadFromFd(fileDescriptor.getFileDescriptor(), + fileDescriptor.toString(), offset, length, ApkAssets.PROPERTY_LOADER, + assetsProvider)); } /** - * Creates a ResourcesProvider from an {@code .apk} file representation in memory. + * Creates a ResourcesProvider from a resources table ({@code .arsc}) file descriptor. * - * @param sharedMemory the shared memory containing the data of the APK to load + *

    The file descriptor is duplicated and the original may be closed by the application at any + * time without affecting the ResourcesProvider. + * + *

    The resources table format is not an archive format and therefore cannot asset files + * within itself. The assets provider can instead provide files that are potentially referenced + * by path in the resources table. + * + * @param fileDescriptor the file descriptor of the resources table to load * @param assetsProvider the assets provider that implements the loading of file-based resources + * + * @see ParcelFileDescriptor#open(File, int) + * @see android.system.Os#memfd_create(String, int) */ @NonNull - public static ResourcesProvider loadFromApk(@NonNull SharedMemory sharedMemory, + public static ResourcesProvider loadFromTable(@NonNull ParcelFileDescriptor fileDescriptor, @Nullable AssetsProvider assetsProvider) throws IOException { return new ResourcesProvider( - ApkAssets.loadApkForLoader(sharedMemory.getFileDescriptor()), assetsProvider); + ApkAssets.loadTableFromFd(fileDescriptor.getFileDescriptor(), + fileDescriptor.toString(), ApkAssets.PROPERTY_LOADER, assetsProvider)); } /** @@ -123,30 +163,30 @@ public class ResourcesProvider implements AutoCloseable, Closeable { * The file descriptor is duplicated and the original may be closed by the application at any * time without affecting the ResourcesProvider. * + *

    The resources table format is not an archive format and therefore cannot asset files + * within itself. The assets provider can instead provide files that are potentially referenced + * by path in the resources table. + * * @param fileDescriptor the file descriptor of the resources table to load - * @param assetsProvider the assets provider that implements the loading of file-based resources + * @param offset The location within the file that the table starts. This must be 0 if length is + * {@link AssetFileDescriptor#UNKNOWN_LENGTH}. + * @param length The number of bytes of the table, or {@link AssetFileDescriptor#UNKNOWN_LENGTH} + * if it extends to the end of the file. + * @param assetsProvider the assets provider that overrides the loading of file-based resources + * + * @see ParcelFileDescriptor#open(File, int) + * @see android.system.Os#memfd_create(String, int) + * @hide */ + @VisibleForTesting @NonNull public static ResourcesProvider loadFromTable(@NonNull ParcelFileDescriptor fileDescriptor, - @Nullable AssetsProvider assetsProvider) + long offset, long length, @Nullable AssetsProvider assetsProvider) throws IOException { return new ResourcesProvider( - ApkAssets.loadArscForLoader(fileDescriptor.getFileDescriptor()), assetsProvider); - } - - /** - * Creates a ResourcesProvider from a resources table ({@code .arsc}) file representation in - * memory. - * - * @param sharedMemory the shared memory containing the data of the resources table to load - * @param assetsProvider the assets provider that overrides the loading of file-based resources - */ - @NonNull - public static ResourcesProvider loadFromTable(@NonNull SharedMemory sharedMemory, - @Nullable AssetsProvider assetsProvider) - throws IOException { - return new ResourcesProvider( - ApkAssets.loadArscForLoader(sharedMemory.getFileDescriptor()), assetsProvider); + ApkAssets.loadTableFromFd(fileDescriptor.getFileDescriptor(), + fileDescriptor.toString(), offset, length, ApkAssets.PROPERTY_LOADER, + assetsProvider)); } /** @@ -166,18 +206,28 @@ public class ResourcesProvider implements AutoCloseable, Closeable { } String splitPath = appInfo.getSplitCodePaths()[splitIndex]; - return new ResourcesProvider(ApkAssets.loadApkForLoader(splitPath), null); + return new ResourcesProvider(ApkAssets.loadFromPath(splitPath, ApkAssets.PROPERTY_LOADER, + null /* assetsProvider */)); } - private ResourcesProvider(@NonNull ApkAssets apkAssets, - @Nullable AssetsProvider assetsProvider) { + /** + * Creates a ResourcesProvider from a directory path. + * + * File-based resources will be resolved within the directory as if the directory is an APK. + * + * @param path the path of the directory to treat as an APK + * @param assetsProvider the assets provider that overrides the loading of file-based resources + */ + @NonNull + public static ResourcesProvider loadFromDirectory(@NonNull String path, + @Nullable AssetsProvider assetsProvider) throws IOException { + return new ResourcesProvider(ApkAssets.loadFromDir(path, ApkAssets.PROPERTY_LOADER, + assetsProvider)); + } + + + private ResourcesProvider(@NonNull ApkAssets apkAssets) { this.mApkAssets = apkAssets; - this.mAssetsProvider = assetsProvider; - } - - @Nullable - public AssetsProvider getAssetsProvider() { - return mAssetsProvider; } /** @hide */ diff --git a/core/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp index b55dc68af5589..fbdd4060d7f26 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 Create(JNIEnv* env, jobject assets_provider) { + return (!assets_provider) ? nullptr + : std::unique_ptr(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 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(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(mOffset), + static_cast(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 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(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,48 +217,56 @@ static jlong NativeLoadFromFd(JNIEnv* env, jclass /*clazz*/, jobject file_descri return 0; } - std::unique_ptr 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 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.get()); + friendly_name_utf8.c_str(), fd); jniThrowException(env, "java/io/IOException", error_msg.c_str()); return 0; } return reinterpret_cast(apk_assets.release()); } -static jlong NativeLoadArsc(JNIEnv* env, jclass /*clazz*/, jstring java_path, - jboolean for_loader) { - ScopedUtfChars path(env, java_path); - if (path.c_str() == nullptr) { - return 0; - } - - ATRACE_NAME(base::StringPrintf("LoadApkAssetsArsc(%s)", path.c_str()).c_str()); - - std::unique_ptr apk_assets = ApkAssets::LoadArsc(path.c_str(), for_loader); - - if (apk_assets == nullptr) { - std::string error_msg = base::StringPrintf("Failed to load asset path %s", path.c_str()); - jniThrowException(env, "java/io/IOException", error_msg.c_str()); - return 0; - } - return reinterpret_cast(apk_assets.release()); -} - -static jlong NativeLoadArscFromFd(JNIEnv* env, jclass /*clazz*/, jobject file_descriptor, - jstring friendly_name, jboolean for_loader) { +static jlong NativeLoadFromFdOffset(JNIEnv* env, jclass /*clazz*/, const format_type_t format, + jobject file_descriptor, jstring friendly_name, + const jlong offset, const jlong length, + const jint property_flags, jobject assets_provider) { ScopedUtfChars friendly_name_utf8(env, friendly_name); if (friendly_name_utf8.c_str() == nullptr) { return 0; } + ATRACE_NAME(base::StringPrintf("LoadApkAssetsFd(%s)", friendly_name_utf8.c_str()).c_str()); + + if (offset < 0) { + jniThrowException(env, "java/lang/IllegalArgumentException", + "offset cannot be negative"); + return 0; + } + + if (length < 0) { + jniThrowException(env, "java/lang/IllegalArgumentException", + "length cannot be negative"); + return 0; + } + int fd = jniGetFDFromFileDescriptor(env, file_descriptor); - ATRACE_NAME(base::StringPrintf("LoadApkAssetsArscFd(%d)", fd).c_str()); if (fd < 0) { jniThrowException(env, "java/lang/IllegalArgumentException", "Bad FileDescriptor"); return 0; @@ -138,18 +278,39 @@ static jlong NativeLoadArscFromFd(JNIEnv* env, jclass /*clazz*/, jobject file_de return 0; } - std::unique_ptr 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 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(offset), + static_cast(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(offset), + static_cast(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(apk_assets.release()); } -static jlong NativeLoadEmpty(JNIEnv* env, jclass /*clazz*/, jboolean for_loader) { - std::unique_ptr 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(apk_assets.release()); } @@ -179,8 +340,8 @@ static jlong NativeOpenXml(JNIEnv* env, jclass /*clazz*/, jlong ptr, jstring fil } const ApkAssets* apk_assets = reinterpret_cast(ptr); - std::unique_ptr asset = apk_assets->Open(path_utf8.c_str(), - Asset::AccessMode::ACCESS_RANDOM); + std::unique_ptr 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; @@ -251,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}, @@ -274,6 +437,21 @@ int register_android_content_res_ApkAssets(JNIEnv* env) { gOverlayableInfoOffsets.constructor = GetMethodIDOrDie(env, gOverlayableInfoOffsets.classObject, "", "(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 062b886f54a17..cb5a332c6e85f 100644 --- a/core/jni/android_util_AssetManager.cpp +++ b/core/jni/android_util_AssetManager.cpp @@ -75,12 +75,6 @@ static struct typedvalue_offsets_t { jfieldID mDensity; } gTypedValueOffsets; -static struct assetfiledescriptor_offsets_t { - jfieldID mFd; - jfieldID mStartOffset; - jfieldID mLength; -} gAssetFileDescriptorOffsets; - // This is also used by asset_manager.cpp. assetmanager_offsets_t gAssetManagerOffsets; @@ -1596,12 +1590,6 @@ int register_android_content_AssetManager(JNIEnv* env) { GetFieldIDOrDie(env, typedValue, "changingConfigurations", "I"); gTypedValueOffsets.mDensity = GetFieldIDOrDie(env, typedValue, "density", "I"); - jclass assetFd = FindClassOrDie(env, "android/content/res/AssetFileDescriptor"); - gAssetFileDescriptorOffsets.mFd = - GetFieldIDOrDie(env, assetFd, "mFd", "Landroid/os/ParcelFileDescriptor;"); - gAssetFileDescriptorOffsets.mStartOffset = GetFieldIDOrDie(env, assetFd, "mStartOffset", "J"); - gAssetFileDescriptorOffsets.mLength = GetFieldIDOrDie(env, assetFd, "mLength", "J"); - jclass assetManager = FindClassOrDie(env, "android/content/res/AssetManager"); gAssetManagerOffsets.mObject = GetFieldIDOrDie(env, assetManager, "mObject", "J"); diff --git a/core/tests/ResourceLoaderTests/Android.bp b/core/tests/ResourceLoaderTests/Android.bp index fec4628106a5e..2b14bca1f7c12 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" ] + ":FrameworksResourceLoaderTests_ProviderOne_Split", + ":FrameworksResourceLoaderTests_ProviderTwo_Split", + ":FrameworksResourceLoaderTests_ProviderThree_Split", + ":FrameworksResourceLoaderTests_ProviderFour_Split" + ] } -filegroup { - name: "FrameworksResourceLoaderTestsResources", - srcs: ["resources"], -} - -genrule { - name: "FrameworksResourceLoaderTestsAssets", - srcs: [ - ":framework-res", - ":FrameworksResourceLoaderTestsResources", +java_genrule { + name: "FrameworksResourceLoaderTests_Providers", + tools: ["soong_zip"], + srcs : [ + ":FrameworksResourceLoaderTests_ProviderOne", + ":FrameworksResourceLoaderTests_ProviderOne_ARSC", + ":FrameworksResourceLoaderTests_ProviderTwo", + ":FrameworksResourceLoaderTests_ProviderTwo_ARSC", + ":FrameworksResourceLoaderTests_ProviderThree", + ":FrameworksResourceLoaderTests_ProviderThree_ARSC", + ":FrameworksResourceLoaderTests_ProviderFour", + ":FrameworksResourceLoaderTests_ProviderFour_ARSC" ], - tools: [ ":aapt2", ":soong_zip" ], - tool_files: [ "resources/compileAndLink.sh" ], - cmd: "$(location resources/compileAndLink.sh) $(location :aapt2) $(location :soong_zip) $(genDir) $(in) $(in)", - out: [ "out.zip" ] -} + out: ["FrameworksResourceLoaderTests_Providers.jar"], + cmd: "mkdir -p $(genDir)/assets/ && cp $(in) $(genDir)/assets/ && " + + "$(location soong_zip) -o $(out) " + + "-L 0 -C $(genDir) -D $(genDir)/assets/" +} \ No newline at end of file diff --git a/core/tests/ResourceLoaderTests/AndroidTest.xml b/core/tests/ResourceLoaderTests/AndroidTest.xml index d732132bef026..800e7a7124ac8 100644 --- a/core/tests/ResourceLoaderTests/AndroidTest.xml +++ b/core/tests/ResourceLoaderTests/AndroidTest.xml @@ -22,7 +22,7 @@