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
diff --git a/api/current.txt b/api/current.txt
index 5ae6590..8a35cc7 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -12846,14 +12846,7 @@
 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 @@
   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 5ade261..24589cf 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -1441,7 +1441,7 @@
         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 88b4c29..27399e4 100644
--- a/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
+++ b/core/java/android/content/pm/parsing/ApkLiteParseUtils.java
@@ -220,7 +220,7 @@
         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 078d175..bc41806 100644
--- a/core/java/android/content/res/ApkAssets.java
+++ b/core/java/android/content/res/ApkAssets.java
@@ -20,6 +20,7 @@
 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 @@
     @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 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 @@
      * @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 @@
      * @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 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 @@
      * @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 @@
      * @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 @@
      *
      * @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 @@
      * 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 {
-        Objects.requireNonNull(fd, "fd");
-        Objects.requireNonNull(friendlyName, "friendlyName");
-        mFlags = flags;
-        mNativePtr = nativeLoadFd(format, fd, friendlyName, flags);
-        mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
-    }
-
-    private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd,
-            @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags)
+            @NonNull String friendlyName, @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 = nativeLoadFd(format, fd, friendlyName, flags, assets);
         mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
+        mAssets = assets;
     }
 
-    private ApkAssets(@PropertyFlags int flags) {
+    private ApkAssets(@FormatType int format, @NonNull FileDescriptor fd,
+            @NonNull String friendlyName, long offset, long length, @PropertyFlags int flags,
+            @Nullable AssetsProvider assets) throws IOException {
+        Objects.requireNonNull(fd, "fd");
+        Objects.requireNonNull(friendlyName, "friendlyName");
         mFlags = flags;
-        mNativePtr = nativeLoadEmpty(flags);
+        mNativePtr = nativeLoadFdOffsets(format, fd, friendlyName, offset, length, flags, assets);
+        mStringBlock = new StringBlock(nativeGetStringBlock(mNativePtr), true /*useSparse*/);
+        mAssets = assets;
+    }
+
+    private ApkAssets(@PropertyFlags int flags, @Nullable AssetsProvider assets) {
+        mFlags = flags;
+        mNativePtr = nativeLoadEmpty(flags, assets);
         mStringBlock = null;
+        mAssets = assets;
     }
 
     @UnsupportedAppUsage
@@ -312,6 +345,14 @@
     }
 
     /**
+     * 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 @@
     }
 
     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 6b9613d..7b2b939 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -27,9 +27,7 @@
 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.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 @@
         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 @@
         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 @@
         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 @@
         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 @@
         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 @@
         }
     }
 
-    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 c315494..0f8f1d1 100644
--- a/core/java/android/content/res/loader/AssetsProvider.java
+++ b/core/java/android/content/res/loader/AssetsProvider.java
@@ -18,12 +18,10 @@
 
 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 @@
      * 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 @@
      * @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 81c2a4c..0000000
--- 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 040b369..0a698d1 100644
--- a/core/java/android/content/res/loader/ResourcesProvider.java
+++ b/core/java/android/content/res/loader/ResourcesProvider.java
@@ -50,8 +50,6 @@
     @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 @@
      */
     @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 @@
             @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 @@
             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 @@
             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 @@
             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 @@
         }
 
         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 @@
     @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 6acb133..fbdd406 100644
--- a/core/jni/android_content_res_ApkAssets.cpp
+++ b/core/jni/android_content_res_ApkAssets.cpp
@@ -37,6 +37,21 @@
   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 @@
   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 @@
 
   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 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 @@
     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 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 @@
     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 @@
   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 @@
 
 // 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 @@
   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 062b886..cb5a332 100644
--- a/core/jni/android_util_AssetManager.cpp
+++ b/core/jni/android_util_AssetManager.cpp
@@ -75,12 +75,6 @@
   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 @@
       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 0000000..8e62cc3
