Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 4ea1e428 authored by Ryan Mitchell's avatar Ryan Mitchell
Browse files

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
parent c07aa702
Loading
Loading
Loading
Loading
+1 −9
Original line number Diff line number Diff line
@@ -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;
+1 −1
Original line number Diff line number Diff line
@@ -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,
+1 −1
Original line number Diff line number Diff line
@@ -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(
+72 −29
Original line number Diff line number Diff line
@@ -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
@@ -311,6 +344,14 @@ public final class ApkAssets {
        return (mFlags & PROPERTY_LOADER) != 0;
    }

    /**
     * Returns the assets provider that overrides the loading of assets present in this apk assets.
     */
    @Nullable
    public AssetsProvider getAssetsProvider() {
        return mAssets;
    }

    /**
     * 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
@@ -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);
+1 −154
Original line number Diff line number Diff line
@@ -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);
Loading