From e35e87f51453849f78a3be41a25e1257820eda5a Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Thu, 19 Mar 2020 16:57:50 -0700 Subject: [PATCH 1/7] Fix bugprone-use-after-move warnings Bug: 150783499 Test: WITH_TIDY=1 make Change-Id: I185cb21521676ddbc4f2b7f098611a2efc7275e6 (cherry picked from commit c658184d366ee08cedd9ec208ea327530b52bfad) Merged-In: I185cb21521676ddbc4f2b7f098611a2efc7275e6 --- libs/androidfw/ApkAssets.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp index 18934fd55bade..b2b0ec2a54f8e 100644 --- a/libs/androidfw/ApkAssets.cpp +++ b/libs/androidfw/ApkAssets.cpp @@ -77,7 +77,8 @@ std::unique_ptr ApkAssets::LoadOverlay(const std::string& idmap return {}; } - return LoadImpl({} /*fd*/, loaded_idmap->OverlayApkPath(), + auto apkPath = loaded_idmap->OverlayApkPath(); + return LoadImpl({} /*fd*/, apkPath, std::move(idmap_asset), std::move(loaded_idmap), PROPERTY_OVERLAY | (system ? PROPERTY_SYSTEM : 0U)); From ad2d924b3490c01168ad1dfeb2fa05ebc61b8ef2 Mon Sep 17 00:00:00 2001 From: Chih-Hung Hsieh Date: Fri, 6 Mar 2020 14:52:28 -0800 Subject: [PATCH 2/7] Fix bugprone-use-after-move warnings Bug: 150783499 Test: WITH_TIDY=1 make Change-Id: I745d8bfde00a77bcdbbeaa21cfe81af1c74a971c Merged-In: I745d8bfde00a77bcdbbeaa21cfe81af1c74a971c --- core/jni/android_content_res_ApkAssets.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp index b55dc68af5589..491d4a61cee4c 100644 --- a/core/jni/android_content_res_ApkAssets.cpp +++ b/core/jni/android_content_res_ApkAssets.cpp @@ -85,6 +85,7 @@ static jlong NativeLoadFromFd(JNIEnv* env, jclass /*clazz*/, jobject file_descri return 0; } + auto dup_fd_id = dup_fd.get(); std::unique_ptr apk_assets = ApkAssets::LoadFromFd(std::move(dup_fd), friendly_name_utf8.c_str(), system, force_shared_lib, @@ -92,7 +93,7 @@ static jlong NativeLoadFromFd(JNIEnv* env, jclass /*clazz*/, jobject file_descri 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(), dup_fd_id); jniThrowException(env, "java/io/IOException", error_msg.c_str()); return 0; } From ef40d2e832e900aba6dbcf8217903d7dbe689ca0 Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Wed, 11 Mar 2020 10:26:08 -0700 Subject: [PATCH 3/7] Refactor ApkAsset loading APIs To add the partner requested ResourcesProvider#loadFromDir APIs, this change adds format type integer that allows us to reduce the number of ApkAssets loading overrides. This change also adds hidden offset and length based ResourcesProvider APIs that could not make R. Bug: 142716192 Test: atest FrameworksResourceLoaderTests Change-Id: I926fde257cae701901dcd4ca408024feae8c90a6 Merged-In: I926fde257cae701901dcd4ca408024feae8c90a6 --- api/current.txt | 4 +- core/java/android/app/ResourcesManager.java | 5 +- .../android/content/pm/PackageParser.java | 3 +- .../content/pm/parsing/ApkLiteParseUtils.java | 2 +- core/java/android/content/res/ApkAssets.java | 271 +++++++++++------- .../android/content/res/AssetManager.java | 9 +- core/java/android/content/res/Resources.java | 9 + .../content/res/loader/ResourcesLoader.java | 22 +- .../content/res/loader/ResourcesProvider.java | 119 +++++--- core/jni/android_content_res_ApkAssets.cpp | 146 ++++++---- libs/androidfw/ApkAssets.cpp | 254 +++++++++------- libs/androidfw/Asset.cpp | 25 +- libs/androidfw/LoadedArsc.cpp | 2 +- libs/androidfw/include/androidfw/ApkAssets.h | 89 +++--- libs/androidfw/include/androidfw/Asset.h | 24 +- libs/androidfw/include/androidfw/LoadedArsc.h | 20 +- libs/androidfw/tests/ApkAssets_test.cpp | 2 +- libs/androidfw/tests/AssetManager2_test.cpp | 6 +- .../tests/AttributeResolution_test.cpp | 2 +- libs/androidfw/tests/Idmap_test.cpp | 4 +- 20 files changed, 634 insertions(+), 384 deletions(-) diff --git a/api/current.txt b/api/current.txt index d944d24d2993e..861d6e8e59580 100644 --- a/api/current.txt +++ b/api/current.txt @@ -12871,11 +12871,8 @@ package android.content.res.loader { 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 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; } } @@ -82233,3 +82230,4 @@ package org.xmlpull.v1.sax2 { } } + diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index 81671c349cbd1..60f61cef0b61f 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -368,10 +368,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) { diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index c6875a4b34433..5ade261532962 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -55,7 +55,6 @@ import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; import android.content.Intent; import android.content.IntentFilter; -import android.content.pm.parsing.ParsingPackageUtils; import android.content.pm.permission.SplitPermissionInfoParcelable; import android.content.pm.split.DefaultSplitAssetLoader; import android.content.pm.split.SplitAssetDependencyLoader; @@ -1442,7 +1441,7 @@ public class PackageParser { try { try { apkAssets = fd != null - ? ApkAssets.loadFromFd(fd, debugPathName, false, false) + ? ApkAssets.loadFromFd(fd, debugPathName, 0 /* flags */) : 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..88b4c290c52ae 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 */) : 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..b9dad85fe84c4 100644 --- a/core/java/android/content/res/ApkAssets.java +++ b/core/java/android/content/res/ApkAssets.java @@ -15,17 +15,19 @@ */ 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.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 +41,72 @@ 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; + + // Format types that change how the apk assets are loaded. + @IntDef(prefix = { "FORMAT_" }, value = { + FORMAT_APK, + FORMAT_IDMAP, + FORMAT_ARSC, + }) + @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; /** * Creates a new ApkAssets instance from the given path on disk. @@ -56,59 +116,59 @@ 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); } /** - * 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. - * @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*/); - } - - /** - * 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 * @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) throws IOException { + return new ApkAssets(FORMAT_APK, fd, friendlyName, flags); + } + + /** + * 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 + * @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) throws IOException { - return new ApkAssets(fd, friendlyName, system, forceSharedLibrary, false /*arscOnly*/, - false /*forLoader*/); + return new ApkAssets(FORMAT_APK, fd, friendlyName, offset, length, flags); } /** @@ -116,99 +176,101 @@ 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); } /** - * 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 * @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) + public static @NonNull ApkAssets loadTableFromFd(@NonNull FileDescriptor fd, + @NonNull String friendlyName, @PropertyFlags int flags) throws IOException { + return new ApkAssets(FORMAT_ARSC, fd, friendlyName, flags); + } + + /** + * 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 + * @return a new instance of ApkAssets. + * @throws IOException if a disk I/O error or parsing error occurred. + */ + public static @NonNull ApkAssets loadTableFromFd(@NonNull FileDescriptor fd, + @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags) throws IOException { - return new ApkAssets(fd, TextUtils.emptyIfNull(fd.toString()), - false /*system*/, false /*forceSharedLib*/, true /*arscOnly*/, true /*forLoader*/); + return new ApkAssets(FORMAT_ARSC, fd, friendlyName, offset, length, flags); } /** * 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 */ @NonNull - public static ApkAssets loadEmptyForLoader() { - return new ApkAssets(true); + public static ApkAssets loadEmptyForLoader(@PropertyFlags int flags) { + return new ApkAssets(flags); } - 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) + throws IOException { Objects.requireNonNull(path, "path"); - mNativePtr = arscOnly ? nativeLoadArsc(path, forLoader) - : nativeLoad(path, system, forceSharedLib, overlay, forLoader); + mFlags = flags; + mNativePtr = nativeLoad(format, path, flags); mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/); } - 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) 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); mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/); } + private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd, + @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags) + throws IOException { + Objects.requireNonNull(fd, "fd"); + Objects.requireNonNull(friendlyName, "friendlyName"); + mFlags = flags; + mNativePtr = nativeLoadFdOffsets(format, fd, friendlyName, offset, length, flags); + mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/); + } + + private ApkAssets(@PropertyFlags int flags) { + mFlags = flags; + mNativePtr = nativeLoadEmpty(flags); + mStringBlock = null; + } + @UnsupportedAppUsage public @NonNull String getAssetPath() { synchronized (this) { @@ -226,8 +288,9 @@ 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; } /** @@ -300,18 +363,14 @@ 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) throws IOException; + private static native long nativeLoadEmpty(@PropertyFlags int flags); + private static native long nativeLoadFd(@FormatType int format, @NonNull FileDescriptor fd, + @NonNull String friendlyName, @PropertyFlags int flags) throws IOException; + private static native long nativeLoadFdOffsets(@FormatType int format, + @NonNull FileDescriptor fd, @NonNull String friendlyName, long offset, long length, + @PropertyFlags int flags) throws IOException; 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..6b9613d6e3be4 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -242,12 +242,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); @@ -443,9 +443,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; diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index cb809da3b8679..e77d8af498730 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -2388,6 +2388,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 +2422,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 +2454,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/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..1d24dda8a4bf7 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"; @@ -51,70 +53,110 @@ public class ResourcesProvider implements AutoCloseable, Closeable { 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 +165,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,7 +208,8 @@ 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); } private ResourcesProvider(@NonNull ApkAssets apkAssets, diff --git a/core/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp index 491d4a61cee4c..44f8a31a4e22e 100644 --- a/core/jni/android_content_res_ApkAssets.cpp +++ b/core/jni/android_content_res_ApkAssets.cpp @@ -37,8 +37,21 @@ 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) { +// 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, +}; + +static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, const format_type_t format, + jstring java_path, const jint property_flags) { ScopedUtfChars path(env, java_path); if (path.c_str() == nullptr) { return 0; @@ -47,25 +60,33 @@ static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, jstring java_path, jboole ATRACE_NAME(base::StringPrintf("LoadApkAssets(%s)", path.c_str()).c_str()); 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); + 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); + 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) { ScopedUtfChars friendly_name_utf8(env, friendly_name); if (friendly_name_utf8.c_str() == nullptr) { return 0; @@ -85,49 +106,53 @@ static jlong NativeLoadFromFd(JNIEnv* env, jclass /*clazz*/, jobject file_descri return 0; } - auto dup_fd_id = dup_fd.get(); - std::unique_ptr apk_assets = ApkAssets::LoadFromFd(std::move(dup_fd), - friendly_name_utf8.c_str(), - system, force_shared_lib, - for_loader); + 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); + break; + case FORMAT_ARSC: + apk_assets = ApkAssets::LoadTableFromFd(std::move(dup_fd), friendly_name_utf8.c_str(), + property_flags); + 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(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) { ScopedUtfChars friendly_name_utf8(env, friendly_name); if (friendly_name_utf8.c_str() == nullptr) { return 0; } + if (offset < 0) { + jniThrowException(env, "java/lang/IllegalArgumentException", + "offset cannot be negative"); + return 0; + } + + if (length < 0) { + jniThrowException(env, "java/lang/IllegalArgumentException", + "length cannot be negative"); + return 0; + } + int fd = jniGetFDFromFileDescriptor(env, file_descriptor); - ATRACE_NAME(base::StringPrintf("LoadApkAssetsArscFd(%d)", fd).c_str()); if (fd < 0) { jniThrowException(env, "java/lang/IllegalArgumentException", "Bad FileDescriptor"); return 0; @@ -139,18 +164,37 @@ 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); + ATRACE_NAME(base::StringPrintf("LoadApkAssetsFd(%s)", friendly_name_utf8.c_str()).c_str()); + + 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, 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, 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) { + std::unique_ptr apk_assets = ApkAssets::LoadEmpty(flags); return reinterpret_cast(apk_assets.release()); } @@ -252,13 +296,11 @@ 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;I)J", (void*)NativeLoad}, + {"nativeLoadEmpty", "(I)J", (void*)NativeLoadEmpty}, + {"nativeLoadFd", "(ILjava/io/FileDescriptor;Ljava/lang/String;I)J", (void*)NativeLoadFromFd}, + {"nativeLoadFdOffsets", "(ILjava/io/FileDescriptor;Ljava/lang/String;JJI)J", + (void*)NativeLoadFromFdOffset}, {"nativeDestroy", "(J)V", (void*)NativeDestroy}, {"nativeGetAssetPath", "(J)Ljava/lang/String;", (void*)NativeGetAssetPath}, {"nativeGetStringBlock", "(J)J", (void*)NativeGetStringBlock}, diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp index b2b0ec2a54f8e..946fcc03f57e0 100644 --- a/libs/androidfw/ApkAssets.cpp +++ b/libs/androidfw/ApkAssets.cpp @@ -41,28 +41,85 @@ using base::unique_fd; static const std::string kResourcesArsc("resources.arsc"); ApkAssets::ApkAssets(ZipArchiveHandle unmanaged_handle, - const std::string& path, + 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), + : zip_handle_(unmanaged_handle, ::CloseArchive), + path_(std::move(path)), + last_mod_time_(last_mod_time), property_flags_(property_flags) { } -std::unique_ptr 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); +std::unique_ptr ApkAssets::Load(const std::string& path, + const package_property_t flags) { + ::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 LoadImpl(unmanaged_handle, path, nullptr /*idmap_asset*/, nullptr /*loaded_idmap*/, + flags); } -std::unique_ptr 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); +std::unique_ptr ApkAssets::LoadFromFd(unique_fd fd, + const std::string& friendly_name, + const package_property_t flags, + 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; + + ::ZipArchiveHandle unmanaged_handle; + const int32_t result = (length == 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 LoadImpl(unmanaged_handle, friendly_name, nullptr /*idmap_asset*/, + nullptr /*loaded_idmap*/, flags); +} + +std::unique_ptr ApkAssets::LoadTable(const std::string& path, + const package_property_t flags) { + auto resources_asset = CreateAssetFromFile(path); + if (!resources_asset) { + LOG(ERROR) << "Failed to open ARSC '" << path; + return {}; + } + + return LoadTableImpl(std::move(resources_asset), path, flags); +} + +std::unique_ptr ApkAssets::LoadTableFromFd(unique_fd fd, + const std::string& friendly_name, + const package_property_t flags, + const off64_t offset, + const off64_t length) { + auto resources_asset = CreateAssetFromFd(std::move(fd), nullptr /* path */, offset, length); + if (!resources_asset) { + LOG(ERROR) << "Failed to open ARSC '" << friendly_name << "' through FD with offset " << offset + << " and length " << length; + return {}; + } + + return LoadTableImpl(std::move(resources_asset), friendly_name, flags); } std::unique_ptr 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 idmap_asset = CreateAssetFromFile(idmap_path); if (idmap_asset == nullptr) { return {}; @@ -77,75 +134,74 @@ std::unique_ptr ApkAssets::LoadOverlay(const std::string& idmap return {}; } - auto apkPath = loaded_idmap->OverlayApkPath(); - return LoadImpl({} /*fd*/, apkPath, - std::move(idmap_asset), - std::move(loaded_idmap), - PROPERTY_OVERLAY | (system ? PROPERTY_SYSTEM : 0U)); -} -std::unique_ptr 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 ApkAssets::LoadArsc(const std::string& path, - bool for_loader) { - return LoadArscImpl({} /*fd*/, path, for_loader ? PROPERTY_LOADER : 0U); -} - -std::unique_ptr 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 ApkAssets::CreateAssetFromFile(const std::string& path) { - unique_fd fd(base::utf8::open(path.c_str(), O_RDONLY | O_BINARY | O_CLOEXEC)); - if (fd == -1) { - 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 {}; - } - - std::unique_ptr file_map = util::make_unique(); - if (!file_map->create(path.c_str(), fd, 0, static_cast(file_len), true /*readOnly*/)) { - LOG(ERROR) << "Failed to mmap file '" << path << "': " << SystemErrorCodeToString(errno); - return {}; - } - return Asset::createFromUncompressedMap(std::move(file_map), Asset::AccessMode::ACCESS_RANDOM); -} - -std::unique_ptr ApkAssets::LoadImpl( - unique_fd fd, const std::string& path, std::unique_ptr idmap_asset, - std::unique_ptr 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); - } - + auto overlay_path = loaded_idmap->OverlayApkPath(); + const int32_t result = ::OpenArchive(overlay_path.c_str(), &unmanaged_handle); if (result != 0) { - LOG(ERROR) << "Failed to open APK '" << path << "' " << ::ErrorCodeString(result); + LOG(ERROR) << "Failed to open overlay APK '" << overlay_path << "' " + << ::ErrorCodeString(result); ::CloseArchive(unmanaged_handle); return {}; } - time_t last_mod_time = getFileModDate(path.c_str()); + return LoadImpl(unmanaged_handle, overlay_path, std::move(idmap_asset), std::move(loaded_idmap), + flags | PROPERTY_OVERLAY); +} + +std::unique_ptr ApkAssets::LoadEmpty(const package_property_t flags) { + std::unique_ptr loaded_apk(new ApkAssets(nullptr, "empty", -1, flags)); + loaded_apk->loaded_arsc_ = LoadedArsc::CreateEmpty(); + // Need to force a move for mingw32. + return std::move(loaded_apk); +} + +std::unique_ptr ApkAssets::CreateAssetFromFile(const std::string& path) { + unique_fd fd(base::utf8::open(path.c_str(), O_RDONLY | O_BINARY | O_CLOEXEC)); + if (!fd.ok()) { + LOG(ERROR) << "Failed to open file '" << path << "': " << SystemErrorCodeToString(errno); + return {}; + } + + return CreateAssetFromFd(std::move(fd), path.c_str()); +} + +std::unique_ptr 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 file_map = util::make_unique(); + if (!file_map->create(path, fd, offset, static_cast(length), true /*readOnly*/)) { + LOG(ERROR) << "Failed to mmap file '" << ((path) ? path : "anon") << "': " + << SystemErrorCodeToString(errno); + return {}; + } + + // 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 ApkAssets::LoadImpl(ZipArchiveHandle unmanaged_handle, + const std::string& path, + std::unique_ptr idmap_asset, + std::unique_ptr idmap, + package_property_t property_flags) { + const time_t last_mod_time = getFileModDate(path.c_str()); // Wrap the handle in a unique_ptr so it gets automatically closed. std::unique_ptr @@ -153,7 +209,7 @@ std::unique_ptr ApkAssets::LoadImpl( // Find the resource table. ::ZipEntry entry; - result = ::FindEntry(loaded_apk->zip_handle_.get(), kResourcesArsc, &entry); + int32_t result = ::FindEntry(loaded_apk->zip_handle_.get(), kResourcesArsc, &entry); if (result != 0) { // There is no resources.arsc, so create an empty LoadedArsc and return. loaded_apk->loaded_arsc_ = LoadedArsc::CreateEmpty(); @@ -173,7 +229,7 @@ std::unique_ptr ApkAssets::LoadImpl( // 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(loaded_apk->resources_asset_->getBuffer(true /*wordAligned*/)), @@ -189,24 +245,10 @@ std::unique_ptr ApkAssets::LoadImpl( return std::move(loaded_apk); } -std::unique_ptr ApkAssets::LoadArscImpl(unique_fd fd, - const std::string& path, - package_property_t property_flags) { - std::unique_ptr resources_asset; - - if (fd >= 0) { - resources_asset = std::unique_ptr(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 {}; - } - - time_t last_mod_time = getFileModDate(path.c_str()); +std::unique_ptr ApkAssets::LoadTableImpl(std::unique_ptr resources_asset, + const std::string& path, + package_property_t property_flags) { + const time_t last_mod_time = getFileModDate(path.c_str()); std::unique_ptr loaded_apk( new ApkAssets(nullptr, path, last_mod_time, property_flags)); @@ -225,13 +267,6 @@ std::unique_ptr ApkAssets::LoadArscImpl(unique_fd fd, return std::move(loaded_apk); } -std::unique_ptr ApkAssets::LoadEmpty(bool for_loader) { - std::unique_ptr 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 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) { @@ -244,10 +279,12 @@ std::unique_ptr ApkAssets::Open(const std::string& path, Asset::AccessMod return {}; } + const int fd = ::GetFileDescriptor(zip_handle_.get()); + const off64_t fd_offset = ::GetFileDescriptorOffset(zip_handle_.get()); if (entry.method == kCompressDeflated) { std::unique_ptr map = util::make_unique(); - if (!map->create(path_.c_str(), ::GetFileDescriptor(zip_handle_.get()), entry.offset, - entry.compressed_length, true /*readOnly*/)) { + if (!map->create(path_.c_str(), fd, fd_offset + entry.offset, entry.compressed_length, + true /*readOnly*/)) { LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << path_ << "'"; return {}; } @@ -261,13 +298,18 @@ std::unique_ptr ApkAssets::Open(const std::string& path, Asset::AccessMod return asset; } else { std::unique_ptr map = util::make_unique(); - if (!map->create(path_.c_str(), ::GetFileDescriptor(zip_handle_.get()), entry.offset, - entry.uncompressed_length, true /*readOnly*/)) { + if (!map->create(path_.c_str(), fd, fd_offset + entry.offset, entry.uncompressed_length, + true /*readOnly*/)) { LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << path_ << "'"; return {}; } - std::unique_ptr asset = Asset::createFromUncompressedMap(std::move(map), mode); + // TODO: apks created from file descriptors residing in RAM currently cannot open file + // descriptors to the assets they contain. This is because the Asset::openFileDeescriptor uses + // the zip path on disk to create a new file descriptor. This is fixed in a future change + // in the change topic. + std::unique_ptr asset = Asset::createFromUncompressedMap(std::move(map), + unique_fd(-1) /* fd*/, mode); if (asset == nullptr) { LOG(ERROR) << "Failed to mmap file '" << path << "' in APK '" << path_ << "'"; return {}; diff --git a/libs/androidfw/Asset.cpp b/libs/androidfw/Asset.cpp index c132f343713f5..cd30c184d5a47 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::createFromUncompressedMap(std::unique_ptr 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/LoadedArsc.cpp b/libs/androidfw/LoadedArsc.cpp index e35c0249fbdf5..70bb441f94cb5 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 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 af802b0e50b96..643dc5c861f73 100644 --- a/libs/androidfw/include/androidfw/ApkAssets.h +++ b/libs/androidfw/include/androidfw/ApkAssets.h @@ -37,49 +37,46 @@ class LoadedIdmap; // Holds an APK. class ApkAssets { + // This means the data extends to the end of the file. + static constexpr off64_t kUnknownLength = -1; + public: - // 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 Load(const std::string& path, bool system = false, - bool for_loader = false); - - // Creates an ApkAssets, but forces any package with ID 0x7f to be loaded as a shared library. - // If `system` is true, the package is marked as a system package, and allows some functions to - // filter out this package when computing what configurations/resources are available. - static std::unique_ptr LoadAsSharedLibrary(const std::string& path, - bool system = false); - - // Creates an ApkAssets from an IDMAP, which contains the original APK path, and the overlay - // data. - // If `system` is true, the package is marked as a system package, and allows some functions to - // filter out this package when computing what configurations/resources are available. - static std::unique_ptr LoadOverlay(const std::string& idmap_path, - bool system = false); + // Creates an ApkAssets from the zip path. + static std::unique_ptr Load(const std::string& path, + 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. + // 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 LoadFromFd(base::unique_fd fd, - const std::string& friendly_name, bool system, - bool force_shared_lib, - bool for_loader = false); + const std::string& friendly_name, + package_property_t flags = 0U, + off64_t offset = 0, + off64_t length = kUnknownLength); - // Creates an empty wrapper ApkAssets from the given path which points to an .arsc. - static std::unique_ptr LoadArsc(const std::string& path, - bool for_loader = false); + // Creates an ApkAssets from the given path which points to a resources.arsc. + static std::unique_ptr LoadTable(const std::string& path, + package_property_t flags = 0U); - // 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 LoadArsc(base::unique_fd fd, - const std::string& friendly_name, - bool for_loader = false); + // Creates an ApkAssets from the given file descriptor which points to an resources.arsc, and + // takes ownership of the file descriptor. + // If `length` equals kUnknownLength, offset must equal 0; otherwise, the .arsc data will be read + // using the `offset` into the file descriptor and will be `length` bytes long. + static std::unique_ptr LoadTableFromFd(base::unique_fd fd, + const std::string& friendly_name, + package_property_t flags = 0U, + off64_t offset = 0, + off64_t length = kUnknownLength); + + // Creates an ApkAssets from an IDMAP, which contains the original APK path, and the overlay + // data. + static std::unique_ptr LoadOverlay(const std::string& idmap_path, + package_property_t flags = 0U); // Creates a totally empty ApkAssets with no resources table and no file entries. - static std::unique_ptr LoadEmpty(bool for_loader = false); + static std::unique_ptr LoadEmpty(package_property_t flags = 0U); std::unique_ptr Open(const std::string& path, Asset::AccessMode mode = Asset::AccessMode::ACCESS_RANDOM) const; @@ -105,28 +102,38 @@ 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 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 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 LoadImpl(base::unique_fd fd, const std::string& path, + static std::unique_ptr LoadImpl(ZipArchiveHandle unmanaged_handle, + const std::string& path, std::unique_ptr idmap_asset, - std::unique_ptr loaded_idmap, + std::unique_ptr idmap, package_property_t property_flags); - static std::unique_ptr LoadArscImpl(base::unique_fd fd, - const std::string& path, - package_property_t property_flags); + static std::unique_ptr LoadTableImpl(std::unique_ptr resources_asset, + const std::string& path, + package_property_t property_flags); ApkAssets(ZipArchiveHandle unmanaged_handle, - const std::string& path, + std::string path, time_t last_mod_time, package_property_t property_flags); diff --git a/libs/androidfw/include/androidfw/Asset.h b/libs/androidfw/include/androidfw/Asset.h index 053dbb7864c63..75761747a5b4a 100644 --- a/libs/androidfw/include/androidfw/Asset.h +++ b/libs/androidfw/include/androidfw/Asset.h @@ -26,6 +26,7 @@ #include +#include #include #include #include @@ -202,8 +203,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 createFromUncompressedMap(std::unique_ptr dataMap, - AccessMode mode); + base::unique_fd fd, AccessMode mode); /* * Create the asset from a memory-mapped file segment with compressed @@ -256,9 +263,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 +280,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 b5d3a1fc6c1fc..89ff9f52125df 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 0f2ee6fb968e2..26bf5ffe5e911 100644 --- a/libs/androidfw/tests/ApkAssets_test.cpp +++ b/libs/androidfw/tests/ApkAssets_test.cpp @@ -70,7 +70,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(); diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp index 35fea7ab86cb6..ac32699c6dfd7 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 c8dbe205fee24..24361b5817f41 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 b679672ab34e2..41ba637da5d77 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(), From 349695f3701e90746cb2197552299dcb99960ce8 Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Wed, 11 Mar 2020 10:22:33 -0700 Subject: [PATCH 4/7] Refactor tests for ApkAsset loading APIs This change adds the file descriptor based ResourceProvider APIs to the provider formats to test. It also refactors the testing API so that all test APKs, ARSCs, and splits have the same resources rather than different formats having different resources. Bug: 142716192 Test: atest FrameworksResourceLoaderTests Change-Id: I37da9f6c815ea67629b3115b0e125ebc79cd7c8c --- core/tests/ResourceLoaderTests/Android.bp | 54 ++-- .../tests/ResourceLoaderTests/AndroidTest.xml | 2 +- core/tests/ResourceLoaderTests/NonAsset.txt | 1 - .../ResourceLoaderTests/assets/Asset.txt | 1 - .../ResourceLoaderTests/assets/asset.txt | 1 + ...{non_asset_bitmap.png => drawable_png.png} | Bin ...on_asset_drawable.xml => drawable_xml.xml} | 0 .../res/values/strings.xml | 23 -- .../values/values.xml} | 3 +- .../ResourceLoaderTests/resources/Android.bp | 115 ++++++++ .../resources/AndroidManifestApp.xml | 9 +- .../resources/AndroidManifestFramework.xml | 9 +- .../AndroidManifestSplit1.xml} | 3 +- .../AndroidManifestSplit2.xml} | 3 +- .../AndroidManifestSplit3.xml} | 5 +- .../AndroidManifestSplit4.xml} | 3 +- .../resources/compileAndLink.sh | 136 --------- .../res/drawable-mdpi/ic_delete.png | Bin .../res/layout/activity_list_item.xml | 0 .../res/values/public.xml} | 5 +- .../res/values/values.xml} | 3 +- .../resources/provider1/assets/asset.txt | 1 + .../res/drawable-nodpi/drawable_png.png} | Bin .../res/drawable-nodpi/drawable_xml.xml} | 0 .../res/layout/layout.xml} | 0 .../res/values/values.xml} | 9 +- .../resources/provider2/assets/asset.txt | 1 + .../res/drawable-nodpi/drawable_png.png} | Bin .../res/drawable-nodpi/drawable_xml.xml} | 0 .../res/layout/layout.xml} | 0 .../resources/provider2/res/values/values.xml | 24 ++ .../resources/provider3/assets/asset.txt | 1 + .../res/drawable-nodpi/drawable_png.png} | Bin .../res/drawable-nodpi/drawable_xml.xml} | 0 .../res/layout/layout.xml} | 0 .../resources/provider3/res/values/values.xml | 24 ++ .../resources/provider4/assets/asset.txt | 1 + .../res/drawable-nodpi/drawable_png.png} | Bin .../res/drawable-nodpi/drawable_xml.xml} | 0 .../res/layout/layout.xml} | 0 .../resources/provider4/res/values/values.xml | 24 ++ .../res/values/values.xml} | 3 +- .../provider_stable/res/values/public.xml} | 9 +- .../res/values/activity_list_item_id.xml | 20 -- .../resources/res/values/dimen_one.xml | 21 -- .../resources/res/values/dimen_two.xml | 21 -- .../resources/res/values/drawable_one.xml | 20 -- .../resources/res/values/layout_id.xml | 20 -- .../res/values/non_asset_bitmap_id.xml | 20 -- .../resources/res/values/string_one.xml | 21 -- .../resources/res/values/string_two.xml | 21 -- .../splits/SplitFour/Android.bp | 19 -- .../splits/SplitOne/Android.bp | 19 -- .../SplitOne/res/values/string_split.xml | 21 -- .../splits/SplitThree/Android.bp | 19 -- .../SplitThree/res/values/string_spli.xml | 21 -- .../splits/SplitTwo/Android.bp | 19 -- .../SplitTwo/res/values/string_split.xml | 21 -- .../test/DirectoryAssetsProviderTest.kt | 105 ------- .../res/loader/test/ResourceLoaderTestBase.kt | 187 ++++++++----- .../loader/test/ResourceLoaderValuesTest.kt | 257 +++++++++--------- .../android/content/res/loader/test/Utils.kt | 13 +- 62 files changed, 503 insertions(+), 835 deletions(-) delete mode 100644 core/tests/ResourceLoaderTests/NonAsset.txt delete mode 100644 core/tests/ResourceLoaderTests/assets/Asset.txt create mode 100644 core/tests/ResourceLoaderTests/assets/asset.txt rename core/tests/ResourceLoaderTests/res/drawable-nodpi/{non_asset_bitmap.png => drawable_png.png} (100%) rename core/tests/ResourceLoaderTests/res/drawable-nodpi/{non_asset_drawable.xml => drawable_xml.xml} (100%) delete mode 100644 core/tests/ResourceLoaderTests/res/values/strings.xml rename core/tests/ResourceLoaderTests/{resources/res/values/non_asset_drawable_id.xml => res/values/values.xml} (89%) create mode 100644 core/tests/ResourceLoaderTests/resources/Android.bp rename core/tests/ResourceLoaderTests/{splits/SplitOne/AndroidManifest.xml => resources/AndroidManifestSplit1.xml} (90%) rename core/tests/ResourceLoaderTests/{splits/SplitThree/AndroidManifest.xml => resources/AndroidManifestSplit2.xml} (90%) rename core/tests/ResourceLoaderTests/{splits/SplitTwo/AndroidManifest.xml => resources/AndroidManifestSplit3.xml} (85%) rename core/tests/ResourceLoaderTests/{splits/SplitFour/AndroidManifest.xml => resources/AndroidManifestSplit4.xml} (90%) delete mode 100755 core/tests/ResourceLoaderTests/resources/compileAndLink.sh rename core/tests/ResourceLoaderTests/resources/{ => framework}/res/drawable-mdpi/ic_delete.png (100%) rename core/tests/ResourceLoaderTests/resources/{ => framework}/res/layout/activity_list_item.xml (100%) rename core/tests/ResourceLoaderTests/resources/{res/values/string_three.xml => framework/res/values/public.xml} (83%) rename core/tests/ResourceLoaderTests/resources/{res/values/dimen_four.xml => framework/res/values/values.xml} (86%) create mode 100644 core/tests/ResourceLoaderTests/resources/provider1/assets/asset.txt rename core/tests/ResourceLoaderTests/resources/{res/drawable-nodpi/nonAssetBitmapRed.png => provider1/res/drawable-nodpi/drawable_png.png} (100%) rename core/tests/ResourceLoaderTests/resources/{res/drawable-nodpi/nonAssetDrawableOne.xml => provider1/res/drawable-nodpi/drawable_xml.xml} (100%) rename core/tests/ResourceLoaderTests/resources/{res/layout/layout_one.xml => provider1/res/layout/layout.xml} (100%) rename core/tests/ResourceLoaderTests/resources/{res/values/string_four.xml => provider1/res/values/values.xml} (78%) create mode 100644 core/tests/ResourceLoaderTests/resources/provider2/assets/asset.txt rename core/tests/ResourceLoaderTests/resources/{res/drawable-nodpi/nonAssetBitmapGreen.png => provider2/res/drawable-nodpi/drawable_png.png} (100%) rename core/tests/ResourceLoaderTests/resources/{res/drawable-nodpi/nonAssetDrawableTwo.xml => provider2/res/drawable-nodpi/drawable_xml.xml} (100%) rename core/tests/ResourceLoaderTests/resources/{res/layout/layout_two.xml => provider2/res/layout/layout.xml} (100%) create mode 100644 core/tests/ResourceLoaderTests/resources/provider2/res/values/values.xml create mode 100644 core/tests/ResourceLoaderTests/resources/provider3/assets/asset.txt rename core/tests/ResourceLoaderTests/resources/{res/drawable-nodpi/nonAssetBitmapBlue.png => provider3/res/drawable-nodpi/drawable_png.png} (100%) rename core/tests/ResourceLoaderTests/resources/{res/drawable-nodpi/nonAssetDrawableThree.xml => provider3/res/drawable-nodpi/drawable_xml.xml} (100%) rename core/tests/ResourceLoaderTests/resources/{res/layout/layout_three.xml => provider3/res/layout/layout.xml} (100%) create mode 100644 core/tests/ResourceLoaderTests/resources/provider3/res/values/values.xml create mode 100644 core/tests/ResourceLoaderTests/resources/provider4/assets/asset.txt rename core/tests/ResourceLoaderTests/resources/{res/drawable-nodpi/nonAssetBitmapWhite.png => provider4/res/drawable-nodpi/drawable_png.png} (100%) rename core/tests/ResourceLoaderTests/resources/{res/drawable-nodpi/nonAssetDrawableFour.xml => provider4/res/drawable-nodpi/drawable_xml.xml} (100%) rename core/tests/ResourceLoaderTests/resources/{res/layout/layout_four.xml => provider4/res/layout/layout.xml} (100%) create mode 100644 core/tests/ResourceLoaderTests/resources/provider4/res/values/values.xml rename core/tests/ResourceLoaderTests/resources/{res/values/dimen_three.xml => provider_additional/res/values/values.xml} (86%) rename core/tests/ResourceLoaderTests/{splits/SplitFour/res/values/string_split.xml => resources/provider_stable/res/values/public.xml} (68%) delete mode 100644 core/tests/ResourceLoaderTests/resources/res/values/activity_list_item_id.xml delete mode 100644 core/tests/ResourceLoaderTests/resources/res/values/dimen_one.xml delete mode 100644 core/tests/ResourceLoaderTests/resources/res/values/dimen_two.xml delete mode 100644 core/tests/ResourceLoaderTests/resources/res/values/drawable_one.xml delete mode 100644 core/tests/ResourceLoaderTests/resources/res/values/layout_id.xml delete mode 100644 core/tests/ResourceLoaderTests/resources/res/values/non_asset_bitmap_id.xml delete mode 100644 core/tests/ResourceLoaderTests/resources/res/values/string_one.xml delete mode 100644 core/tests/ResourceLoaderTests/resources/res/values/string_two.xml delete mode 100644 core/tests/ResourceLoaderTests/splits/SplitFour/Android.bp delete mode 100644 core/tests/ResourceLoaderTests/splits/SplitOne/Android.bp delete mode 100644 core/tests/ResourceLoaderTests/splits/SplitOne/res/values/string_split.xml delete mode 100644 core/tests/ResourceLoaderTests/splits/SplitThree/Android.bp delete mode 100644 core/tests/ResourceLoaderTests/splits/SplitThree/res/values/string_spli.xml delete mode 100644 core/tests/ResourceLoaderTests/splits/SplitTwo/Android.bp delete mode 100644 core/tests/ResourceLoaderTests/splits/SplitTwo/res/values/string_split.xml delete mode 100644 core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryAssetsProviderTest.kt 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 @@

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/ResourcesProvider.java b/core/java/android/content/res/loader/ResourcesProvider.java index 040b3694d0cdf..0a698d18682b9 100644 --- a/core/java/android/content/res/loader/ResourcesProvider.java +++ b/core/java/android/content/res/loader/ResourcesProvider.java @@ -50,8 +50,6 @@ public class ResourcesProvider implements AutoCloseable, Closeable { @GuardedBy("mLock") private final ApkAssets mApkAssets; - private final AssetsProvider mAssetsProvider; - /** * Creates an empty ResourcesProvider with no resource data. This is useful for loading * file-based assets not associated with resource identifiers. @@ -60,8 +58,8 @@ public class ResourcesProvider implements AutoCloseable, Closeable { */ @NonNull public static ResourcesProvider empty(@NonNull AssetsProvider assetsProvider) { - return new ResourcesProvider(ApkAssets.loadEmptyForLoader(ApkAssets.PROPERTY_LOADER), - assetsProvider); + return new ResourcesProvider(ApkAssets.loadEmptyForLoader(ApkAssets.PROPERTY_LOADER, + assetsProvider)); } /** @@ -101,7 +99,7 @@ public class ResourcesProvider implements AutoCloseable, Closeable { @Nullable AssetsProvider assetsProvider) throws IOException { return new ResourcesProvider(ApkAssets.loadFromFd(fileDescriptor.getFileDescriptor(), - fileDescriptor.toString(), ApkAssets.PROPERTY_LOADER), assetsProvider); + fileDescriptor.toString(), ApkAssets.PROPERTY_LOADER, assetsProvider)); } /** @@ -130,8 +128,8 @@ public class ResourcesProvider implements AutoCloseable, Closeable { long offset, long length, @Nullable AssetsProvider assetsProvider) throws IOException { return new ResourcesProvider(ApkAssets.loadFromFd(fileDescriptor.getFileDescriptor(), - fileDescriptor.toString(), offset, length, ApkAssets.PROPERTY_LOADER), - assetsProvider); + fileDescriptor.toString(), offset, length, ApkAssets.PROPERTY_LOADER, + assetsProvider)); } /** @@ -156,7 +154,7 @@ public class ResourcesProvider implements AutoCloseable, Closeable { throws IOException { return new ResourcesProvider( ApkAssets.loadTableFromFd(fileDescriptor.getFileDescriptor(), - fileDescriptor.toString(), ApkAssets.PROPERTY_LOADER), assetsProvider); + fileDescriptor.toString(), ApkAssets.PROPERTY_LOADER, assetsProvider)); } /** @@ -187,8 +185,8 @@ public class ResourcesProvider implements AutoCloseable, Closeable { throws IOException { return new ResourcesProvider( ApkAssets.loadTableFromFd(fileDescriptor.getFileDescriptor(), - fileDescriptor.toString(), offset, length, ApkAssets.PROPERTY_LOADER), - assetsProvider); + fileDescriptor.toString(), offset, length, ApkAssets.PROPERTY_LOADER, + assetsProvider)); } /** @@ -208,8 +206,8 @@ public class ResourcesProvider implements AutoCloseable, Closeable { } String splitPath = appInfo.getSplitCodePaths()[splitIndex]; - return new ResourcesProvider(ApkAssets.loadFromPath(splitPath, ApkAssets.PROPERTY_LOADER), - null); + return new ResourcesProvider(ApkAssets.loadFromPath(splitPath, ApkAssets.PROPERTY_LOADER, + null /* assetsProvider */)); } /** @@ -223,20 +221,13 @@ public class ResourcesProvider implements AutoCloseable, Closeable { @NonNull public static ResourcesProvider loadFromDirectory(@NonNull String path, @Nullable AssetsProvider assetsProvider) throws IOException { - return new ResourcesProvider(ApkAssets.loadFromDir(path, ApkAssets.PROPERTY_LOADER), - assetsProvider); + return new ResourcesProvider(ApkAssets.loadFromDir(path, ApkAssets.PROPERTY_LOADER, + assetsProvider)); } - private ResourcesProvider(@NonNull ApkAssets apkAssets, - @Nullable AssetsProvider assetsProvider) { + private ResourcesProvider(@NonNull ApkAssets apkAssets) { this.mApkAssets = apkAssets; - this.mAssetsProvider = assetsProvider; - } - - @Nullable - public AssetsProvider getAssetsProvider() { - return mAssetsProvider; } /** @hide */ diff --git a/core/jni/android_content_res_ApkAssets.cpp b/core/jni/android_content_res_ApkAssets.cpp index 6acb133a4e5b5..fbdd4060d7f26 100644 --- a/core/jni/android_content_res_ApkAssets.cpp +++ b/core/jni/android_content_res_ApkAssets.cpp @@ -37,6 +37,21 @@ static struct overlayableinfo_offsets_t { jmethodID constructor; } gOverlayableInfoOffsets; +static struct assetfiledescriptor_offsets_t { + jfieldID mFd; + jfieldID mStartOffset; + jfieldID mLength; +} gAssetFileDescriptorOffsets; + +static struct assetsprovider_offsets_t { + jclass classObject; + jmethodID loadAssetFd; +} gAssetsProviderOffsets; + +static struct { + jmethodID detachFd; +} gParcelFileDescriptorOffsets; + // Keep in sync with f/b/android/content/res/ApkAssets.java using format_type_t = jint; enum : format_type_t { @@ -53,8 +68,97 @@ enum : format_type_t { FORMAT_DIRECTORY = 3, }; +class LoaderAssetsProvider : public AssetsProvider { + public: + static std::unique_ptr 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) { + jstring java_path, const jint property_flags, jobject assets_provider) { ScopedUtfChars path(env, java_path); if (path.c_str() == nullptr) { return 0; @@ -62,19 +166,20 @@ static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, const format_type_t forma ATRACE_NAME(base::StringPrintf("LoadApkAssets(%s)", path.c_str()).c_str()); + auto loader_assets = LoaderAssetsProvider::Create(env, assets_provider); std::unique_ptr apk_assets; switch (format) { case FORMAT_APK: - apk_assets = ApkAssets::Load(path.c_str(), property_flags); + apk_assets = ApkAssets::Load(path.c_str(), property_flags, std::move(loader_assets)); break; case FORMAT_IDMAP: apk_assets = ApkAssets::LoadOverlay(path.c_str(), property_flags); break; case FORMAT_ARSC: - apk_assets = ApkAssets::LoadTable(path.c_str(), property_flags); + apk_assets = ApkAssets::LoadTable(path.c_str(), property_flags, std::move(loader_assets)); break; case FORMAT_DIRECTORY: - apk_assets = ApkAssets::LoadFromDir(path.c_str(), property_flags); + apk_assets = ApkAssets::LoadFromDir(path.c_str(), property_flags, std::move(loader_assets)); break; default: const std::string error_msg = base::StringPrintf("Unsupported format type %d", format); @@ -92,7 +197,7 @@ static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, const format_type_t forma static jlong NativeLoadFromFd(JNIEnv* env, jclass /*clazz*/, const format_type_t format, jobject file_descriptor, jstring friendly_name, - const jint property_flags) { + const jint property_flags, jobject assets_provider) { ScopedUtfChars friendly_name_utf8(env, friendly_name); if (friendly_name_utf8.c_str() == nullptr) { return 0; @@ -112,15 +217,16 @@ static jlong NativeLoadFromFd(JNIEnv* env, jclass /*clazz*/, const format_type_t return 0; } + auto loader_assets = LoaderAssetsProvider::Create(env, assets_provider); std::unique_ptr apk_assets; switch (format) { case FORMAT_APK: apk_assets = ApkAssets::LoadFromFd(std::move(dup_fd), friendly_name_utf8.c_str(), - property_flags); + property_flags, std::move(loader_assets)); break; case FORMAT_ARSC: apk_assets = ApkAssets::LoadTableFromFd(std::move(dup_fd), friendly_name_utf8.c_str(), - property_flags); + property_flags, std::move(loader_assets)); break; default: const std::string error_msg = base::StringPrintf("Unsupported format type %d", format); @@ -140,12 +246,14 @@ static jlong NativeLoadFromFd(JNIEnv* env, jclass /*clazz*/, const format_type_t static jlong NativeLoadFromFdOffset(JNIEnv* env, jclass /*clazz*/, const format_type_t format, jobject file_descriptor, jstring friendly_name, const jlong offset, const jlong length, - const jint property_flags) { + const jint property_flags, jobject assets_provider) { ScopedUtfChars friendly_name_utf8(env, friendly_name); if (friendly_name_utf8.c_str() == nullptr) { return 0; } + ATRACE_NAME(base::StringPrintf("LoadApkAssetsFd(%s)", friendly_name_utf8.c_str()).c_str()); + if (offset < 0) { jniThrowException(env, "java/lang/IllegalArgumentException", "offset cannot be negative"); @@ -170,18 +278,19 @@ static jlong NativeLoadFromFdOffset(JNIEnv* env, jclass /*clazz*/, const format_ return 0; } - ATRACE_NAME(base::StringPrintf("LoadApkAssetsFd(%s)", friendly_name_utf8.c_str()).c_str()); - + auto loader_assets = LoaderAssetsProvider::Create(env, assets_provider); std::unique_ptr apk_assets; switch (format) { case FORMAT_APK: apk_assets = ApkAssets::LoadFromFd(std::move(dup_fd), friendly_name_utf8.c_str(), - property_flags, static_cast(offset), + 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, static_cast(offset), + property_flags, std::move(loader_assets), + static_cast(offset), static_cast(length)); break; default: @@ -199,8 +308,9 @@ static jlong NativeLoadFromFdOffset(JNIEnv* env, jclass /*clazz*/, const format_ return reinterpret_cast(apk_assets.release()); } -static jlong NativeLoadEmpty(JNIEnv* env, jclass /*clazz*/, jint flags) { - std::unique_ptr apk_assets = ApkAssets::LoadEmpty(flags); +static jlong NativeLoadEmpty(JNIEnv* env, jclass /*clazz*/, jint flags, jobject assets_provider) { + auto loader_assets = LoaderAssetsProvider::Create(env, assets_provider); + auto apk_assets = ApkAssets::LoadEmpty(flags, std::move(loader_assets)); return reinterpret_cast(apk_assets.release()); } @@ -302,11 +412,15 @@ static jboolean NativeDefinesOverlayable(JNIEnv* env, jclass /*clazz*/, jlong pt // JNI registration. static const JNINativeMethod gApkAssetsMethods[] = { - {"nativeLoad", "(ILjava/lang/String;I)J", (void*)NativeLoad}, - {"nativeLoadEmpty", "(I)J", (void*)NativeLoadEmpty}, - {"nativeLoadFd", "(ILjava/io/FileDescriptor;Ljava/lang/String;I)J", (void*)NativeLoadFromFd}, - {"nativeLoadFdOffsets", "(ILjava/io/FileDescriptor;Ljava/lang/String;JJI)J", - (void*)NativeLoadFromFdOffset}, + {"nativeLoad", "(ILjava/lang/String;ILandroid/content/res/loader/AssetsProvider;)J", + (void*)NativeLoad}, + {"nativeLoadEmpty", "(ILandroid/content/res/loader/AssetsProvider;)J", (void*)NativeLoadEmpty}, + {"nativeLoadFd", + "(ILjava/io/FileDescriptor;Ljava/lang/String;ILandroid/content/res/loader/AssetsProvider;)J", + (void*)NativeLoadFromFd}, + {"nativeLoadFdOffsets", + "(ILjava/io/FileDescriptor;Ljava/lang/String;JJILandroid/content/res/loader/AssetsProvider;)J", + (void*)NativeLoadFromFdOffset}, {"nativeDestroy", "(J)V", (void*)NativeDestroy}, {"nativeGetAssetPath", "(J)Ljava/lang/String;", (void*)NativeGetAssetPath}, {"nativeGetStringBlock", "(J)J", (void*)NativeGetStringBlock}, @@ -323,6 +437,21 @@ int register_android_content_res_ApkAssets(JNIEnv* env) { gOverlayableInfoOffsets.constructor = GetMethodIDOrDie(env, gOverlayableInfoOffsets.classObject, "", "(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/assets/base_asset.txt b/core/tests/ResourceLoaderTests/assets/base_asset.txt new file mode 100644 index 0000000000000..8e62cc3462387 --- /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/resources/provider1/assets/loader_asset.txt b/core/tests/ResourceLoaderTests/resources/provider1/assets/loader_asset.txt new file mode 100644 index 0000000000000..0e41ffa475af3 --- /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/provider2/assets/loader_asset.txt b/core/tests/ResourceLoaderTests/resources/provider2/assets/loader_asset.txt new file mode 100644 index 0000000000000..bca782ec1b2bf --- /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/provider3/assets/loader_asset.txt b/core/tests/ResourceLoaderTests/resources/provider3/assets/loader_asset.txt new file mode 100644 index 0000000000000..bae8ef79a2ce4 --- /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/provider4/assets/loader_asset.txt b/core/tests/ResourceLoaderTests/resources/provider4/assets/loader_asset.txt new file mode 100644 index 0000000000000..b75d9963575bb --- /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/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 da5092de06275..0000000000000 --- 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> { - 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 bd2bac50f1002..4764c1008d2fd 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 @@ -20,15 +20,19 @@ import android.content.Context import android.content.res.AssetFileDescriptor import android.content.res.Configuration import android.content.res.Resources +import android.content.res.loader.AssetsProvider import android.content.res.loader.ResourcesProvider import android.os.ParcelFileDescriptor import android.system.Os +import android.util.ArrayMap import androidx.test.InstrumentationRegistry +import org.json.JSONObject import org.junit.After import org.junit.Before import java.io.Closeable import java.io.FileOutputStream import java.io.File +import java.io.FileDescriptor import java.util.zip.ZipInputStream abstract class ResourceLoaderTestBase { @@ -36,6 +40,29 @@ abstract class ResourceLoaderTestBase { protected val PROVIDER_TWO: String = "FrameworksResourceLoaderTests_ProviderTwo" protected val PROVIDER_THREE: String = "FrameworksResourceLoaderTests_ProviderThree" protected val PROVIDER_FOUR: String = "FrameworksResourceLoaderTests_ProviderFour" + protected val PROVIDER_EMPTY: String = "empty" + + companion object { + /** Converts the map to a stable JSON string representation. */ + fun mapToString(m: Map): String { + return JSONObject(ArrayMap().apply { putAll(m) }).toString() + } + + /** Creates a lambda that runs multiple resources queries and concatenates the results. */ + fun query(queries: Map String>): Resources.() -> String { + return { + val resultMap = ArrayMap() + queries.forEach { q -> + resultMap[q.key] = try { + q.value.invoke(this) + } catch (e: Exception) { + e.javaClass.simpleName + } + } + mapToString(resultMap) + } + } + } // Data type of the current test iteration open lateinit var dataType: DataType @@ -65,86 +92,140 @@ abstract class ResourceLoaderTestBase { } } - protected fun String.openProvider(dataType: DataType) - :ResourcesProvider = when (dataType) { - DataType.APK_DISK_FD -> { - val file = context.copiedAssetFile("${this}.apk") - ResourcesProvider.loadFromApk(ParcelFileDescriptor.fromFd(file.fd)).apply { - file.close() + protected fun String.openProvider(dataType: DataType, assetsProvider: MemoryAssetsProvider?) + :ResourcesProvider { + if (assetsProvider != null) { + openedObjects += assetsProvider + } + return when (dataType) { + DataType.APK_DISK_FD -> { + val file = context.copiedAssetFile("${this}.apk") + ResourcesProvider.loadFromApk(ParcelFileDescriptor.fromFd(file.fd), + assetsProvider).apply { + file.close() + } } - } - DataType.APK_DISK_FD_OFFSETS -> { - val asset = context.assets.openFd("${this}.apk") - ResourcesProvider.loadFromApk(asset.parcelFileDescriptor, asset.startOffset, - asset.length, null).apply { - asset.close() + DataType.APK_DISK_FD_OFFSETS -> { + val asset = context.assets.openFd("${this}.apk") + ResourcesProvider.loadFromApk(asset.parcelFileDescriptor, asset.startOffset, + asset.length, assetsProvider).apply { + asset.close() + } } - } - DataType.ARSC_DISK_FD -> { - val file = context.copiedAssetFile("${this}.arsc") - ResourcesProvider.loadFromTable(ParcelFileDescriptor.fromFd(file.fd), null).apply { - file.close() + DataType.ARSC_DISK_FD -> { + val file = context.copiedAssetFile("${this}.arsc") + ResourcesProvider.loadFromTable(ParcelFileDescriptor.fromFd(file.fd), + assetsProvider).apply { + file.close() + } } - } - DataType.ARSC_DISK_FD_OFFSETS -> { - val asset = context.assets.openFd("${this}.arsc") - ResourcesProvider.loadFromTable(asset.parcelFileDescriptor, asset.startOffset, - asset.length, null).apply { - asset.close() + DataType.ARSC_DISK_FD_OFFSETS -> { + val asset = context.assets.openFd("${this}.arsc") + ResourcesProvider.loadFromTable(asset.parcelFileDescriptor, asset.startOffset, + asset.length, assetsProvider).apply { + asset.close() + } } - } - DataType.APK_RAM_OFFSETS -> { - val asset = context.assets.openFd("${this}.apk") - val leadingGarbageSize = 100L - val trailingGarbageSize = 55L - val fd = loadAssetIntoMemory(asset, leadingGarbageSize.toInt(), - trailingGarbageSize.toInt()) - ResourcesProvider.loadFromApk(fd, leadingGarbageSize, asset.declaredLength, - null).apply { - asset.close() - fd.close() + DataType.APK_RAM_OFFSETS -> { + val asset = context.assets.openFd("${this}.apk") + val leadingGarbageSize = 100L + val trailingGarbageSize = 55L + val fd = loadAssetIntoMemory(asset, leadingGarbageSize.toInt(), + trailingGarbageSize.toInt()) + ResourcesProvider.loadFromApk(fd, leadingGarbageSize, asset.declaredLength, + assetsProvider).apply { + asset.close() + fd.close() + } } - } - DataType.APK_RAM_FD -> { - val asset = context.assets.openFd("${this}.apk") - var fd = loadAssetIntoMemory(asset) - ResourcesProvider.loadFromApk(fd).apply { - asset.close() - fd.close() + DataType.APK_RAM_FD -> { + val asset = context.assets.openFd("${this}.apk") + var fd = loadAssetIntoMemory(asset) + ResourcesProvider.loadFromApk(fd, assetsProvider).apply { + asset.close() + fd.close() + } } - } - DataType.ARSC_RAM_MEMORY -> { - val asset = context.assets.openFd("${this}.arsc") - var fd = loadAssetIntoMemory(asset) - ResourcesProvider.loadFromTable(fd, null).apply { - asset.close() - fd.close() + DataType.ARSC_RAM_MEMORY -> { + val asset = context.assets.openFd("${this}.arsc") + var fd = loadAssetIntoMemory(asset) + ResourcesProvider.loadFromTable(fd, assetsProvider).apply { + asset.close() + fd.close() + } } - } - DataType.ARSC_RAM_MEMORY_OFFSETS -> { - val asset = context.assets.openFd("${this}.arsc") - val leadingGarbageSize = 100L - val trailingGarbageSize = 55L - val fd = loadAssetIntoMemory(asset, leadingGarbageSize.toInt(), - trailingGarbageSize.toInt()) - ResourcesProvider.loadFromTable(fd, leadingGarbageSize, asset.declaredLength, - null).apply { - asset.close() - fd.close() + DataType.ARSC_RAM_MEMORY_OFFSETS -> { + val asset = context.assets.openFd("${this}.arsc") + val leadingGarbageSize = 100L + val trailingGarbageSize = 55L + val fd = loadAssetIntoMemory(asset, leadingGarbageSize.toInt(), + trailingGarbageSize.toInt()) + ResourcesProvider.loadFromTable(fd, leadingGarbageSize, asset.declaredLength, + assetsProvider).apply { + asset.close() + fd.close() + } + } + DataType.EMPTY -> { + if (equals(PROVIDER_EMPTY)) { + ResourcesProvider.empty(EmptyAssetsProvider()) + } else { + if (assetsProvider == null) ResourcesProvider.empty(ZipAssetsProvider(this)) + else ResourcesProvider.empty(assetsProvider) + } + } + DataType.DIRECTORY -> { + ResourcesProvider.loadFromDirectory(zipToDir("${this}.apk").absolutePath, + assetsProvider) + } + DataType.SPLIT -> { + ResourcesProvider.loadFromSplit(context, "${this}_Split") } - } - DataType.SPLIT -> { - ResourcesProvider.loadFromSplit(context, "${this}_Split") - } - DataType.DIRECTORY -> { - ResourcesProvider.loadFromDirectory(zipToDir("${this}.apk").absolutePath, null) } } + class EmptyAssetsProvider : AssetsProvider + + /** */ + inner class ZipAssetsProvider(val providerName : String) : AssetsProvider { + val root: File = zipToDir("${providerName}.apk") + + override fun loadAssetFd(path: String, accessMode: Int): AssetFileDescriptor? { + val f = File(root, path) + return if (f.exists()) AssetFileDescriptor( + ParcelFileDescriptor.open(File(root, path), + ParcelFileDescriptor.MODE_READ_ONLY), 0, + AssetFileDescriptor.UNKNOWN_LENGTH) else null + } + } + + /** AssetsProvider for testing that returns file descriptors to files in RAM. */ + class MemoryAssetsProvider : AssetsProvider, Closeable { + var loadAssetResults = HashMap() + + fun addLoadAssetFdResult(path : String, value : String) = apply { + val fd = Os.memfd_create(path, 0) + val valueBytes = value.toByteArray() + Os.write(fd, valueBytes, 0, valueBytes.size) + loadAssetResults[path] = fd + } + + override fun loadAssetFd(path: String, accessMode: Int): AssetFileDescriptor? { + return if (loadAssetResults.containsKey(path)) AssetFileDescriptor( + ParcelFileDescriptor.dup(loadAssetResults[path]), 0, + AssetFileDescriptor.UNKNOWN_LENGTH) else null + } + + override fun close() { + for (f in loadAssetResults.values) { + Os.close(f) + } + } + } /** Extracts an archive-based asset into a directory on disk. */ - private fun zipToDir(name : String, suffix : String = "") : File { - val root = File(context.filesDir, name.split('.')[0] + suffix) + private fun zipToDir(name : String) : File { + val root = File(context.filesDir, name.split('.')[0]) if (root.exists()) { return root } @@ -210,7 +291,8 @@ abstract class ResourceLoaderTestBase { ARSC_DISK_FD_OFFSETS, ARSC_RAM_MEMORY, ARSC_RAM_MEMORY_OFFSETS, - SPLIT, - DIRECTORY + EMPTY, + DIRECTORY, + SPLIT } } 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 c01db0d7428b6..a9945369d1df2 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 @@ -26,9 +26,7 @@ import android.graphics.Color import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.ColorDrawable import android.os.IBinder -import android.util.ArrayMap import androidx.test.rule.ActivityTestRule -import org.json.JSONObject import org.junit.Assert.assertEquals import org.junit.Assert.assertNotEquals import org.junit.Rule @@ -51,26 +49,6 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() { private val mTestActivityRule = ActivityTestRule(TestActivity::class.java) companion object { - /** Converts the map to a stable JSON string representation. */ - private fun mapToString(m : Map) :String { - return JSONObject(ArrayMap().apply { putAll(m) }).toString() - } - - /** Creates a lambda that runs multiple resources queries and concatenates the results. */ - fun query(queries : Map String>) :Resources.() -> String { - return { - val resultMap = ArrayMap() - queries.forEach { q -> - resultMap[q.key] = try { - q.value.invoke(this) - } catch (e : Exception) { - e.javaClass.simpleName - } - } - mapToString(resultMap) - } - } - @Parameterized.Parameters(name = "{1} {0}") @JvmStatic fun parameters(): Array { @@ -109,21 +87,13 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() { // Test resolution of file-based resources and assets with no assets provider. parameters += Parameter( - "fileBased", + "tableFileBased", query(mapOf( // Drawable xml in res directory "drawableXml" to { res -> (res.getDrawable(R.drawable.drawable_xml) as ColorDrawable) .color.toString() }, - // File in the assets directory - "openAsset" to { res -> - res.assets.open("asset.txt").reader().readText() - }, - // From assets directory returning file descriptor - "openAssetFd" to { res -> - res.assets.openFd("asset.txt").readText() - }, // Asset as compiled XML layout in res directory "layout" to { res -> res.getLayout(R.layout.layout).advanceToRoot().name @@ -135,38 +105,109 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() { } )), mapOf("drawableXml" to Color.parseColor("#B2D2F2").toString(), - "openAsset" to "In assets directory", - "openAssetFd" to "In assets directory", "layout" to "MysteryLayout", "drawablePng" to Color.parseColor("#FF00FF").toString()), mapOf("drawableXml" to Color.parseColor("#000001").toString(), - "openAsset" to "One", - "openAssetFd" to "One", "layout" to "RelativeLayout", "drawablePng" to Color.RED.toString()), mapOf("drawableXml" to Color.parseColor("#000002").toString(), - "openAsset" to "Two", - "openAssetFd" to "Two", "layout" to "LinearLayout", "drawablePng" to Color.GREEN.toString()), mapOf("drawableXml" to Color.parseColor("#000003").toString(), - "openAsset" to "Three", - "openAssetFd" to "Three", "layout" to "FrameLayout", "drawablePng" to Color.BLUE.toString()), mapOf("drawableXml" to Color.parseColor("#000004").toString(), - "openAsset" to "Four", - "openAssetFd" to "Four", "layout" to "TableLayout", "drawablePng" to Color.WHITE.toString()), listOf(DataType.APK_DISK_FD, DataType.APK_DISK_FD_OFFSETS, DataType.APK_RAM_FD, DataType.APK_RAM_OFFSETS, DataType.SPLIT, DataType.DIRECTORY) ) + // Test resolution of assets. + parameters += Parameter( + "fileBased", + query(mapOf( + // File in the assets directory + "openAsset" to { res -> + res.assets.open("asset.txt").reader().readText() + }, + // From assets directory returning file descriptor + "openAssetFd" to { res -> + res.assets.openFd("asset.txt").readText() + }, + // Asset as compiled XML layout in res directory + "layout" to { res -> + res.assets.openXmlResourceParser("res/layout/layout.xml") + .advanceToRoot().name + } + )), + mapOf("openAsset" to "In assets directory", + "openAssetFd" to "In assets directory", + "layout" to "MysteryLayout"), + + mapOf("openAsset" to "One", + "openAssetFd" to "One", + "layout" to "RelativeLayout"), + + mapOf("openAsset" to "Two", + "openAssetFd" to "Two", + "layout" to "LinearLayout"), + + mapOf("openAsset" to "Three", + "openAssetFd" to "Three", + "layout" to "FrameLayout"), + + mapOf("openAsset" to "Four", + "openAssetFd" to "Four", + "layout" to "TableLayout"), + listOf(DataType.EMPTY) + ) + + // Test assets from apk and provider + parameters += Parameter( + "fileBasedApkAssetsProvider", + query(mapOf( + // File in the assets directory + "openAsset" to { res -> + res.assets.open("asset.txt").reader().readText() + }, + // From assets directory returning file descriptor + "openAssetFd" to { res -> + res.assets.openFd("asset.txt").readText() + } + )), + mapOf("openAsset" to "In assets directory", + "openAssetFd" to "In assets directory"), + + mapOf("openAsset" to "AssetsOne", + "openAssetFd" to "AssetsOne"), + { MemoryAssetsProvider().addLoadAssetFdResult("assets/asset.txt", + "AssetsOne") }, + + mapOf("openAsset" to "Two", + "openAssetFd" to "Two"), + null /* assetProviderTwo */, + + mapOf("openAsset" to "AssetsThree", + "openAssetFd" to "AssetsThree"), + { MemoryAssetsProvider().addLoadAssetFdResult("assets/asset.txt", + "AssetsThree") }, + + mapOf("openAsset" to "Four", + "openAssetFd" to "Four"), + null /* assetProviderFour */, + listOf(DataType.APK_DISK_FD, DataType.APK_DISK_FD_OFFSETS, DataType.APK_RAM_FD, + DataType.APK_RAM_OFFSETS, DataType.DIRECTORY) + + ) + + // TODO(151949807): Increase testing for cookie based APIs and for what happens when + // some providers do not overlay base resources + return parameters.flatMap { parameter -> parameter.dataTypes.map { dataType -> arrayOf(dataType, parameter) @@ -188,10 +229,15 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() { private val valueThree by lazy { mapToString(parameter.valueThree) } private val valueFour by lazy { mapToString(parameter.valueFour) } - private fun openOne() = PROVIDER_ONE.openProvider(dataType) - private fun openTwo() = PROVIDER_TWO.openProvider(dataType) - private fun openThree() = PROVIDER_THREE.openProvider(dataType) - private fun openFour() = PROVIDER_FOUR.openProvider(dataType) + private fun openOne() = PROVIDER_ONE.openProvider(dataType, + parameter.assetProviderOne?.invoke()) + private fun openTwo() = PROVIDER_TWO.openProvider(dataType, + parameter.assetProviderTwo?.invoke()) + private fun openThree() = PROVIDER_THREE.openProvider(dataType, + parameter.assetProviderThree?.invoke()) + private fun openFour() = PROVIDER_FOUR.openProvider(dataType, + parameter.assetProviderFour?.invoke()) + private fun openEmpty() = PROVIDER_EMPTY.openProvider(DataType.EMPTY, null) // Class method for syntax highlighting purposes private fun getValue(c: Context = context) = parameter.getValue(c.resources) @@ -289,6 +335,27 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() { assertEquals(valueOriginal, getValue()) } + @Test + fun emptyProvider() { + val testOne = openOne() + val testTwo = openTwo() + val testEmpty = openEmpty() + val loader = ResourcesLoader() + + resources.addLoaders(loader) + loader.providers = listOf(testOne, testEmpty, testTwo) + assertEquals(valueTwo, getValue()) + + loader.removeProvider(testTwo) + assertEquals(valueOne, getValue()) + + loader.removeProvider(testOne) + assertEquals(valueOriginal, getValue()) + + loader.providers = Collections.emptyList() + assertEquals(valueOriginal, getValue()) + } + @Test(expected = UnsupportedOperationException::class) fun getProvidersDoesNotLeakMutability() { val testOne = openOne() @@ -476,6 +543,9 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() { loader1.removeProvider(testOne) assertEquals(valueFour, getValue()) + + loader2.removeProvider(testFour) + assertEquals(valueThree, getValue()) } private fun createContext(context: Context, id: Int): Context { @@ -644,15 +714,29 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() { } data class Parameter( - val testPrefix: String, - val getValue: Resources.() -> String, - val valueOriginal: Map, - val valueOne: Map, - val valueTwo: Map, - val valueThree: Map, - val valueFour: Map, - val dataTypes: List + val testPrefix: String, + val getValue: Resources.() -> String, + val valueOriginal: Map, + val valueOne: Map, + val assetProviderOne: (() -> MemoryAssetsProvider)? = null, + val valueTwo: Map, + val assetProviderTwo: (() -> MemoryAssetsProvider)? = null, + val valueThree: Map, + val assetProviderThree: (() -> MemoryAssetsProvider)? = null, + val valueFour: Map, + val assetProviderFour: (() -> MemoryAssetsProvider)? = null, + val dataTypes: List ) { + constructor(testPrefix: String, + getValue: Resources.() -> String, + valueOriginal : Map, + valueOne: Map, + valueTwo: Map, + valueThree: Map, + valueFour: Map, + dataTypes: List): this(testPrefix, getValue, valueOriginal, valueOne, + null, valueTwo, null, valueThree, null, valueFour, null, dataTypes) + override fun toString() = testPrefix } } diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp index f5bf84f18a89d..202651dc86d58 100644 --- a/libs/androidfw/ApkAssets.cpp +++ b/libs/androidfw/ApkAssets.cpp @@ -144,8 +144,8 @@ class ZipAssetsProvider : public AssetsProvider { } protected: - std::unique_ptr OpenInternal( - const std::string& path, Asset::AccessMode mode, bool* file_exists) const override { + std::unique_ptr OpenInternal( + const std::string& path, Asset::AccessMode mode, bool* file_exists) const override { if (file_exists) { *file_exists = false; } @@ -292,49 +292,91 @@ class EmptyAssetsProvider : public AssetsProvider { DISALLOW_COPY_AND_ASSIGN(EmptyAssetsProvider); }; -std::unique_ptr ApkAssets::Load(const std::string& path, - const package_property_t flags) { +// AssetProvider implementation +class MultiAssetsProvider : public AssetsProvider { + public: + ~MultiAssetsProvider() override = default; + + static std::unique_ptr Create( + std::unique_ptr child, std::unique_ptr parent) { + CHECK(parent != nullptr) << "parent provider must not be null"; + return (!child) ? std::move(parent) + : std::unique_ptr(new MultiAssetsProvider( + std::move(child), std::move(parent))); + } + + bool ForEachFile(const std::string& root_path, + const std::function& 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 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 child, + std::unique_ptr parent) + : child_(std::move(child)), parent_(std::move(parent)) { } + + std::unique_ptr child_; + std::unique_ptr parent_; +}; + +// Opens the archive using the file path. Calling CloseArchive on the zip handle will close the +// file. +std::unique_ptr ApkAssets::Load( + const std::string& path, const package_property_t flags, + std::unique_ptr override_asset) { auto assets = ZipAssetsProvider::Create(path); - return (assets) ? LoadImpl(std::move(assets), path, nullptr /*idmap_asset*/, - nullptr /*loaded_idmap*/, flags) + return (assets) ? LoadImpl(std::move(assets), path, flags, std::move(override_asset)) : nullptr; } -std::unique_ptr ApkAssets::LoadFromFd(unique_fd fd, - const std::string& friendly_name, - const package_property_t flags, - const off64_t offset, - const off64_t length) { +// Opens the archive using the file file descriptor with the specified file offset and read length. +// If the `assume_ownership` parameter is 'true' calling CloseArchive will close the file. +std::unique_ptr ApkAssets::LoadFromFd( + unique_fd fd, const std::string& friendly_name, const package_property_t flags, + std::unique_ptr override_asset, const off64_t offset, + const off64_t length) { CHECK(length >= kUnknownLength) << "length must be greater than or equal to " << kUnknownLength; CHECK(length != kUnknownLength || offset == 0) << "offset must be 0 if length is " << kUnknownLength; + auto assets = ZipAssetsProvider::Create(std::move(fd), friendly_name, offset, length); - return (assets) ? LoadImpl(std::move(assets), friendly_name, nullptr /*idmap_asset*/, - nullptr /*loaded_idmap*/, flags) + return (assets) ? LoadImpl(std::move(assets), friendly_name, flags, std::move(override_asset)) : nullptr; } -std::unique_ptr ApkAssets::LoadTable(const std::string& path, - const package_property_t flags) { - auto resources_asset = CreateAssetFromFile(path); - return (resources_asset) ? LoadTableImpl(std::move(resources_asset), path, flags) - : nullptr; +std::unique_ptr ApkAssets::LoadTable( + const std::string& path, const package_property_t flags, + std::unique_ptr override_asset) { + + auto assets = CreateAssetFromFile(path); + return (assets) ? LoadTableImpl(std::move(assets), path, flags, std::move(override_asset)) + : nullptr; } -std::unique_ptr ApkAssets::LoadTableFromFd(unique_fd fd, - const std::string& friendly_name, - const package_property_t flags, - const off64_t offset, - const off64_t length) { - auto resources_asset = CreateAssetFromFd(std::move(fd), nullptr /* path */, offset, length); - return (resources_asset) ? LoadTableImpl(std::move(resources_asset), friendly_name, flags) - : nullptr; +std::unique_ptr ApkAssets::LoadTableFromFd( + unique_fd fd, const std::string& friendly_name, const package_property_t flags, + std::unique_ptr 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 ApkAssets::LoadOverlay(const std::string& idmap_path, const package_property_t flags) { CHECK((flags & PROPERTY_LOADER) == 0U) << "Cannot load RROs through loaders"; - std::unique_ptr idmap_asset = CreateAssetFromFile(idmap_path); if (idmap_asset == nullptr) { return {}; @@ -351,23 +393,28 @@ std::unique_ptr ApkAssets::LoadOverlay(const std::string& idmap auto overlay_path = loaded_idmap->OverlayApkPath(); auto assets = ZipAssetsProvider::Create(overlay_path); - return (assets) ? LoadImpl(std::move(assets), overlay_path, std::move(idmap_asset), - std::move(loaded_idmap), flags | PROPERTY_OVERLAY) + return (assets) ? LoadImpl(std::move(assets), overlay_path, flags | PROPERTY_OVERLAY, + nullptr /* override_asset */, std::move(idmap_asset), + std::move(loaded_idmap)) : nullptr; } -std::unique_ptr ApkAssets::LoadFromDir(const std::string& path, - const package_property_t flags) { +std::unique_ptr ApkAssets::LoadFromDir( + const std::string& path, const package_property_t flags, + std::unique_ptr override_asset) { + auto assets = DirectoryAssetsProvider::Create(path); - return (assets) ? LoadImpl(std::move(assets), path, nullptr /*idmap_asset*/, - nullptr /*loaded_idmap*/, flags) + return (assets) ? LoadImpl(std::move(assets), path, flags, std::move(override_asset)) : nullptr; } -std::unique_ptr ApkAssets::LoadEmpty(const package_property_t flags) { - std::unique_ptr loaded_apk(new ApkAssets( - std::unique_ptr(new EmptyAssetsProvider()), "empty" /* path */, - -1 /* last_mod-time */, flags)); +std::unique_ptr ApkAssets::LoadEmpty( + const package_property_t flags, std::unique_ptr override_asset) { + + auto assets = (override_asset) ? std::move(override_asset) + : std::unique_ptr(new EmptyAssetsProvider()); + std::unique_ptr loaded_apk(new ApkAssets(std::move(assets), "empty" /* path */, + -1 /* last_mod-time */, flags)); loaded_apk->loaded_arsc_ = LoadedArsc::CreateEmpty(); // Need to force a move for mingw32. return std::move(loaded_apk); @@ -413,27 +460,30 @@ std::unique_ptr ApkAssets::CreateAssetFromFd(base::unique_fd fd, Asset::AccessMode::ACCESS_RANDOM); } -std::unique_ptr ApkAssets::LoadImpl(std::unique_ptr assets, - const std::string& path, - std::unique_ptr idmap_asset, - std::unique_ptr idmap, - package_property_t property_flags) { +std::unique_ptr ApkAssets::LoadImpl( + std::unique_ptr assets, const std::string& path, + package_property_t property_flags, std::unique_ptr override_assets, + std::unique_ptr idmap_asset, std::unique_ptr idmap) { + const time_t last_mod_time = getFileModDate(path.c_str()); + // Open the resource table via mmap unless it is compressed. This logic is taken care of by Open. + bool resources_asset_exists = false; + auto resources_asset_ = assets->Open(kResourcesArsc, Asset::AccessMode::ACCESS_BUFFER, + &resources_asset_exists); + + assets = MultiAssetsProvider::Create(std::move(override_assets), std::move(assets)); + // Wrap the handle in a unique_ptr so it gets automatically closed. std::unique_ptr loaded_apk(new ApkAssets(std::move(assets), path, last_mod_time, property_flags)); - // Open the resource table via mmap unless it is compressed. This logic is taken care of by Open. - bool resources_asset_exists = false; - loaded_apk->resources_asset_ = loaded_apk->assets_provider_->Open( - kResourcesArsc, Asset::AccessMode::ACCESS_BUFFER, &resources_asset_exists); - if (!resources_asset_exists) { loaded_apk->loaded_arsc_ = LoadedArsc::CreateEmpty(); return std::move(loaded_apk); } + loaded_apk->resources_asset_ = std::move(resources_asset_); if (!loaded_apk->resources_asset_) { LOG(ERROR) << "Failed to open '" << kResourcesArsc << "' in APK '" << path << "'."; return {}; @@ -457,14 +507,17 @@ std::unique_ptr ApkAssets::LoadImpl(std::unique_ptr ApkAssets::LoadTableImpl(std::unique_ptr resources_asset, - const std::string& path, - package_property_t property_flags) { +std::unique_ptr ApkAssets::LoadTableImpl( + std::unique_ptr resources_asset, const std::string& path, + package_property_t property_flags, std::unique_ptr override_assets) { + const time_t last_mod_time = getFileModDate(path.c_str()); + auto assets = (override_assets) ? std::move(override_assets) + : std::unique_ptr(new EmptyAssetsProvider()); + std::unique_ptr loaded_apk( - new ApkAssets(std::unique_ptr(new EmptyAssetsProvider()), path, last_mod_time, - property_flags)); + new ApkAssets(std::move(assets), path, last_mod_time, property_flags)); loaded_apk->resources_asset_ = std::move(resources_asset); const StringPiece data( diff --git a/libs/androidfw/include/androidfw/ApkAssets.h b/libs/androidfw/include/androidfw/ApkAssets.h index 944476890af04..879b050b65bd5 100644 --- a/libs/androidfw/include/androidfw/ApkAssets.h +++ b/libs/androidfw/include/androidfw/ApkAssets.h @@ -75,33 +75,33 @@ class ApkAssets { // Creates an ApkAssets. // If `system` is true, the package is marked as a system package, and allows some functions to // filter out this package when computing what configurations/resources are available. - static std::unique_ptr Load(const std::string& path, - package_property_t flags = 0U); + static std::unique_ptr Load( + const std::string& path, package_property_t flags = 0U, + std::unique_ptr override_asset = nullptr); // Creates an ApkAssets from the given file descriptor, and takes ownership of the file // descriptor. The `friendly_name` is some name that will be used to identify the source of // this ApkAssets in log messages and other debug scenarios. // If `length` equals kUnknownLength, offset must equal 0; otherwise, the apk data will be read // using the `offset` into the file descriptor and will be `length` bytes long. - static std::unique_ptr LoadFromFd(base::unique_fd fd, - const std::string& friendly_name, - package_property_t flags = 0U, - off64_t offset = 0, - off64_t length = kUnknownLength); + static std::unique_ptr LoadFromFd( + base::unique_fd fd, const std::string& friendly_name, package_property_t flags = 0U, + std::unique_ptr 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 LoadTable(const std::string& path, - package_property_t flags = 0U); + static std::unique_ptr LoadTable( + const std::string& path, package_property_t flags = 0U, + std::unique_ptr 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 LoadTableFromFd(base::unique_fd fd, - const std::string& friendly_name, - package_property_t flags = 0U, - off64_t offset = 0, - off64_t length = kUnknownLength); + static std::unique_ptr LoadTableFromFd( + base::unique_fd fd, const std::string& friendly_name, package_property_t flags = 0U, + std::unique_ptr override_asset = nullptr, off64_t offset = 0, + off64_t length = kUnknownLength); // Creates an ApkAssets from an IDMAP, which contains the original APK path, and the overlay // data. @@ -110,11 +110,14 @@ class ApkAssets { // Creates an ApkAssets from the directory path. File-based resources are read within the // directory as if the directory is an APK. - static std::unique_ptr LoadFromDir(const std::string& path, - package_property_t flags = 0U); + static std::unique_ptr LoadFromDir( + const std::string& path, package_property_t flags = 0U, + std::unique_ptr override_asset = nullptr); // Creates a totally empty ApkAssets with no resources table and no file entries. - static std::unique_ptr LoadEmpty(package_property_t flags = 0U); + static std::unique_ptr LoadEmpty( + package_property_t flags = 0U, + std::unique_ptr override_asset = nullptr); inline const std::string& GetPath() const { return path_; @@ -158,15 +161,17 @@ class ApkAssets { private: DISALLOW_COPY_AND_ASSIGN(ApkAssets); - static std::unique_ptr LoadImpl(std::unique_ptr assets, - const std::string& path, - std::unique_ptr idmap_asset, - std::unique_ptr idmap, - package_property_t property_flags); + static std::unique_ptr LoadImpl( + std::unique_ptr assets, const std::string& path, + package_property_t property_flags, + std::unique_ptr override_assets = nullptr, + std::unique_ptr idmap_asset = nullptr, + std::unique_ptr idmap = nullptr); - static std::unique_ptr LoadTableImpl(std::unique_ptr resources_asset, - const std::string& path, - package_property_t property_flags); + static std::unique_ptr LoadTableImpl( + std::unique_ptr resources_asset, const std::string& path, + package_property_t property_flags, + std::unique_ptr override_assets = nullptr); ApkAssets(std::unique_ptr assets_provider, std::string path, diff --git a/startop/view_compiler/apk_layout_compiler.cc b/startop/view_compiler/apk_layout_compiler.cc index e70c68852b679..eaa3e04cc8147 100644 --- a/startop/view_compiler/apk_layout_compiler.cc +++ b/startop/view_compiler/apk_layout_compiler.cc @@ -168,8 +168,7 @@ void CompileApkLayouts(const std::string& filename, CompilationTarget target, void CompileApkLayoutsFd(android::base::unique_fd fd, CompilationTarget target, std::ostream& target_out) { constexpr const char* friendly_name{"viewcompiler assets"}; - auto assets = android::ApkAssets::LoadFromFd( - std::move(fd), friendly_name, /*system=*/false, /*force_shared_lib=*/false); + auto assets = android::ApkAssets::LoadFromFd(std::move(fd), friendly_name); CompileApkAssetsLayouts(assets, target, target_out); } From 39cacf2de79e201ac77e7e1100a7b0ba29abb8f5 Mon Sep 17 00:00:00 2001 From: Ryan Mitchell Date: Mon, 16 Mar 2020 14:54:02 -0700 Subject: [PATCH 7/7] Allow using loaders on non-RM Resources instances Currently there is a limitation where ResourcesLoaders cannot be used on Resources object not created through ResourcesManager. This change creates an update handler for Resources objects that are not registered with ResourcesManager. The handler changes the loaders on the asset manager owned by the Resources instance. Bug: 151666644 Test: atest ResourceLoaderValuesTest Change-Id: I5a89f686386bdb088dc964014e7becc0c2b4770f --- core/java/android/app/ResourcesManager.java | 5 +- .../android/content/res/AssetManager.java | 43 +++++- core/java/android/content/res/Resources.java | 37 +++++- .../res/loader/test/ResourceLoaderTestBase.kt | 46 +++---- .../loader/test/ResourceLoaderValuesTest.kt | 123 ++++++++++++++---- libs/androidfw/ApkAssets.cpp | 2 +- libs/androidfw/tests/ApkAssets_test.cpp | 3 +- libs/androidfw/tests/Theme_test.cpp | 2 +- 8 files changed, 198 insertions(+), 63 deletions(-) diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java index 60f61cef0b61f..5f756033390b8 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -1288,7 +1288,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) { @@ -1316,7 +1317,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/res/AssetManager.java b/core/java/android/content/res/AssetManager.java index 7b2b93949a9fb..d2103af1d247b 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -148,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); } } @@ -328,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`. * diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index e77d8af498730..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(); } } 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 4764c1008d2fd..ec6a605340ae1 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 @@ -68,8 +68,7 @@ abstract class ResourceLoaderTestBase { open lateinit var dataType: DataType protected lateinit var context: Context - protected open val resources: Resources - get() = context.resources + protected lateinit var resources: Resources // Track opened streams and ResourcesProviders to close them after testing private val openedObjects = mutableListOf() @@ -78,6 +77,7 @@ abstract class ResourceLoaderTestBase { fun setUpBase() { context = InstrumentationRegistry.getTargetContext() .createConfigurationContext(Configuration()) + resources = context.resources } @After @@ -92,42 +92,42 @@ abstract class ResourceLoaderTestBase { } } - protected fun String.openProvider(dataType: DataType, assetsProvider: MemoryAssetsProvider?) - :ResourcesProvider { + protected fun String.openProvider(dataType: DataType, + assetsProvider: MemoryAssetsProvider?): ResourcesProvider { if (assetsProvider != null) { openedObjects += assetsProvider } return when (dataType) { DataType.APK_DISK_FD -> { - val file = context.copiedAssetFile("${this}.apk") + 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") + 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") + 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") + 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 asset = context.assets.openFd("$this.apk") val leadingGarbageSize = 100L val trailingGarbageSize = 55L val fd = loadAssetIntoMemory(asset, leadingGarbageSize.toInt(), @@ -139,7 +139,7 @@ abstract class ResourceLoaderTestBase { } } DataType.APK_RAM_FD -> { - val asset = context.assets.openFd("${this}.apk") + val asset = context.assets.openFd("$this.apk") var fd = loadAssetIntoMemory(asset) ResourcesProvider.loadFromApk(fd, assetsProvider).apply { asset.close() @@ -147,7 +147,7 @@ abstract class ResourceLoaderTestBase { } } DataType.ARSC_RAM_MEMORY -> { - val asset = context.assets.openFd("${this}.arsc") + val asset = context.assets.openFd("$this.arsc") var fd = loadAssetIntoMemory(asset) ResourcesProvider.loadFromTable(fd, assetsProvider).apply { asset.close() @@ -155,7 +155,7 @@ abstract class ResourceLoaderTestBase { } } DataType.ARSC_RAM_MEMORY_OFFSETS -> { - val asset = context.assets.openFd("${this}.arsc") + val asset = context.assets.openFd("$this.arsc") val leadingGarbageSize = 100L val trailingGarbageSize = 55L val fd = loadAssetIntoMemory(asset, leadingGarbageSize.toInt(), @@ -175,7 +175,7 @@ abstract class ResourceLoaderTestBase { } } DataType.DIRECTORY -> { - ResourcesProvider.loadFromDirectory(zipToDir("${this}.apk").absolutePath, + ResourcesProvider.loadFromDirectory(zipToDir("$this.apk").absolutePath, assetsProvider) } DataType.SPLIT -> { @@ -186,9 +186,9 @@ abstract class ResourceLoaderTestBase { class EmptyAssetsProvider : AssetsProvider - /** */ - inner class ZipAssetsProvider(val providerName : String) : AssetsProvider { - val root: File = zipToDir("${providerName}.apk") + /** 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) @@ -203,7 +203,7 @@ abstract class ResourceLoaderTestBase { class MemoryAssetsProvider : AssetsProvider, Closeable { var loadAssetResults = HashMap() - fun addLoadAssetFdResult(path : String, value : String) = apply { + 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) @@ -224,7 +224,7 @@ abstract class ResourceLoaderTestBase { } /** Extracts an archive-based asset into a directory on disk. */ - private fun zipToDir(name : String) : File { + private fun zipToDir(name: String): File { val root = File(context.filesDir, name.split('.')[0]) if (root.exists()) { return root @@ -252,13 +252,13 @@ abstract class ResourceLoaderTestBase { return root } - /** Loads the asset into a temporary file stored in RAM. */ - private fun loadAssetIntoMemory(asset: AssetFileDescriptor, - leadingGarbageSize: Int = 0, - trailingGarbageSize: Int = 0 + private fun loadAssetIntoMemory( + asset: AssetFileDescriptor, + leadingGarbageSize: Int = 0, + trailingGarbageSize: Int = 0 ): ParcelFileDescriptor { - val originalFd = Os.memfd_create(asset.toString(), 0 /* flags */); + val originalFd = Os.memfd_create(asset.toString(), 0 /* flags */) val fd = ParcelFileDescriptor.dup(originalFd) Os.close(originalFd) 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 a9945369d1df2..5aa8814c7481a 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 @@ -63,22 +64,40 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() { }, "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"), + "getAdditional" to "NotFoundException", + "getIdentifier" to "Not overlaid", + "getIdentifierAdditional" to "NotFoundException"), mapOf("getOverlaid" to "One", - "getAdditional" to "One"), + "getAdditional" to "One", + "getIdentifier" to "One", + "getIdentifierAdditional" to "One"), mapOf("getOverlaid" to "Two", - "getAdditional" to "Two"), + "getAdditional" to "Two", + "getIdentifier" to "Two", + "getIdentifierAdditional" to "Two"), mapOf("getOverlaid" to "Three", - "getAdditional" to "Three"), + "getAdditional" to "Three", + "getIdentifier" to "Three", + "getIdentifierAdditional" to "Three"), mapOf("getOverlaid" to "Four", - "getAdditional" 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, @@ -224,7 +243,7 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() { lateinit var parameter: Parameter private val valueOriginal by lazy { mapToString(parameter.valueOriginal) } - private val valueOne by lazy { mapToString(parameter.valueOne) } + 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) } @@ -241,6 +260,7 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() { // 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() { @@ -713,28 +733,79 @@ 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 testPrefix: String, - val getValue: Resources.() -> String, - val valueOriginal: Map, - val valueOne: Map, - val assetProviderOne: (() -> MemoryAssetsProvider)? = null, - val valueTwo: Map, - val assetProviderTwo: (() -> MemoryAssetsProvider)? = null, - val valueThree: Map, - val assetProviderThree: (() -> MemoryAssetsProvider)? = null, - val valueFour: Map, - val assetProviderFour: (() -> MemoryAssetsProvider)? = null, - val dataTypes: List + val testPrefix: String, + val getValue: Resources.() -> String, + val valueOriginal: Map, + val valueOne: Map, + val assetProviderOne: (() -> MemoryAssetsProvider)? = null, + val valueTwo: Map, + val assetProviderTwo: (() -> MemoryAssetsProvider)? = null, + val valueThree: Map, + val assetProviderThree: (() -> MemoryAssetsProvider)? = null, + val valueFour: Map, + val assetProviderFour: (() -> MemoryAssetsProvider)? = null, + val dataTypes: List ) { - constructor(testPrefix: String, - getValue: Resources.() -> String, - valueOriginal : Map, - valueOne: Map, - valueTwo: Map, - valueThree: Map, - valueFour: Map, - dataTypes: List): this(testPrefix, getValue, valueOriginal, valueOne, + constructor( + testPrefix: String, + getValue: Resources.() -> String, + valueOriginal: Map, + valueOne: Map, + valueTwo: Map, + valueThree: Map, + valueFour: Map, + dataTypes: List + ): this(testPrefix, getValue, valueOriginal, valueOne, null, valueTwo, null, valueThree, null, valueFour, null, dataTypes) override fun toString() = testPrefix diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp index 202651dc86d58..918e7af12d312 100644 --- a/libs/androidfw/ApkAssets.cpp +++ b/libs/androidfw/ApkAssets.cpp @@ -471,7 +471,7 @@ std::unique_ptr ApkAssets::LoadImpl( bool resources_asset_exists = false; auto resources_asset_ = assets->Open(kResourcesArsc, Asset::AccessMode::ACCESS_BUFFER, &resources_asset_exists); - + assets = MultiAssetsProvider::Create(std::move(override_assets), std::move(assets)); // Wrap the handle in a unique_ptr so it gets automatically closed. diff --git a/libs/androidfw/tests/ApkAssets_test.cpp b/libs/androidfw/tests/ApkAssets_test.cpp index ce9e53244801b..19db25ce88116 100644 --- a/libs/androidfw/tests/ApkAssets_test.cpp +++ b/libs/androidfw/tests/ApkAssets_test.cpp @@ -50,8 +50,7 @@ TEST(ApkAssetsTest, LoadApkFromFd) { unique_fd fd(::open(path.c_str(), O_RDONLY | O_BINARY)); ASSERT_THAT(fd.get(), Ge(0)); - std::unique_ptr loaded_apk = - ApkAssets::LoadFromFd(std::move(fd), path, false /*system*/, false /*force_shared_lib*/); + std::unique_ptr loaded_apk = ApkAssets::LoadFromFd(std::move(fd), path); ASSERT_THAT(loaded_apk, NotNull()); const LoadedArsc* loaded_arsc = loaded_apk->GetLoadedArsc(); diff --git a/libs/androidfw/tests/Theme_test.cpp b/libs/androidfw/tests/Theme_test.cpp index be5ecd94a588b..16b9c75982fbc 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");