--- /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 0000000..0e41ffa
--- /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 0000000..bca782e
--- /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 0000000..bae8ef7
--- /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 0000000..b75d996
--- /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 da5092d..0000000
--- 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 bd2bac5..4764c10 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.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 @@
     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 @@
         }
     }
 
-    protected fun String.openProvider(dataType: DataType)
-            :ResourcesProvider = when (dataType) {
-        DataType.APK_DISK_FD -> {
-            val file = context.copiedAssetFile("${this}.apk")
-            ResourcesProvider.loadFromApk(ParcelFileDescriptor.fromFd(file.fd)).apply {
-                file.close()
+    protected fun String.openProvider(dataType: DataType, assetsProvider: MemoryAssetsProvider?)
+            :ResourcesProvider {
+        if (assetsProvider != null) {
+            openedObjects += assetsProvider
+        }
+        return when (dataType) {
+            DataType.APK_DISK_FD -> {
+                val file = context.copiedAssetFile("${this}.apk")
+                ResourcesProvider.loadFromApk(ParcelFileDescriptor.fromFd(file.fd),
+                        assetsProvider).apply {
+                    file.close()
+                }
             }
-        }
-        DataType.APK_DISK_FD_OFFSETS -> {
-            val asset = context.assets.openFd("${this}.apk")
-            ResourcesProvider.loadFromApk(asset.parcelFileDescriptor, asset.startOffset,
-                    asset.length, null).apply {
-                asset.close()
+            DataType.APK_DISK_FD_OFFSETS -> {
+                val asset = context.assets.openFd("${this}.apk")
+                ResourcesProvider.loadFromApk(asset.parcelFileDescriptor, asset.startOffset,
+                        asset.length, assetsProvider).apply {
+                    asset.close()
+                }
             }
-        }
-        DataType.ARSC_DISK_FD -> {
-            val file = context.copiedAssetFile("${this}.arsc")
-            ResourcesProvider.loadFromTable(ParcelFileDescriptor.fromFd(file.fd), null).apply {
-                file.close()
+            DataType.ARSC_DISK_FD -> {
+                val file = context.copiedAssetFile("${this}.arsc")
+                ResourcesProvider.loadFromTable(ParcelFileDescriptor.fromFd(file.fd),
+                        assetsProvider).apply {
+                    file.close()
+                }
             }
-        }
-        DataType.ARSC_DISK_FD_OFFSETS -> {
-            val asset = context.assets.openFd("${this}.arsc")
-            ResourcesProvider.loadFromTable(asset.parcelFileDescriptor, asset.startOffset,
-                    asset.length, null).apply {
-                asset.close()
+            DataType.ARSC_DISK_FD_OFFSETS -> {
+                val asset = context.assets.openFd("${this}.arsc")
+                ResourcesProvider.loadFromTable(asset.parcelFileDescriptor, asset.startOffset,
+                        asset.length, assetsProvider).apply {
+                    asset.close()
+                }
             }
-        }
-        DataType.APK_RAM_OFFSETS -> {
-            val asset = context.assets.openFd("${this}.apk")
-            val leadingGarbageSize = 100L
-            val trailingGarbageSize = 55L
-            val fd = loadAssetIntoMemory(asset, leadingGarbageSize.toInt(),
-                    trailingGarbageSize.toInt())
-            ResourcesProvider.loadFromApk(fd, leadingGarbageSize, asset.declaredLength,
-                    null).apply {
-                asset.close()
-                fd.close()
+            DataType.APK_RAM_OFFSETS -> {
+                val asset = context.assets.openFd("${this}.apk")
+                val leadingGarbageSize = 100L
+                val trailingGarbageSize = 55L
+                val fd = loadAssetIntoMemory(asset, leadingGarbageSize.toInt(),
+                        trailingGarbageSize.toInt())
+                ResourcesProvider.loadFromApk(fd, leadingGarbageSize, asset.declaredLength,
+                        assetsProvider).apply {
+                    asset.close()
+                    fd.close()
+                }
             }
-        }
-        DataType.APK_RAM_FD -> {
-            val asset = context.assets.openFd("${this}.apk")
-            var fd = loadAssetIntoMemory(asset)
-            ResourcesProvider.loadFromApk(fd).apply {
-                asset.close()
-                fd.close()
+            DataType.APK_RAM_FD -> {
+                val asset = context.assets.openFd("${this}.apk")
+                var fd = loadAssetIntoMemory(asset)
+                ResourcesProvider.loadFromApk(fd, assetsProvider).apply {
+                    asset.close()
+                    fd.close()
+                }
             }
-        }
-        DataType.ARSC_RAM_MEMORY -> {
-            val asset = context.assets.openFd("${this}.arsc")
-            var fd = loadAssetIntoMemory(asset)
-            ResourcesProvider.loadFromTable(fd, null).apply {
-                asset.close()
-                fd.close()
+            DataType.ARSC_RAM_MEMORY -> {
+                val asset = context.assets.openFd("${this}.arsc")
+                var fd = loadAssetIntoMemory(asset)
+                ResourcesProvider.loadFromTable(fd, assetsProvider).apply {
+                    asset.close()
+                    fd.close()
+                }
             }
-        }
-        DataType.ARSC_RAM_MEMORY_OFFSETS -> {
-            val asset = context.assets.openFd("${this}.arsc")
-            val leadingGarbageSize = 100L
-            val trailingGarbageSize = 55L
-            val fd = loadAssetIntoMemory(asset, leadingGarbageSize.toInt(),
-                    trailingGarbageSize.toInt())
-            ResourcesProvider.loadFromTable(fd, leadingGarbageSize, asset.declaredLength,
-                    null).apply {
-                asset.close()
-                fd.close()
+            DataType.ARSC_RAM_MEMORY_OFFSETS -> {
+                val asset = context.assets.openFd("${this}.arsc")
+                val leadingGarbageSize = 100L
+                val trailingGarbageSize = 55L
+                val fd = loadAssetIntoMemory(asset, leadingGarbageSize.toInt(),
+                        trailingGarbageSize.toInt())
+                ResourcesProvider.loadFromTable(fd, leadingGarbageSize, asset.declaredLength,
+                        assetsProvider).apply {
+                    asset.close()
+                    fd.close()
+                }
             }
-        }
-        DataType.SPLIT -> {
-            ResourcesProvider.loadFromSplit(context, "${this}_Split")
-        }
-        DataType.DIRECTORY -> {
-            ResourcesProvider.loadFromDirectory(zipToDir("${this}.apk").absolutePath, null)
+            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")
+            }
         }
     }
 
+    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 @@
         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 c01db0d7..a994536 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.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 @@
     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 @@
 
             // 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 @@
                             }
                     )),
                     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 @@
     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 @@
         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 @@
 
         loader1.removeProvider(testOne)
         assertEquals(valueFour, getValue())
+
+        loader2.removeProvider(testFour)
+        assertEquals(valueThree, getValue())
     }
 
     private fun createContext(context: Context, id: Int): Context {
@@ -644,15 +714,29 @@
     }
 
     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 f5bf84f..202651d 100644
--- a/libs/androidfw/ApkAssets.cpp
+++ b/libs/androidfw/ApkAssets.cpp
@@ -144,8 +144,8 @@
   }
 
  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 @@
   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 @@
   
   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 @@
                                           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 @@
   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 9444768..879b050 100644
--- a/libs/androidfw/include/androidfw/ApkAssets.h
+++ b/libs/androidfw/include/androidfw/ApkAssets.h
@@ -75,33 +75,33 @@
   // 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 @@
 
   // 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 @@
  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> 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);
+  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 e70c688..eaa3e04 100644
--- a/startop/view_compiler/apk_layout_compiler.cc
+++ b/startop/view_compiler/apk_layout_compiler.cc
@@ -168,8 +168,7 @@
 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);
 }