diff options
author | Ryan Mitchell <rtmitchell@google.com> | 2020-03-11 13:15:28 -0700 |
---|---|---|
committer | Ryan Mitchell <rtmitchell@google.com> | 2020-03-19 18:33:55 -0700 |
commit | 4ea1e4288985508e3e0f21febe4da242c86a7dd1 (patch) | |
tree | e66c14e354dab6b573f4ca8dd643642e35ef54d2 | |
parent | c07aa702703388747bd6e9b1091127e2736ffcd8 (diff) |
Move AssetsProvider to native layer
Querying in the native layer for assets provided through
AssetsProviders does not currently work. This change refactors the
AssetProvider API to return a file descriptor that is read in the
native layer and can bubble up to the java layer.
This change also removes the InputStream API to favor of developers
using memfd_create.
Bug: 142716192
Test: atest ResourceLoaderValuesTest
Change-Id: I1a7eca0994c3b7cc32008d9a72bf91086ff0e816
21 files changed, 672 insertions, 762 deletions
diff --git a/api/current.txt b/api/current.txt index 5ae65901fec8..8a35cc779ceb 100644 --- a/api/current.txt +++ b/api/current.txt @@ -12846,14 +12846,7 @@ package android.content.res { package android.content.res.loader { public interface AssetsProvider { - method @Nullable public default java.io.InputStream loadAsset(@NonNull String, int) throws java.io.IOException; - method @Nullable public default android.os.ParcelFileDescriptor loadAssetParcelFd(@NonNull String) throws java.io.IOException; - } - - public class DirectoryAssetsProvider implements android.content.res.loader.AssetsProvider { - ctor public DirectoryAssetsProvider(@NonNull java.io.File); - method @Nullable public java.io.File findFile(@NonNull String); - method @NonNull public java.io.File getDirectory(); + method @Nullable public default android.content.res.AssetFileDescriptor loadAssetFd(@NonNull String, int); } public class ResourcesLoader { @@ -12868,7 +12861,6 @@ package android.content.res.loader { public class ResourcesProvider implements java.lang.AutoCloseable java.io.Closeable { method public void close(); method @NonNull public static android.content.res.loader.ResourcesProvider empty(@NonNull android.content.res.loader.AssetsProvider); - method @Nullable public android.content.res.loader.AssetsProvider getAssetsProvider(); method @NonNull public static android.content.res.loader.ResourcesProvider loadFromApk(@NonNull android.os.ParcelFileDescriptor) throws java.io.IOException; method @NonNull public static android.content.res.loader.ResourcesProvider loadFromApk(@NonNull android.os.ParcelFileDescriptor, @Nullable android.content.res.loader.AssetsProvider) throws java.io.IOException; method @NonNull public static android.content.res.loader.ResourcesProvider loadFromDirectory(@NonNull String, @Nullable android.content.res.loader.AssetsProvider) throws java.io.IOException; diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 5ade26153296..24589cfec27a 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -1441,7 +1441,7 @@ public class PackageParser { try { try { apkAssets = fd != null - ? ApkAssets.loadFromFd(fd, debugPathName, 0 /* flags */) + ? ApkAssets.loadFromFd(fd, debugPathName, 0 /* flags */, null /* assets */) : ApkAssets.loadFromPath(apkPath); } catch (IOException e) { throw new PackageParserException(INSTALL_PARSE_FAILED_NOT_APK, diff --git a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java index 88b4c290c52a..27399e4b39bc 100644 --- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java +++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java @@ -220,7 +220,7 @@ public class ApkLiteParseUtils { try { try { apkAssets = fd != null - ? ApkAssets.loadFromFd(fd, debugPathName, 0 /* flags */) + ? ApkAssets.loadFromFd(fd, debugPathName, 0 /* flags */, null /* assets */) : ApkAssets.loadFromPath(apkPath); } catch (IOException e) { throw new PackageParser.PackageParserException( diff --git a/core/java/android/content/res/ApkAssets.java b/core/java/android/content/res/ApkAssets.java index 078d175b9a80..bc418061e1d1 100644 --- a/core/java/android/content/res/ApkAssets.java +++ b/core/java/android/content/res/ApkAssets.java @@ -20,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.content.om.OverlayableInfo; +import android.content.res.loader.AssetsProvider; import android.content.res.loader.ResourcesProvider; import com.android.internal.annotations.GuardedBy; @@ -112,6 +113,9 @@ public final class ApkAssets { @PropertyFlags private final int mFlags; + @Nullable + private final AssetsProvider mAssets; + /** * Creates a new ApkAssets instance from the given path on disk. * @@ -133,7 +137,21 @@ public final class ApkAssets { */ public static @NonNull ApkAssets loadFromPath(@NonNull String path, @PropertyFlags int flags) throws IOException { - return new ApkAssets(FORMAT_APK, path, flags); + return new ApkAssets(FORMAT_APK, path, flags, null /* assets */); + } + + /** + * Creates a new ApkAssets instance from the given path on disk. + * + * @param path The path to an APK on disk. + * @param flags flags that change the behavior of loaded apk assets + * @param assets The assets provider that overrides the loading of file-based resources + * @return a new instance of ApkAssets. + * @throws IOException if a disk I/O error or parsing error occurred. + */ + public static @NonNull ApkAssets loadFromPath(@NonNull String path, @PropertyFlags int flags, + @Nullable AssetsProvider assets) throws IOException { + return new ApkAssets(FORMAT_APK, path, flags, assets); } /** @@ -145,12 +163,14 @@ public final class ApkAssets { * @param fd The FileDescriptor of an open, readable APK. * @param friendlyName The friendly name used to identify this ApkAssets when logging. * @param flags flags that change the behavior of loaded apk assets + * @param assets The assets provider that overrides the loading of file-based resources * @return a new instance of ApkAssets. * @throws IOException if a disk I/O error or parsing error occurred. */ public static @NonNull ApkAssets loadFromFd(@NonNull FileDescriptor fd, - @NonNull String friendlyName, @PropertyFlags int flags) throws IOException { - return new ApkAssets(FORMAT_APK, fd, friendlyName, flags); + @NonNull String friendlyName, @PropertyFlags int flags, + @Nullable AssetsProvider assets) throws IOException { + return new ApkAssets(FORMAT_APK, fd, friendlyName, flags, assets); } /** @@ -166,13 +186,15 @@ public final class ApkAssets { * @param length The number of bytes of the apk, or {@link AssetFileDescriptor#UNKNOWN_LENGTH} * if it extends to the end of the file. * @param flags flags that change the behavior of loaded apk assets + * @param assets The assets provider that overrides the loading of file-based resources * @return a new instance of ApkAssets. * @throws IOException if a disk I/O error or parsing error occurred. */ public static @NonNull ApkAssets loadFromFd(@NonNull FileDescriptor fd, - @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags) + @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags, + @Nullable AssetsProvider assets) throws IOException { - return new ApkAssets(FORMAT_APK, fd, friendlyName, offset, length, flags); + return new ApkAssets(FORMAT_APK, fd, friendlyName, offset, length, flags, assets); } /** @@ -186,7 +208,7 @@ public final class ApkAssets { */ public static @NonNull ApkAssets loadOverlayFromPath(@NonNull String idmapPath, @PropertyFlags int flags) throws IOException { - return new ApkAssets(FORMAT_IDMAP, idmapPath, flags); + return new ApkAssets(FORMAT_IDMAP, idmapPath, flags, null /* assets */); } /** @@ -199,12 +221,14 @@ public final class ApkAssets { * @param fd The FileDescriptor of an open, readable resources.arsc. * @param friendlyName The friendly name used to identify this ApkAssets when logging. * @param flags flags that change the behavior of loaded apk assets + * @param assets The assets provider that overrides the loading of file-based resources * @return a new instance of ApkAssets. * @throws IOException if a disk I/O error or parsing error occurred. */ public static @NonNull ApkAssets loadTableFromFd(@NonNull FileDescriptor fd, - @NonNull String friendlyName, @PropertyFlags int flags) throws IOException { - return new ApkAssets(FORMAT_ARSC, fd, friendlyName, flags); + @NonNull String friendlyName, @PropertyFlags int flags, + @Nullable AssetsProvider assets) throws IOException { + return new ApkAssets(FORMAT_ARSC, fd, friendlyName, flags, assets); } /** @@ -221,13 +245,14 @@ public final class ApkAssets { * @param length The number of bytes of the table, or {@link AssetFileDescriptor#UNKNOWN_LENGTH} * if it extends to the end of the file. * @param flags flags that change the behavior of loaded apk assets + * @param assets The assets provider that overrides the loading of file-based resources * @return a new instance of ApkAssets. * @throws IOException if a disk I/O error or parsing error occurred. */ public static @NonNull ApkAssets loadTableFromFd(@NonNull FileDescriptor fd, - @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags) - throws IOException { - return new ApkAssets(FORMAT_ARSC, fd, friendlyName, offset, length, flags); + @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags, + @Nullable AssetsProvider assets) throws IOException { + return new ApkAssets(FORMAT_ARSC, fd, friendlyName, offset, length, flags, assets); } /** @@ -236,12 +261,13 @@ public final class ApkAssets { * * @param path The path to a directory on disk. * @param flags flags that change the behavior of loaded apk assets + * @param assets The assets provider that overrides the loading of file-based resources * @return a new instance of ApkAssets. * @throws IOException if a disk I/O error or parsing error occurred. */ public static @NonNull ApkAssets loadFromDir(@NonNull String path, - @PropertyFlags int flags) throws IOException { - return new ApkAssets(FORMAT_DIR, path, flags); + @PropertyFlags int flags, @Nullable AssetsProvider assets) throws IOException { + return new ApkAssets(FORMAT_DIR, path, flags, assets); } /** @@ -250,43 +276,50 @@ public final class ApkAssets { * tracking a separate identifier. * * @param flags flags that change the behavior of loaded apk assets + * @param assets The assets provider that overrides the loading of file-based resources */ @NonNull - public static ApkAssets loadEmptyForLoader(@PropertyFlags int flags) { - return new ApkAssets(flags); + public static ApkAssets loadEmptyForLoader(@PropertyFlags int flags, + @Nullable AssetsProvider assets) { + return new ApkAssets(flags, assets); } - private ApkAssets(@FormatType int format, @NonNull String path, @PropertyFlags int flags) - throws IOException { + private ApkAssets(@FormatType int format, @NonNull String path, @PropertyFlags int flags, + @Nullable AssetsProvider assets) throws IOException { Objects.requireNonNull(path, "path"); mFlags = flags; - mNativePtr = nativeLoad(format, path, flags); + mNativePtr = nativeLoad(format, path, flags, assets); mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/); + mAssets = assets; } private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd, - @NonNull String friendlyName, @PropertyFlags int flags) throws IOException { + @NonNull String friendlyName, @PropertyFlags int flags, @Nullable AssetsProvider assets) + throws IOException { Objects.requireNonNull(fd, "fd"); Objects.requireNonNull(friendlyName, "friendlyName"); mFlags = flags; - mNativePtr = nativeLoadFd(format, fd, friendlyName, flags); + mNativePtr = nativeLoadFd(format, fd, friendlyName, flags, assets); mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/); + mAssets = assets; } private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd, - @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags) - throws IOException { + @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags, + @Nullable AssetsProvider assets) throws IOException { Objects.requireNonNull(fd, "fd"); Objects.requireNonNull(friendlyName, "friendlyName"); mFlags = flags; - mNativePtr = nativeLoadFdOffsets(format, fd, friendlyName, offset, length, flags); + mNativePtr = nativeLoadFdOffsets(format, fd, friendlyName, offset, length, flags, assets); mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/); + mAssets = assets; } - private ApkAssets(@PropertyFlags int flags) { + private ApkAssets(@PropertyFlags int flags, @Nullable AssetsProvider assets) { mFlags = flags; - mNativePtr = nativeLoadEmpty(flags); + mNativePtr = nativeLoadEmpty(flags, assets); mStringBlock = null; + mAssets = assets; } @UnsupportedAppUsage @@ -312,6 +345,14 @@ public final class ApkAssets { } /** + * Returns the assets provider that overrides the loading of assets present in this apk assets. + */ + @Nullable + public AssetsProvider getAssetsProvider() { + return mAssets; + } + + /** * Retrieve a parser for a compiled XML file. This is associated with a single APK and * <em>NOT</em> a full AssetManager. This means that shared-library references will not be * dynamically assigned runtime package IDs. @@ -382,13 +423,15 @@ public final class ApkAssets { } private static native long nativeLoad(@FormatType int format, @NonNull String path, - @PropertyFlags int flags) throws IOException; - private static native long nativeLoadEmpty(@PropertyFlags int flags); + @PropertyFlags int flags, @Nullable AssetsProvider asset) throws IOException; + private static native long nativeLoadEmpty(@PropertyFlags int flags, + @Nullable AssetsProvider asset); private static native long nativeLoadFd(@FormatType int format, @NonNull FileDescriptor fd, - @NonNull String friendlyName, @PropertyFlags int flags) throws IOException; + @NonNull String friendlyName, @PropertyFlags int flags, + @Nullable AssetsProvider asset) throws IOException; private static native long nativeLoadFdOffsets(@FormatType int format, @NonNull FileDescriptor fd, @NonNull String friendlyName, long offset, long length, - @PropertyFlags int flags) throws IOException; + @PropertyFlags int flags, @Nullable AssetsProvider asset) throws IOException; private static native void nativeDestroy(long ptr); private static native @NonNull String nativeGetAssetPath(long ptr); private static native long nativeGetStringBlock(long ptr); diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java index 6b9613d6e3be..7b2b93949a9f 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -27,9 +27,7 @@ import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.pm.ActivityInfo; import android.content.res.Configuration.NativeConfig; -import android.content.res.loader.AssetsProvider; import android.content.res.loader.ResourcesLoader; -import android.content.res.loader.ResourcesProvider; import android.os.ParcelFileDescriptor; import android.util.ArraySet; import android.util.Log; @@ -44,7 +42,6 @@ import java.io.FileDescriptor; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -828,13 +825,6 @@ public final class AssetManager implements AutoCloseable { Objects.requireNonNull(fileName, "fileName"); synchronized (this) { ensureOpenLocked(); - - String path = Paths.get("assets", fileName).toString(); - InputStream inputStream = searchLoaders(0, path, accessMode); - if (inputStream != null) { - return inputStream; - } - final long asset = nativeOpenAsset(mObject, fileName, accessMode); if (asset == 0) { throw new FileNotFoundException("Asset file: " + fileName); @@ -859,13 +849,6 @@ public final class AssetManager implements AutoCloseable { Objects.requireNonNull(fileName, "fileName"); synchronized (this) { ensureOpenLocked(); - - String path = Paths.get("assets", fileName).toString(); - AssetFileDescriptor fileDescriptor = searchLoadersFd(0, path); - if (fileDescriptor != null) { - return fileDescriptor; - } - final ParcelFileDescriptor pfd = nativeOpenAssetFd(mObject, fileName, mOffsets); if (pfd == null) { throw new FileNotFoundException("Asset file: " + fileName); @@ -959,12 +942,6 @@ public final class AssetManager implements AutoCloseable { Objects.requireNonNull(fileName, "fileName"); synchronized (this) { ensureOpenLocked(); - - InputStream inputStream = searchLoaders(cookie, fileName, accessMode); - if (inputStream != null) { - return inputStream; - } - final long asset = nativeOpenNonAsset(mObject, cookie, fileName, accessMode); if (asset == 0) { throw new FileNotFoundException("Asset absolute file: " + fileName); @@ -1004,12 +981,6 @@ public final class AssetManager implements AutoCloseable { Objects.requireNonNull(fileName, "fileName"); synchronized (this) { ensureOpenLocked(); - - AssetFileDescriptor fileDescriptor = searchLoadersFd(cookie, fileName); - if (fileDescriptor != null) { - return fileDescriptor; - } - final ParcelFileDescriptor pfd = nativeOpenNonAssetFd(mObject, cookie, fileName, mOffsets); if (pfd == null) { @@ -1072,15 +1043,7 @@ public final class AssetManager implements AutoCloseable { synchronized (this) { ensureOpenLocked(); - final long xmlBlock; - AssetFileDescriptor fileDescriptor = searchLoadersFd(cookie, fileName); - if (fileDescriptor != null) { - xmlBlock = nativeOpenXmlAssetFd(mObject, cookie, - fileDescriptor.getFileDescriptor()); - } else { - xmlBlock = nativeOpenXmlAsset(mObject, cookie, fileName); - } - + final long xmlBlock = nativeOpenXmlAsset(mObject, cookie, fileName); if (xmlBlock == 0) { throw new FileNotFoundException("Asset XML file: " + fileName); } @@ -1090,122 +1053,6 @@ public final class AssetManager implements AutoCloseable { } } - private ResourcesProvider findResourcesProvider(int assetCookie) { - if (mLoaders == null) { - return null; - } - - int apkAssetsIndex = assetCookie - 1; - if (apkAssetsIndex >= mApkAssets.length || apkAssetsIndex < 0) { - return null; - } - - final ApkAssets apkAssets = mApkAssets[apkAssetsIndex]; - if (!apkAssets.isForLoader()) { - return null; - } - - for (int i = mLoaders.length - 1; i >= 0; i--) { - final ResourcesLoader loader = mLoaders[i]; - for (int j = 0, n = loader.getProviders().size(); j < n; j++) { - final ResourcesProvider provider = loader.getProviders().get(j); - if (apkAssets == provider.getApkAssets()) { - return provider; - } - } - } - - return null; - } - - private InputStream searchLoaders(int cookie, @NonNull String fileName, int accessMode) - throws IOException { - if (mLoaders == null) { - return null; - } - - if (cookie == 0) { - // A cookie of 0 means no specific ApkAssets, so search everything - for (int i = mLoaders.length - 1; i >= 0; i--) { - final ResourcesLoader loader = mLoaders[i]; - final List<ResourcesProvider> providers = loader.getProviders(); - for (int j = providers.size() - 1; j >= 0; j--) { - final AssetsProvider assetsProvider = providers.get(j).getAssetsProvider(); - if (assetsProvider == null) { - continue; - } - - try { - final InputStream inputStream = assetsProvider.loadAsset( - fileName, accessMode); - if (inputStream != null) { - return inputStream; - } - } catch (IOException ignored) { - // When searching, ignore read failures - } - } - } - - return null; - } - - final ResourcesProvider provider = findResourcesProvider(cookie); - if (provider != null && provider.getAssetsProvider() != null) { - return provider.getAssetsProvider().loadAsset( - fileName, accessMode); - } - - return null; - } - - private AssetFileDescriptor searchLoadersFd(int cookie, @NonNull String fileName) - throws IOException { - if (mLoaders == null) { - return null; - } - - if (cookie == 0) { - // A cookie of 0 means no specific ApkAssets, so search everything - for (int i = mLoaders.length - 1; i >= 0; i--) { - final ResourcesLoader loader = mLoaders[i]; - final List<ResourcesProvider> providers = loader.getProviders(); - for (int j = providers.size() - 1; j >= 0; j--) { - final AssetsProvider assetsProvider = providers.get(j).getAssetsProvider(); - if (assetsProvider == null) { - continue; - } - - try { - final ParcelFileDescriptor fileDescriptor = assetsProvider - .loadAssetParcelFd(fileName); - if (fileDescriptor != null) { - return new AssetFileDescriptor(fileDescriptor, 0, - AssetFileDescriptor.UNKNOWN_LENGTH); - } - } catch (IOException ignored) { - // When searching, ignore read failures - } - } - } - - return null; - } - - final ResourcesProvider provider = findResourcesProvider(cookie); - if (provider != null && provider.getAssetsProvider() != null) { - final ParcelFileDescriptor fileDescriptor = provider.getAssetsProvider() - .loadAssetParcelFd(fileName); - if (fileDescriptor != null) { - return new AssetFileDescriptor(fileDescriptor, 0, - AssetFileDescriptor.UNKNOWN_LENGTH); - } - return null; - } - - return null; - } - void xmlBlockGone(int id) { synchronized (this) { decRefsLocked(id); diff --git a/core/java/android/content/res/loader/AssetsProvider.java b/core/java/android/content/res/loader/AssetsProvider.java index c315494cf728..0f8f1d1da8d2 100644 --- a/core/java/android/content/res/loader/AssetsProvider.java +++ b/core/java/android/content/res/loader/AssetsProvider.java @@ -18,12 +18,10 @@ package android.content.res.loader; import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.res.AssetFileDescriptor; import android.content.res.AssetManager; import android.os.ParcelFileDescriptor; -import java.io.IOException; -import java.io.InputStream; - /** * Provides callbacks that allow for the value of a file-based resources or assets of a * {@link ResourcesProvider} to be specified or overridden. @@ -34,6 +32,10 @@ public interface AssetsProvider { * Callback that allows the value of a file-based resources or asset to be specified or * overridden. * + * <p>The system will take ownership of the file descriptor returned from this method, so + * {@link ParcelFileDescriptor#dup() dup} the file descriptor before returning if the system + * should not own it. + * * <p>There are two situations in which this method will be called: * <ul> * <li>AssetManager is queried for an InputStream of an asset using APIs like @@ -52,17 +54,7 @@ public interface AssetsProvider { * @see AssetManager#open */ @Nullable - default InputStream loadAsset(@NonNull String path, int accessMode) throws IOException { - return null; - } - - /** - * {@link ParcelFileDescriptor} variant of {@link #loadAsset(String, int)}. - * - * @param path the asset path being loaded - */ - @Nullable - default ParcelFileDescriptor loadAssetParcelFd(@NonNull String path) throws IOException { + default AssetFileDescriptor loadAssetFd(@NonNull String path, int accessMode) { return null; } } diff --git a/core/java/android/content/res/loader/DirectoryAssetsProvider.java b/core/java/android/content/res/loader/DirectoryAssetsProvider.java deleted file mode 100644 index 81c2a4c1b4d6..000000000000 --- a/core/java/android/content/res/loader/DirectoryAssetsProvider.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2020 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.content.res.loader; - -import android.annotation.NonNull; -import android.annotation.Nullable; -import android.os.ParcelFileDescriptor; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; - -/** - * A {@link AssetsProvider} that searches a directory for assets. - * Assumes that resource paths are resolvable child paths of the root directory passed in. - */ -public class DirectoryAssetsProvider implements AssetsProvider { - - @NonNull - private final File mDirectory; - - /** - * Creates a DirectoryAssetsProvider with given root directory. - * - * @param directory the root directory to resolve files from - */ - public DirectoryAssetsProvider(@NonNull File directory) { - this.mDirectory = directory; - } - - @Nullable - @Override - public InputStream loadAsset(@NonNull String path, int accessMode) throws IOException { - final File file = findFile(path); - if (file == null || !file.exists()) { - return null; - } - return new FileInputStream(file); - } - - @Nullable - @Override - public ParcelFileDescriptor loadAssetParcelFd(@NonNull String path) throws IOException { - final File file = findFile(path); - if (file == null || !file.exists()) { - return null; - } - return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); - } - - /** - * Finds the file relative to the root directory. - * - * @param path the relative path of the file - */ - @Nullable - public File findFile(@NonNull String path) { - return mDirectory.toPath().resolve(path).toFile(); - } - - @NonNull - public File getDirectory() { - return mDirectory; - } -} diff --git a/core/java/android/content/res/loader/ResourcesProvider.java b/core/java/android/content/res/loader/ResourcesProvider.java index 040b3694d0cd..0a698d18682b 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 6acb133a4e5b..fbdd4060d7f2 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<AssetsProvider> Create(JNIEnv* env, jobject assets_provider) { + return (!assets_provider) ? nullptr + : std::unique_ptr<AssetsProvider>(new LoaderAssetsProvider( + env->NewGlobalRef(assets_provider))); + } + + ~LoaderAssetsProvider() override { + const auto env = AndroidRuntime::getJNIEnv(); + CHECK(env != nullptr) << "Current thread not attached to a Java VM." + << " Failed to close LoaderAssetsProvider."; + env->DeleteGlobalRef(assets_provider_); + } + + protected: + std::unique_ptr<Asset> OpenInternal(const std::string& path, + Asset::AccessMode mode, + bool* file_exists) const override { + const auto env = AndroidRuntime::getJNIEnv(); + CHECK(env != nullptr) << "Current thread not attached to a Java VM." + << " ResourcesProvider assets cannot be retrieved on current thread."; + + jstring java_string = env->NewStringUTF(path.c_str()); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + return nullptr; + } + + // Check if the AssetsProvider provides a value for the path. + jobject asset_fd = env->CallObjectMethod(assets_provider_, + gAssetsProviderOffsets.loadAssetFd, + java_string, static_cast<jint>(mode)); + env->DeleteLocalRef(java_string); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + return nullptr; + } + + if (!asset_fd) { + if (file_exists) { + *file_exists = false; + } + return nullptr; + } + + const jlong mOffset = env->GetLongField(asset_fd, gAssetFileDescriptorOffsets.mStartOffset); + const jlong mLength = env->GetLongField(asset_fd, gAssetFileDescriptorOffsets.mLength); + jobject mFd = env->GetObjectField(asset_fd, gAssetFileDescriptorOffsets.mFd); + env->DeleteLocalRef(asset_fd); + + if (!mFd) { + jniThrowException(env, "java/lang/NullPointerException", nullptr); + env->ExceptionDescribe(); + env->ExceptionClear(); + return nullptr; + } + + // Gain ownership of the file descriptor. + const jint fd = env->CallIntMethod(mFd, gParcelFileDescriptorOffsets.detachFd); + env->DeleteLocalRef(mFd); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + return nullptr; + } + + if (file_exists) { + *file_exists = true; + } + + return ApkAssets::CreateAssetFromFd(base::unique_fd(fd), + nullptr /* path */, + static_cast<off64_t>(mOffset), + static_cast<off64_t>(mLength)); + } + + private: + DISALLOW_COPY_AND_ASSIGN(LoaderAssetsProvider); + + explicit LoaderAssetsProvider(jobject assets_provider) + : assets_provider_(assets_provider) { } + + // The global reference to the AssetsProvider + jobject assets_provider_; +}; + static jlong NativeLoad(JNIEnv* env, jclass /*clazz*/, const format_type_t format, - jstring java_path, const jint property_flags) { + 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<const ApkAssets> 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<const ApkAssets> 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<const ApkAssets> apk_assets; switch (format) { case FORMAT_APK: apk_assets = ApkAssets::LoadFromFd(std::move(dup_fd), friendly_name_utf8.c_str(), - property_flags, static_cast<off64_t>(offset), + property_flags, std::move(loader_assets), + static_cast<off64_t>(offset), static_cast<off64_t>(length)); break; case FORMAT_ARSC: apk_assets = ApkAssets::LoadTableFromFd(std::move(dup_fd), friendly_name_utf8.c_str(), - property_flags, static_cast<off64_t>(offset), + property_flags, std::move(loader_assets), + static_cast<off64_t>(offset), static_cast<off64_t>(length)); break; default: @@ -199,8 +308,9 @@ static jlong NativeLoadFromFdOffset(JNIEnv* env, jclass /*clazz*/, const format_ return reinterpret_cast<jlong>(apk_assets.release()); } -static jlong NativeLoadEmpty(JNIEnv* env, jclass /*clazz*/, jint flags) { - std::unique_ptr<const ApkAssets> 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<jlong>(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, "<init>", "(Ljava/lang/String;Ljava/lang/String;)V"); + jclass assetFd = FindClassOrDie(env, "android/content/res/AssetFileDescriptor"); + gAssetFileDescriptorOffsets.mFd = + GetFieldIDOrDie(env, assetFd, "mFd", "Landroid/os/ParcelFileDescriptor;"); + gAssetFileDescriptorOffsets.mStartOffset = GetFieldIDOrDie(env, assetFd, "mStartOffset", "J"); + gAssetFileDescriptorOffsets.mLength = GetFieldIDOrDie(env, assetFd, "mLength", "J"); + + jclass assetsProvider = FindClassOrDie(env, "android/content/res/loader/AssetsProvider"); + gAssetsProviderOffsets.classObject = MakeGlobalRefOrDie(env, assetsProvider); + gAssetsProviderOffsets.loadAssetFd = GetMethodIDOrDie( + env, gAssetsProviderOffsets.classObject, "loadAssetFd", + "(Ljava/lang/String;I)Landroid/content/res/AssetFileDescriptor;"); + + jclass parcelFd = FindClassOrDie(env, "android/os/ParcelFileDescriptor"); + gParcelFileDescriptorOffsets.detachFd = GetMethodIDOrDie(env, parcelFd, "detachFd", "()I"); + return RegisterMethodsOrDie(env, "android/content/res/ApkAssets", gApkAssetsMethods, arraysize(gApkAssetsMethods)); } diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp index 062b886f54a1..cb5a332c6e85 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 000000000000..8e62cc346238 --- /dev/null +++ b/core/tests/ResourceLoaderTests/assets/base_asset.txt @@ -0,0 +1 @@ +Base
\ No newline at end of file diff --git a/core/tests/ResourceLoaderTests/resources/provider1/assets/loader_asset.txt b/core/tests/ResourceLoaderTests/resources/provider1/assets/loader_asset.txt new file mode 100644 index 000000000000..0e41ffa475af --- /dev/null +++ b/core/tests/ResourceLoaderTests/resources/provider1/assets/loader_asset.txt @@ -0,0 +1 @@ +LoaderOne
\ No newline at end of file diff --git a/core/tests/ResourceLoaderTests/resources/provider2/assets/loader_asset.txt b/core/tests/ResourceLoaderTests/resources/provider2/assets/loader_asset.txt new file mode 100644 index 000000000000..bca782ec1b2b --- /dev/null +++ b/core/tests/ResourceLoaderTests/resources/provider2/assets/loader_asset.txt @@ -0,0 +1 @@ +LoaderTwo
\ No newline at end of file diff --git a/core/tests/ResourceLoaderTests/resources/provider3/assets/loader_asset.txt b/core/tests/ResourceLoaderTests/resources/provider3/assets/loader_asset.txt new file mode 100644 index 000000000000..bae8ef79a2ce --- /dev/null +++ b/core/tests/ResourceLoaderTests/resources/provider3/assets/loader_asset.txt @@ -0,0 +1 @@ +LoaderThree
\ No newline at end of file diff --git a/core/tests/ResourceLoaderTests/resources/provider4/assets/loader_asset.txt b/core/tests/ResourceLoaderTests/resources/provider4/assets/loader_asset.txt new file mode 100644 index 000000000000..b75d9963575b --- /dev/null +++ b/core/tests/ResourceLoaderTests/resources/provider4/assets/loader_asset.txt @@ -0,0 +1 @@ +LoaderFour
\ No newline at end of file diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetsTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetsTest.kt deleted file mode 100644 index da5092de0627..000000000000 --- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetsTest.kt +++ /dev/null @@ -1,220 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.content.res.loader.test - -import android.content.res.AssetManager -import android.content.res.loader.AssetsProvider -import android.content.res.loader.DirectoryAssetsProvider -import android.content.res.loader.ResourcesLoader -import android.content.res.loader.ResourcesProvider -import com.google.common.truth.Truth.assertThat -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.rules.TestName -import org.junit.runner.RunWith -import org.junit.runners.Parameterized -import org.mockito.Mockito.anyInt -import org.mockito.Mockito.anyString -import org.mockito.Mockito.doAnswer -import org.mockito.Mockito.doReturn -import org.mockito.Mockito.eq -import org.mockito.Mockito.inOrder -import org.mockito.Mockito.mock -import java.io.File -import java.io.FileNotFoundException -import java.io.IOException -import java.nio.file.Paths - -@RunWith(Parameterized::class) -class ResourceLoaderAssetsTest : ResourceLoaderTestBase() { - - companion object { - private const val BASE_TEST_PATH = "android/content/res/loader/test/file.txt" - private const val TEST_TEXT = "some text" - - @JvmStatic - @Parameterized.Parameters(name = "{0}") - fun parameters(): Array<Array<out Any?>> { - val fromInputStream: AssetsProvider.(String) -> Any? = { - loadAsset(eq(it), anyInt()) - } - - val fromFileDescriptor: AssetsProvider.(String) -> Any? = { - loadAssetParcelFd(eq(it)) - } - - val openAsset: AssetManager.() -> String? = { - open(BASE_TEST_PATH).reader().readText() - } - - val openNonAsset: AssetManager.() -> String? = { - openNonAssetFd(BASE_TEST_PATH).readText() - } - - return arrayOf( - arrayOf("assets", fromInputStream, openAsset), - arrayOf("", fromFileDescriptor, openNonAsset) - ) - } - } - - @get:Rule - val testName = TestName() - - @JvmField - @field:Parameterized.Parameter(0) - var prefix: String? = null - - @field:Parameterized.Parameter(1) - lateinit var loadAssetFunction: AssetsProvider.(String) -> Any? - - @field:Parameterized.Parameter(2) - lateinit var openAssetFunction: AssetManager.() -> String? - - private val testPath: String - get() = Paths.get(prefix.orEmpty(), BASE_TEST_PATH).toString() - - private fun AssetsProvider.loadAsset() = loadAssetFunction(testPath) - - private fun AssetManager.openAsset() = openAssetFunction() - - private lateinit var testDir: File - - @Before - fun setUpTestDir() { - testDir = context.filesDir.resolve("DirectoryAssetsProvider_${testName.methodName}") - testDir.resolve(testPath).apply { parentFile!!.mkdirs() }.writeText(TEST_TEXT) - } - - @Test - fun multipleProvidersSearchesBackwards() { - // DirectoryResourceLoader relies on a private field and can't be spied directly, so wrap it - val assetsProvider = DirectoryAssetsProvider(testDir) - val assetProviderWrapper = mock(AssetsProvider::class.java).apply { - doAnswer { assetsProvider.loadAsset(it.arguments[0] as String, it.arguments[1] as Int) } - .`when`(this).loadAsset(anyString(), anyInt()) - doAnswer { assetsProvider.loadAssetParcelFd(it.arguments[0] as String) } - .`when`(this).loadAssetParcelFd(anyString()) - } - - val one = ResourcesProvider.empty(assetProviderWrapper) - val two = mockProvider { - doReturn(null).`when`(it).loadAsset() - } - - val loader = ResourcesLoader() - loader.providers = listOf(one, two) - resources.addLoaders(loader) - - assertOpenedAsset() - inOrder(two.assetsProvider, one.assetsProvider).apply { - verify(two.assetsProvider)?.loadAsset() - verify(one.assetsProvider)?.loadAsset() - } - } - - @Test - fun multipleLoadersSearchesBackwards() { - // DirectoryResourceLoader relies on a private field and can't be spied directly, so wrap it - val assetsProvider = DirectoryAssetsProvider(testDir) - val assetProviderWrapper = mock(AssetsProvider::class.java).apply { - doAnswer { assetsProvider.loadAsset(it.arguments[0] as String, it.arguments[1] as Int) } - .`when`(this).loadAsset(anyString(), anyInt()) - doAnswer { assetsProvider.loadAssetParcelFd(it.arguments[0] as String) } - .`when`(this).loadAssetParcelFd(anyString()) - } - - val one = ResourcesProvider.empty(assetProviderWrapper) - val two = mockProvider { - doReturn(null).`when`(it).loadAsset() - } - - val loader1 = ResourcesLoader() - loader1.addProvider(one) - val loader2 = ResourcesLoader() - loader2.addProvider(two) - - resources.addLoaders(loader1, loader2) - - assertOpenedAsset() - inOrder(two.assetsProvider, one.assetsProvider).apply { - verify(two.assetsProvider)?.loadAsset() - verify(one.assetsProvider)?.loadAsset() - } - } - - @Test(expected = FileNotFoundException::class) - fun failToFindThrowsFileNotFound() { - val assetsProvider1 = mock(AssetsProvider::class.java).apply { - doReturn(null).`when`(this).loadAsset() - } - val assetsProvider2 = mock(AssetsProvider::class.java).apply { - doReturn(null).`when`(this).loadAsset() - } - - val loader = ResourcesLoader() - val one = ResourcesProvider.empty(assetsProvider1) - val two = ResourcesProvider.empty(assetsProvider2) - resources.addLoaders(loader) - loader.providers = listOf(one, two) - - assertOpenedAsset() - } - - @Test - fun throwingIOExceptionIsSkipped() { - val assetsProvider1 = DirectoryAssetsProvider(testDir) - val assetsProvider2 = mock(AssetsProvider::class.java).apply { - doAnswer { throw IOException() }.`when`(this).loadAsset() - } - - val loader = ResourcesLoader() - val one = ResourcesProvider.empty(assetsProvider1) - val two = ResourcesProvider.empty(assetsProvider2) - resources.addLoaders(loader) - loader.providers = listOf(one, two) - - assertOpenedAsset() - } - - @Test(expected = IllegalStateException::class) - fun throwingNonIOExceptionCausesFailure() { - val assetsProvider1 = DirectoryAssetsProvider(testDir) - val assetsProvider2 = mock(AssetsProvider::class.java).apply { - doAnswer { throw IllegalStateException() }.`when`(this).loadAsset() - } - - val loader = ResourcesLoader() - val one = ResourcesProvider.empty(assetsProvider1) - val two = ResourcesProvider.empty(assetsProvider2) - resources.addLoaders(loader) - loader.providers = listOf(one, two) - - assertOpenedAsset() - } - - private fun mockProvider(block: (AssetsProvider) -> Unit = {}): ResourcesProvider { - return ResourcesProvider.empty(mock(AssetsProvider::class.java).apply { - block.invoke(this) - }) - } - - private fun assertOpenedAsset() { - assertThat(resources.assets.openAsset()).isEqualTo(TEST_TEXT) - } -} diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderTestBase.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderTestBase.kt index bd2bac50f100..4764c1008d2f 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, String>): String { + return JSONObject(ArrayMap<String, String>().apply { putAll(m) }).toString() + } + + /** Creates a lambda that runs multiple resources queries and concatenates the results. */ + fun query(queries: Map<String, (Resources) -> String>): Resources.() -> String { + return { + val resultMap = ArrayMap<String, String>() + queries.forEach { q -> + resultMap[q.key] = try { + q.value.invoke(this) + } catch (e: Exception) { + e.javaClass.simpleName + } + } + mapToString(resultMap) + } + } + } // Data type of the current test iteration open lateinit var dataType: DataType @@ -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 } - DataType.APK_DISK_FD_OFFSETS -> { - val asset = context.assets.openFd("${this}.apk") - ResourcesProvider.loadFromApk(asset.parcelFileDescriptor, asset.startOffset, - asset.length, null).apply { - asset.close() + return when (dataType) { + DataType.APK_DISK_FD -> { + val file = context.copiedAssetFile("${this}.apk") + ResourcesProvider.loadFromApk(ParcelFileDescriptor.fromFd(file.fd), + assetsProvider).apply { + file.close() + } } - } - DataType.ARSC_DISK_FD -> { - val file = context.copiedAssetFile("${this}.arsc") - ResourcesProvider.loadFromTable(ParcelFileDescriptor.fromFd(file.fd), null).apply { - file.close() + DataType.APK_DISK_FD_OFFSETS -> { + val asset = context.assets.openFd("${this}.apk") + ResourcesProvider.loadFromApk(asset.parcelFileDescriptor, asset.startOffset, + asset.length, assetsProvider).apply { + asset.close() + } } - } - DataType.ARSC_DISK_FD_OFFSETS -> { - val asset = context.assets.openFd("${this}.arsc") - ResourcesProvider.loadFromTable(asset.parcelFileDescriptor, asset.startOffset, - asset.length, null).apply { - asset.close() + DataType.ARSC_DISK_FD -> { + val file = context.copiedAssetFile("${this}.arsc") + ResourcesProvider.loadFromTable(ParcelFileDescriptor.fromFd(file.fd), + assetsProvider).apply { + file.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.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_FD -> { - val asset = context.assets.openFd("${this}.apk") - var fd = loadAssetIntoMemory(asset) - ResourcesProvider.loadFromApk(fd).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.ARSC_RAM_MEMORY -> { - val asset = context.assets.openFd("${this}.arsc") - var fd = loadAssetIntoMemory(asset) - ResourcesProvider.loadFromTable(fd, null).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_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 -> { + val asset = context.assets.openFd("${this}.arsc") + var fd = loadAssetIntoMemory(asset) + ResourcesProvider.loadFromTable(fd, assetsProvider).apply { + asset.close() + fd.close() + } + } + DataType.ARSC_RAM_MEMORY_OFFSETS -> { + val asset = context.assets.openFd("${this}.arsc") + val leadingGarbageSize = 100L + val trailingGarbageSize = 55L + val fd = loadAssetIntoMemory(asset, leadingGarbageSize.toInt(), + trailingGarbageSize.toInt()) + ResourcesProvider.loadFromTable(fd, leadingGarbageSize, asset.declaredLength, + assetsProvider).apply { + asset.close() + fd.close() + } + } + DataType.EMPTY -> { + if (equals(PROVIDER_EMPTY)) { + ResourcesProvider.empty(EmptyAssetsProvider()) + } else { + if (assetsProvider == null) ResourcesProvider.empty(ZipAssetsProvider(this)) + else ResourcesProvider.empty(assetsProvider) + } + } + DataType.DIRECTORY -> { + ResourcesProvider.loadFromDirectory(zipToDir("${this}.apk").absolutePath, + assetsProvider) + } + DataType.SPLIT -> { + ResourcesProvider.loadFromSplit(context, "${this}_Split") } } - DataType.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<String, FileDescriptor>() + + fun addLoadAssetFdResult(path : String, value : String) = apply { + val fd = Os.memfd_create(path, 0) + val valueBytes = value.toByteArray() + Os.write(fd, valueBytes, 0, valueBytes.size) + loadAssetResults[path] = fd + } + + override fun loadAssetFd(path: String, accessMode: Int): AssetFileDescriptor? { + return if (loadAssetResults.containsKey(path)) AssetFileDescriptor( + ParcelFileDescriptor.dup(loadAssetResults[path]), 0, + AssetFileDescriptor.UNKNOWN_LENGTH) else null + } + + override fun close() { + for (f in loadAssetResults.values) { + Os.close(f) + } + } + } /** Extracts an archive-based asset into a directory on disk. */ - private fun zipToDir(name : String, 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 c01db0d7428b..a9945369d1df 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>(TestActivity::class.java) companion object { - /** Converts the map to a stable JSON string representation. */ - private fun mapToString(m : Map<String, String>) :String { - return JSONObject(ArrayMap<String, String>().apply { putAll(m) }).toString() - } - - /** Creates a lambda that runs multiple resources queries and concatenates the results. */ - fun query(queries : Map<String, (Resources) -> String>) :Resources.() -> String { - return { - val resultMap = ArrayMap<String, String>() - queries.forEach { q -> - resultMap[q.key] = try { - q.value.invoke(this) - } catch (e : Exception) { - e.javaClass.simpleName - } - } - mapToString(resultMap) - } - } - @Parameterized.Parameters(name = "{1} {0}") @JvmStatic fun parameters(): Array<Any> { @@ -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<String, String>, - val valueOne: Map<String, String>, - val valueTwo: Map<String, String>, - val valueThree: Map<String, String>, - val valueFour: Map<String, String>, - val dataTypes: List<DataType> + val testPrefix: String, + val getValue: Resources.() -> String, + val valueOriginal: Map<String, String>, + val valueOne: Map<String, String>, + val assetProviderOne: (() -> MemoryAssetsProvider)? = null, + val valueTwo: Map<String, String>, + val assetProviderTwo: (() -> MemoryAssetsProvider)? = null, + val valueThree: Map<String, String>, + val assetProviderThree: (() -> MemoryAssetsProvider)? = null, + val valueFour: Map<String, String>, + val assetProviderFour: (() -> MemoryAssetsProvider)? = null, + val dataTypes: List<DataType> ) { + constructor(testPrefix: String, + getValue: Resources.() -> String, + valueOriginal : Map<String, String>, + valueOne: Map<String, String>, + valueTwo: Map<String, String>, + valueThree: Map<String, String>, + valueFour: Map<String, String>, + dataTypes: List<DataType>): this(testPrefix, getValue, valueOriginal, valueOne, + null, valueTwo, null, valueThree, null, valueFour, null, dataTypes) + override fun toString() = testPrefix } } diff --git a/libs/androidfw/ApkAssets.cpp b/libs/androidfw/ApkAssets.cpp index f5bf84f18a89..202651dc86d5 100644 --- a/libs/androidfw/ApkAssets.cpp +++ b/libs/androidfw/ApkAssets.cpp @@ -144,8 +144,8 @@ class ZipAssetsProvider : public AssetsProvider { } protected: - std::unique_ptr<Asset> OpenInternal( - const std::string& path, Asset::AccessMode mode, bool* file_exists) const override { + std::unique_ptr<Asset> 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<const ApkAssets> 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<const AssetsProvider> Create( + std::unique_ptr<const AssetsProvider> child, std::unique_ptr<const AssetsProvider> parent) { + CHECK(parent != nullptr) << "parent provider must not be null"; + return (!child) ? std::move(parent) + : std::unique_ptr<const AssetsProvider>(new MultiAssetsProvider( + std::move(child), std::move(parent))); + } + + bool ForEachFile(const std::string& root_path, + const std::function<void(const StringPiece&, FileType)>& f) const override { + // TODO: Only call the function once for files defined in the parent and child + return child_->ForEachFile(root_path, f) && parent_->ForEachFile(root_path, f); + } + + protected: + std::unique_ptr<Asset> OpenInternal( + const std::string& path, Asset::AccessMode mode, bool* file_exists) const override { + auto asset = child_->Open(path, mode, file_exists); + return (asset) ? std::move(asset) : parent_->Open(path, mode, file_exists); + } + + private: + DISALLOW_COPY_AND_ASSIGN(MultiAssetsProvider); + + MultiAssetsProvider(std::unique_ptr<const AssetsProvider> child, + std::unique_ptr<const AssetsProvider> parent) + : child_(std::move(child)), parent_(std::move(parent)) { } + + std::unique_ptr<const AssetsProvider> child_; + std::unique_ptr<const AssetsProvider> parent_; +}; + +// Opens the archive using the file path. Calling CloseArchive on the zip handle will close the +// file. +std::unique_ptr<const ApkAssets> ApkAssets::Load( + const std::string& path, const package_property_t flags, + std::unique_ptr<const AssetsProvider> override_asset) { auto assets = ZipAssetsProvider::Create(path); - return (assets) ? LoadImpl(std::move(assets), path, nullptr /*idmap_asset*/, - nullptr /*loaded_idmap*/, flags) + return (assets) ? LoadImpl(std::move(assets), path, flags, std::move(override_asset)) : nullptr; } -std::unique_ptr<const ApkAssets> 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<const ApkAssets> ApkAssets::LoadFromFd( + unique_fd fd, const std::string& friendly_name, const package_property_t flags, + std::unique_ptr<const AssetsProvider> override_asset, const off64_t offset, + const off64_t length) { CHECK(length >= kUnknownLength) << "length must be greater than or equal to " << kUnknownLength; CHECK(length != kUnknownLength || offset == 0) << "offset must be 0 if length is " << kUnknownLength; + auto assets = ZipAssetsProvider::Create(std::move(fd), friendly_name, offset, length); - return (assets) ? LoadImpl(std::move(assets), friendly_name, nullptr /*idmap_asset*/, - nullptr /*loaded_idmap*/, flags) + return (assets) ? LoadImpl(std::move(assets), friendly_name, flags, std::move(override_asset)) : nullptr; } -std::unique_ptr<const ApkAssets> ApkAssets::LoadTable(const std::string& path, - const package_property_t flags) { - auto resources_asset = CreateAssetFromFile(path); - return (resources_asset) ? LoadTableImpl(std::move(resources_asset), path, flags) - : nullptr; +std::unique_ptr<const ApkAssets> ApkAssets::LoadTable( + const std::string& path, const package_property_t flags, + std::unique_ptr<const AssetsProvider> override_asset) { + + auto assets = CreateAssetFromFile(path); + return (assets) ? LoadTableImpl(std::move(assets), path, flags, std::move(override_asset)) + : nullptr; } -std::unique_ptr<const ApkAssets> ApkAssets::LoadTableFromFd(unique_fd fd, - const std::string& friendly_name, - const package_property_t flags, - 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<const ApkAssets> ApkAssets::LoadTableFromFd( + unique_fd fd, const std::string& friendly_name, const package_property_t flags, + std::unique_ptr<const AssetsProvider> override_asset, const off64_t offset, + const off64_t length) { + + auto assets = CreateAssetFromFd(std::move(fd), nullptr /* path */, offset, length); + return (assets) ? LoadTableImpl(std::move(assets), friendly_name, flags, + std::move(override_asset)) + : nullptr; } std::unique_ptr<const ApkAssets> ApkAssets::LoadOverlay(const std::string& idmap_path, const package_property_t flags) { CHECK((flags & PROPERTY_LOADER) == 0U) << "Cannot load RROs through loaders"; - std::unique_ptr<Asset> idmap_asset = CreateAssetFromFile(idmap_path); if (idmap_asset == nullptr) { return {}; @@ -351,23 +393,28 @@ std::unique_ptr<const ApkAssets> 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<const ApkAssets> ApkAssets::LoadFromDir(const std::string& path, - const package_property_t flags) { +std::unique_ptr<const ApkAssets> ApkAssets::LoadFromDir( + const std::string& path, const package_property_t flags, + std::unique_ptr<const AssetsProvider> override_asset) { + auto assets = DirectoryAssetsProvider::Create(path); - return (assets) ? LoadImpl(std::move(assets), path, nullptr /*idmap_asset*/, - nullptr /*loaded_idmap*/, flags) + return (assets) ? LoadImpl(std::move(assets), path, flags, std::move(override_asset)) : nullptr; } -std::unique_ptr<const ApkAssets> ApkAssets::LoadEmpty(const package_property_t flags) { - std::unique_ptr<ApkAssets> loaded_apk(new ApkAssets( - std::unique_ptr<AssetsProvider>(new EmptyAssetsProvider()), "empty" /* path */, - -1 /* last_mod-time */, flags)); +std::unique_ptr<const ApkAssets> ApkAssets::LoadEmpty( + const package_property_t flags, std::unique_ptr<const AssetsProvider> override_asset) { + + auto assets = (override_asset) ? std::move(override_asset) + : std::unique_ptr<const AssetsProvider>(new EmptyAssetsProvider()); + std::unique_ptr<ApkAssets> loaded_apk(new ApkAssets(std::move(assets), "empty" /* path */, + -1 /* last_mod-time */, flags)); loaded_apk->loaded_arsc_ = LoadedArsc::CreateEmpty(); // Need to force a move for mingw32. return std::move(loaded_apk); @@ -413,27 +460,30 @@ std::unique_ptr<Asset> ApkAssets::CreateAssetFromFd(base::unique_fd fd, Asset::AccessMode::ACCESS_RANDOM); } -std::unique_ptr<const ApkAssets> ApkAssets::LoadImpl(std::unique_ptr<const AssetsProvider> assets, - const std::string& path, - std::unique_ptr<Asset> idmap_asset, - std::unique_ptr<const LoadedIdmap> idmap, - package_property_t property_flags) { +std::unique_ptr<const ApkAssets> ApkAssets::LoadImpl( + std::unique_ptr<const AssetsProvider> assets, const std::string& path, + package_property_t property_flags, std::unique_ptr<const AssetsProvider> override_assets, + std::unique_ptr<Asset> idmap_asset, std::unique_ptr<const LoadedIdmap> idmap) { + 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<ApkAssets> 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<const ApkAssets> ApkAssets::LoadImpl(std::unique_ptr<const Asset return std::move(loaded_apk); } -std::unique_ptr<const ApkAssets> ApkAssets::LoadTableImpl(std::unique_ptr<Asset> resources_asset, - const std::string& path, - package_property_t property_flags) { +std::unique_ptr<const ApkAssets> ApkAssets::LoadTableImpl( + std::unique_ptr<Asset> resources_asset, const std::string& path, + package_property_t property_flags, std::unique_ptr<const AssetsProvider> override_assets) { + const time_t last_mod_time = getFileModDate(path.c_str()); + auto assets = (override_assets) ? std::move(override_assets) + : std::unique_ptr<AssetsProvider>(new EmptyAssetsProvider()); + std::unique_ptr<ApkAssets> loaded_apk( - new ApkAssets(std::unique_ptr<AssetsProvider>(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 944476890af0..879b050b65bd 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<const ApkAssets> Load(const std::string& path, - package_property_t flags = 0U); + static std::unique_ptr<const ApkAssets> Load( + const std::string& path, package_property_t flags = 0U, + std::unique_ptr<const AssetsProvider> override_asset = nullptr); // Creates an ApkAssets from the given file descriptor, and takes ownership of the file // descriptor. The `friendly_name` is some name that will be used to identify the source of // this ApkAssets in log messages and other debug scenarios. // If `length` equals kUnknownLength, offset must equal 0; otherwise, the apk data will be read // using the `offset` into the file descriptor and will be `length` bytes long. - static std::unique_ptr<const ApkAssets> LoadFromFd(base::unique_fd fd, - const std::string& friendly_name, - package_property_t flags = 0U, - off64_t offset = 0, - off64_t length = kUnknownLength); + static std::unique_ptr<const ApkAssets> LoadFromFd( + base::unique_fd fd, const std::string& friendly_name, package_property_t flags = 0U, + std::unique_ptr<const AssetsProvider> override_asset = nullptr, off64_t offset = 0, + off64_t length = kUnknownLength); // Creates an ApkAssets from the given path which points to a resources.arsc. - static std::unique_ptr<const ApkAssets> LoadTable(const std::string& path, - package_property_t flags = 0U); + static std::unique_ptr<const ApkAssets> LoadTable( + const std::string& path, package_property_t flags = 0U, + std::unique_ptr<const AssetsProvider> override_asset = nullptr); // Creates an ApkAssets from the given file descriptor which points to an resources.arsc, and // takes ownership of the file descriptor. // If `length` equals kUnknownLength, offset must equal 0; otherwise, the .arsc data will be read // using the `offset` into the file descriptor and will be `length` bytes long. - static std::unique_ptr<const ApkAssets> LoadTableFromFd(base::unique_fd fd, - const std::string& friendly_name, - package_property_t flags = 0U, - off64_t offset = 0, - off64_t length = kUnknownLength); + static std::unique_ptr<const ApkAssets> LoadTableFromFd( + base::unique_fd fd, const std::string& friendly_name, package_property_t flags = 0U, + std::unique_ptr<const AssetsProvider> override_asset = nullptr, off64_t offset = 0, + off64_t length = kUnknownLength); // Creates an ApkAssets from an IDMAP, which contains the original APK path, and the overlay // data. @@ -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<const ApkAssets> LoadFromDir(const std::string& path, - package_property_t flags = 0U); + static std::unique_ptr<const ApkAssets> LoadFromDir( + const std::string& path, package_property_t flags = 0U, + std::unique_ptr<const AssetsProvider> override_asset = nullptr); // Creates a totally empty ApkAssets with no resources table and no file entries. - static std::unique_ptr<const ApkAssets> LoadEmpty(package_property_t flags = 0U); + static std::unique_ptr<const ApkAssets> LoadEmpty( + package_property_t flags = 0U, + std::unique_ptr<const AssetsProvider> override_asset = nullptr); inline const std::string& GetPath() const { return path_; @@ -158,15 +161,17 @@ class ApkAssets { private: DISALLOW_COPY_AND_ASSIGN(ApkAssets); - static std::unique_ptr<const ApkAssets> LoadImpl(std::unique_ptr<const AssetsProvider> assets, - const std::string& path, - std::unique_ptr<Asset> idmap_asset, - std::unique_ptr<const LoadedIdmap> idmap, - package_property_t property_flags); - - static std::unique_ptr<const ApkAssets> LoadTableImpl(std::unique_ptr<Asset> resources_asset, - const std::string& path, - package_property_t property_flags); + static std::unique_ptr<const ApkAssets> LoadImpl( + std::unique_ptr<const AssetsProvider> assets, const std::string& path, + package_property_t property_flags, + std::unique_ptr<const AssetsProvider> override_assets = nullptr, + std::unique_ptr<Asset> idmap_asset = nullptr, + std::unique_ptr<const LoadedIdmap> idmap = nullptr); + + static std::unique_ptr<const ApkAssets> LoadTableImpl( + std::unique_ptr<Asset> resources_asset, const std::string& path, + package_property_t property_flags, + std::unique_ptr<const AssetsProvider> override_assets = nullptr); ApkAssets(std::unique_ptr<const AssetsProvider> assets_provider, std::string path, diff --git a/startop/view_compiler/apk_layout_compiler.cc b/startop/view_compiler/apk_layout_compiler.cc index e70c68852b67..eaa3e04cc814 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); } |