Refactor ResourcesLoader APIs

This changes refactors the ResourcesLoader APIs.
The main changes are:

Rather than pairing a ResourcesLoader with a ResourcesProvider, a
ResourcesProvider is paired with an AssetsProvider which is only
responsible for overriding the values of file-base resources and
assets. An AssetsProvider can be shared between multiple
ResourcesProviders.

ResourcesLoader now holds a list of ResourcesProviders.

ResourcesLoaders are part of ResourcesKeys and requests for resources
with the same loaders will use the same underlying ResourcesImpl. This
allows the loader specific code in RM to be cleaned up.

ResourcesLoaders and Resources objects use callbacks to notify RM
of changes to the Resources instance that may require a new
ResourcesImpl.

When a context is created from another context, the new context will
include the loaders of the original context. Change to list of either
context's loaders will not change the loaders of the other context,
but changes to the providers a loaders uses will update all Resources
objects that use that loader.

Activity resources will include the loaders of the application context
at the time of the Activity's creation.

Bug: 147359613
Test: atest ResourceLoaderTests
Change-Id: I2957c803d3f0c1280abfd3c723d76b18df2c3789
diff --git a/api/current.txt b/api/current.txt
index 4c3110c..2303f8d 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -12635,8 +12635,8 @@
 
   public class Resources {
     ctor @Deprecated public Resources(android.content.res.AssetManager, android.util.DisplayMetrics, android.content.res.Configuration);
-    method public void addLoader(@NonNull android.content.res.loader.ResourceLoader, @NonNull android.content.res.loader.ResourcesProvider, @IntRange(from=0) int);
-    method public int addLoader(@NonNull android.content.res.loader.ResourceLoader, @NonNull android.content.res.loader.ResourcesProvider);
+    method public void addLoader(@NonNull android.content.res.loader.ResourcesLoader);
+    method public void clearLoaders();
     method public final void finishPreloading();
     method public final void flushLayoutCache();
     method @NonNull public android.content.res.XmlResourceParser getAnimation(@AnimRes @AnimatorRes int) throws android.content.res.Resources.NotFoundException;
@@ -12663,7 +12663,7 @@
     method @NonNull public int[] getIntArray(@ArrayRes int) throws android.content.res.Resources.NotFoundException;
     method public int getInteger(@IntegerRes int) throws android.content.res.Resources.NotFoundException;
     method @NonNull public android.content.res.XmlResourceParser getLayout(@LayoutRes int) throws android.content.res.Resources.NotFoundException;
-    method @NonNull public java.util.List<android.util.Pair<android.content.res.loader.ResourceLoader,android.content.res.loader.ResourcesProvider>> getLoaders();
+    method @NonNull public java.util.List<android.content.res.loader.ResourcesLoader> getLoaders();
     method @Deprecated public android.graphics.Movie getMovie(@RawRes int) throws android.content.res.Resources.NotFoundException;
     method @NonNull public String getQuantityString(@PluralsRes int, int, java.lang.Object...) throws android.content.res.Resources.NotFoundException;
     method @NonNull public String getQuantityString(@PluralsRes int, int) throws android.content.res.Resources.NotFoundException;
@@ -12691,8 +12691,8 @@
     method public android.content.res.AssetFileDescriptor openRawResourceFd(@RawRes int) throws android.content.res.Resources.NotFoundException;
     method public void parseBundleExtra(String, android.util.AttributeSet, android.os.Bundle) throws org.xmlpull.v1.XmlPullParserException;
     method public void parseBundleExtras(android.content.res.XmlResourceParser, android.os.Bundle) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
-    method public int removeLoader(@NonNull android.content.res.loader.ResourceLoader);
-    method public void setLoaders(@Nullable java.util.List<android.util.Pair<android.content.res.loader.ResourceLoader,android.content.res.loader.ResourcesProvider>>);
+    method public void removeLoader(@NonNull android.content.res.loader.ResourcesLoader);
+    method public void setLoaders(@NonNull java.util.List<android.content.res.loader.ResourcesLoader>);
     method @Deprecated public void updateConfiguration(android.content.res.Configuration, android.util.DisplayMetrics);
     field @AnyRes public static final int ID_NULL = 0; // 0x0
   }
@@ -12762,27 +12762,37 @@
 
 package android.content.res.loader {
 
-  public class DirectoryResourceLoader implements android.content.res.loader.ResourceLoader {
-    ctor public DirectoryResourceLoader(@NonNull java.io.File);
+  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();
   }
 
-  public interface ResourceLoader {
-    method @Nullable public default java.io.InputStream loadAsset(@NonNull String, int) throws java.io.IOException;
-    method @Nullable public default android.os.ParcelFileDescriptor loadAssetFd(@NonNull String) throws java.io.IOException;
-    method @Nullable public default android.graphics.drawable.Drawable loadDrawable(@NonNull android.util.TypedValue, int, int, @Nullable android.content.res.Resources.Theme);
-    method @Nullable public default android.content.res.XmlResourceParser loadXmlResourceParser(@NonNull String, @AnyRes int);
+  public class ResourcesLoader {
+    ctor public ResourcesLoader();
+    method public void addProvider(@NonNull android.content.res.loader.ResourcesProvider);
+    method public void clearProviders();
+    method @NonNull public java.util.List<android.content.res.loader.ResourcesProvider> getProviders();
+    method public void removeProvider(@NonNull android.content.res.loader.ResourcesProvider);
+    method public void setProviders(@NonNull java.util.List<android.content.res.loader.ResourcesProvider>);
   }
 
-  public final class ResourcesProvider implements java.lang.AutoCloseable java.io.Closeable {
+  public class ResourcesProvider implements java.lang.AutoCloseable java.io.Closeable {
     method public void close();
-    method @NonNull public static android.content.res.loader.ResourcesProvider empty();
+    method @NonNull public static android.content.res.loader.ResourcesProvider empty(@NonNull android.content.res.loader.AssetsProvider);
+    method @Nullable public android.content.res.loader.AssetsProvider getAssetsProvider();
     method @NonNull public static android.content.res.loader.ResourcesProvider loadFromApk(@NonNull android.os.ParcelFileDescriptor) throws java.io.IOException;
+    method @NonNull public static android.content.res.loader.ResourcesProvider loadFromApk(@NonNull android.os.ParcelFileDescriptor, @Nullable android.content.res.loader.AssetsProvider) throws java.io.IOException;
     method @NonNull public static android.content.res.loader.ResourcesProvider loadFromApk(@NonNull android.os.SharedMemory) throws java.io.IOException;
-    method @NonNull public static android.content.res.loader.ResourcesProvider loadFromArsc(@NonNull android.os.ParcelFileDescriptor) throws java.io.IOException;
-    method @NonNull public static android.content.res.loader.ResourcesProvider loadFromArsc(@NonNull android.os.SharedMemory) throws java.io.IOException;
+    method @NonNull public static android.content.res.loader.ResourcesProvider loadFromApk(@NonNull android.os.SharedMemory, @Nullable android.content.res.loader.AssetsProvider) throws java.io.IOException;
     method @NonNull public static android.content.res.loader.ResourcesProvider loadFromSplit(@NonNull android.content.Context, @NonNull String) throws java.io.IOException;
+    method @NonNull public static android.content.res.loader.ResourcesProvider loadFromTable(@NonNull android.os.ParcelFileDescriptor, @Nullable android.content.res.loader.AssetsProvider) throws java.io.IOException;
+    method @NonNull public static android.content.res.loader.ResourcesProvider loadFromTable(@NonNull android.os.SharedMemory, @Nullable android.content.res.loader.AssetsProvider) throws java.io.IOException;
   }
 
 }
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 2ca5b1d..247ddb6 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -2206,7 +2206,7 @@
     Resources getTopLevelResources(String resDir, String[] splitResDirs, String[] overlayDirs,
             String[] libDirs, int displayId, LoadedApk pkgInfo) {
         return mResourcesManager.getResources(null, resDir, splitResDirs, overlayDirs, libDirs,
-                displayId, null, pkgInfo.getCompatibilityInfo(), pkgInfo.getClassLoader());
+                displayId, null, pkgInfo.getCompatibilityInfo(), pkgInfo.getClassLoader(), null);
     }
 
     @UnsupportedAppUsage
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index b7555ee..136c84e 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -46,6 +46,7 @@
 import android.content.res.CompatibilityInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
+import android.content.res.loader.ResourcesLoader;
 import android.database.DatabaseErrorHandler;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteDatabase.CursorFactory;
@@ -100,6 +101,7 @@
 import java.nio.ByteOrder;
 import java.nio.file.Path;
 import java.util.ArrayList;
+import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 
@@ -2217,7 +2219,8 @@
     }
 
     private static Resources createResources(IBinder activityToken, LoadedApk pi, String splitName,
-            int displayId, Configuration overrideConfig, CompatibilityInfo compatInfo) {
+            int displayId, Configuration overrideConfig, CompatibilityInfo compatInfo,
+            List<ResourcesLoader> resourcesLoader) {
         final String[] splitResDirs;
         final ClassLoader classLoader;
         try {
@@ -2234,7 +2237,8 @@
                 displayId,
                 overrideConfig,
                 compatInfo,
-                classLoader);
+                classLoader,
+                resourcesLoader);
     }
 
     @Override
@@ -2249,7 +2253,7 @@
             final int displayId = getDisplayId();
 
             c.setResources(createResources(mToken, pi, null, displayId, null,
-                    getDisplayAdjustments(displayId).getCompatibilityInfo()));
+                    getDisplayAdjustments(displayId).getCompatibilityInfo(), null));
             if (c.mResources != null) {
                 return c;
             }
@@ -2284,7 +2288,7 @@
             final int displayId = getDisplayId();
 
             c.setResources(createResources(mToken, pi, null, displayId, null,
-                    getDisplayAdjustments(displayId).getCompatibilityInfo()));
+                    getDisplayAdjustments(displayId).getCompatibilityInfo(), null));
             if (c.mResources != null) {
                 return c;
             }
@@ -2328,7 +2332,8 @@
                 displayId,
                 null,
                 mPackageInfo.getCompatibilityInfo(),
-                classLoader));
+                classLoader,
+                mResources.getLoaders()));
         return context;
     }
 
@@ -2342,8 +2347,10 @@
                 mSplitName, mToken, mUser, mFlags, mClassLoader, null);
 
         final int displayId = getDisplayId();
+
         context.setResources(createResources(mToken, mPackageInfo, mSplitName, displayId,
-                overrideConfiguration, getDisplayAdjustments(displayId).getCompatibilityInfo()));
+                overrideConfiguration, getDisplayAdjustments(displayId).getCompatibilityInfo(),
+                mResources.getLoaders()));
         return context;
     }
 
@@ -2357,8 +2364,10 @@
                 mSplitName, mToken, mUser, mFlags, mClassLoader, null);
 
         final int displayId = display.getDisplayId();
+
         context.setResources(createResources(mToken, mPackageInfo, mSplitName, displayId,
-                null, getDisplayAdjustments(displayId).getCompatibilityInfo()));
+                null, getDisplayAdjustments(displayId).getCompatibilityInfo(),
+                mResources.getLoaders()));
         context.mDisplay = display;
         return context;
     }
@@ -2564,7 +2573,7 @@
         ContextImpl context = new ContextImpl(null, systemContext.mMainThread, packageInfo, null,
                 null, null, null, 0, null, null);
         context.setResources(createResources(null, packageInfo, null, displayId, null,
-                packageInfo.getCompatibilityInfo()));
+                packageInfo.getCompatibilityInfo(), null));
         context.updateDisplay(displayId);
         context.mIsSystemOrSystemUiContext = true;
         return context;
@@ -2637,7 +2646,8 @@
                 displayId,
                 overrideConfiguration,
                 compatInfo,
-                classLoader));
+                classLoader,
+                packageInfo.getApplication().getResources().getLoaders()));
         context.mDisplay = resourcesManager.getAdjustedDisplay(displayId,
                 context.getResources());
         return context;
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index f0d0e98..44c2486 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -365,7 +365,8 @@
                 mResources = ResourcesManager.getInstance().getResources(null, mResDir,
                         splitPaths, mOverlayDirs, mApplicationInfo.sharedLibraryFiles,
                         Display.DEFAULT_DISPLAY, null, getCompatibilityInfo(),
-                        getClassLoader());
+                        getClassLoader(), mApplication == null ? null
+                                : mApplication.getResources().getLoaders());
             }
         }
         mAppComponentFactory = createAppFactory(aInfo, mDefaultClassLoader);
@@ -1158,7 +1159,7 @@
             mResources = ResourcesManager.getInstance().getResources(null, mResDir,
                     splitPaths, mOverlayDirs, mApplicationInfo.sharedLibraryFiles,
                     Display.DEFAULT_DISPLAY, null, getCompatibilityInfo(),
-                    getClassLoader());
+                    getClassLoader(), null);
         }
         return mResources;
     }
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 7ab85a4..d09f0bc 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -32,7 +32,7 @@
 import android.content.res.Resources;
 import android.content.res.ResourcesImpl;
 import android.content.res.ResourcesKey;
-import android.content.res.loader.ResourceLoader;
+import android.content.res.loader.ResourcesLoader;
 import android.hardware.display.DisplayManagerGlobal;
 import android.os.IBinder;
 import android.os.Process;
@@ -46,7 +46,6 @@
 import android.view.Display;
 import android.view.DisplayAdjustments;
 
-import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.IndentingPrintWriter;
@@ -57,9 +56,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.List;
-import java.util.Map;
 import java.util.Objects;
 import java.util.WeakHashMap;
 import java.util.function.Predicate;
@@ -98,52 +95,6 @@
             new ArrayMap<>();
 
     /**
-     * A list of {@link Resources} that contain unique {@link ResourcesImpl}s.
-     *
-     * These are isolated so that {@link ResourceLoader}s can be added and removed without
-     * affecting other instances.
-     *
-     * When a reference is added here, it is guaranteed that the {@link ResourcesImpl}
-     * it contains is unique to itself and will never be set to a shared reference.
-     */
-    @GuardedBy("this")
-    private List<ResourcesWithLoaders> mResourcesWithLoaders = Collections.emptyList();
-
-    private static class ResourcesWithLoaders {
-
-        private WeakReference<Resources> mResources;
-        private ResourcesKey mResourcesKey;
-
-        @Nullable
-        private WeakReference<IBinder> mActivityToken;
-
-        ResourcesWithLoaders(Resources resources, ResourcesKey resourcesKey,
-                IBinder activityToken) {
-            this.mResources = new WeakReference<>(resources);
-            this.mResourcesKey = resourcesKey;
-            this.mActivityToken = new WeakReference<>(activityToken);
-        }
-
-        @Nullable
-        Resources resources() {
-            return mResources.get();
-        }
-
-        @Nullable
-        IBinder activityToken() {
-            return mActivityToken == null ? null : mActivityToken.get();
-        }
-
-        ResourcesKey resourcesKey() {
-            return mResourcesKey;
-        }
-
-        void updateKey(ResourcesKey newKey) {
-            mResourcesKey = newKey;
-        }
-    }
-
-    /**
      * A list of Resource references that can be reused.
      */
     @UnsupportedAppUsage
@@ -219,6 +170,11 @@
     private final ArrayMap<Pair<Integer, DisplayAdjustments>, WeakReference<Display>>
             mAdjustedDisplays = new ArrayMap<>();
 
+    /**
+     * Callback implementation for handling updates to Resources objects.
+     */
+    private final UpdateHandler mUpdateCallbacks = new UpdateHandler();
+
     @UnsupportedAppUsage
     public ResourcesManager() {
     }
@@ -253,24 +209,6 @@
                 }
             }
 
-            for (int i = mResourcesWithLoaders.size() - 1; i >= 0; i--) {
-                ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(i);
-                Resources resources = resourcesWithLoaders.resources();
-                if (resources == null) {
-                    continue;
-                }
-
-                final ResourcesKey key = resourcesWithLoaders.resourcesKey();
-                if (key.isPathReferenced(path)) {
-                    mResourcesWithLoaders.remove(i);
-                    ResourcesImpl impl = resources.getImpl();
-                    if (impl != null) {
-                        impl.flushLayoutCache();
-                    }
-                    count++;
-                }
-            }
-
             Log.i(TAG, "Invalidated " + count + " asset managers that referenced " + path);
 
             for (int i = mCachedApkAssets.size() - 1; i >= 0; i--) {
@@ -513,6 +451,12 @@
             }
         }
 
+        if (key.mLoaders != null) {
+            for (final ResourcesLoader loader : key.mLoaders) {
+                builder.addLoader(loader);
+            }
+        }
+
         return builder.build();
     }
 
@@ -570,16 +514,6 @@
 
             pw.print("resource impls: ");
             pw.println(countLiveReferences(mResourceImpls.values()));
-
-            int resourcesWithLoadersCount = 0;
-            for (int index = 0; index < mResourcesWithLoaders.size(); index++) {
-                if (mResourcesWithLoaders.get(index).resources() != null) {
-                    resourcesWithLoadersCount++;
-                }
-            }
-
-            pw.print("resources with loaders: ");
-            pw.println(resourcesWithLoadersCount);
         }
     }
 
@@ -660,19 +594,6 @@
      */
     private @Nullable ResourcesKey findKeyForResourceImplLocked(
             @NonNull ResourcesImpl resourceImpl) {
-        int size = mResourcesWithLoaders.size();
-        for (int index = 0; index < size; index++) {
-            ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(index);
-            Resources resources = resourcesWithLoaders.resources();
-            if (resources == null) {
-                continue;
-            }
-
-            if (resourceImpl == resources.getImpl()) {
-                return resourcesWithLoaders.resourcesKey();
-            }
-        }
-
         int refCount = mResourceImpls.size();
         for (int i = 0; i < refCount; i++) {
             WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
@@ -722,30 +643,10 @@
     @Nullable
     private Resources findResourcesForActivityLocked(@NonNull IBinder targetActivityToken,
             @NonNull ResourcesKey targetKey, @NonNull ClassLoader targetClassLoader) {
-        int size = mResourcesWithLoaders.size();
-        for (int index = 0; index < size; index++) {
-            ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(index);
-            Resources resources = resourcesWithLoaders.resources();
-            if (resources == null) {
-                continue;
-            }
-
-            IBinder activityToken = resourcesWithLoaders.activityToken();
-            ResourcesKey key = resourcesWithLoaders.resourcesKey();
-
-            ClassLoader classLoader = resources.getClassLoader();
-
-            if (Objects.equals(activityToken, targetActivityToken)
-                    && Objects.equals(key, targetKey)
-                    && Objects.equals(classLoader, targetClassLoader)) {
-                return resources;
-            }
-        }
-
         ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
                 targetActivityToken);
 
-        size = activityResources.activityResources.size();
+        final int size = activityResources.activityResources.size();
         for (int index = 0; index < size; index++) {
             WeakReference<Resources> ref = activityResources.activityResources.get(index);
             Resources resources = ref.get();
@@ -771,6 +672,7 @@
         Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
                 : new Resources(classLoader);
         resources.setImpl(impl);
+        resources.setCallbacks(mUpdateCallbacks);
         activityResources.activityResources.add(new WeakReference<>(resources));
         if (DEBUG) {
             Slog.d(TAG, "- creating new ref=" + resources);
@@ -784,6 +686,7 @@
         Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
                 : new Resources(classLoader);
         resources.setImpl(impl);
+        resources.setCallbacks(mUpdateCallbacks);
         mResourceReferences.add(new WeakReference<>(resources));
         if (DEBUG) {
             Slog.d(TAG, "- creating new ref=" + resources);
@@ -795,7 +698,7 @@
     /**
      * Creates base resources for an Activity. Calls to
      * {@link #getResources(IBinder, String, String[], String[], String[], int, Configuration,
-     * CompatibilityInfo, ClassLoader)} with the same activityToken will have their override
+     * CompatibilityInfo, ClassLoader, List)} with the same activityToken will have their override
      * configurations merged with the one specified here.
      *
      * @param activityToken Represents an Activity.
@@ -820,7 +723,8 @@
             int displayId,
             @Nullable Configuration overrideConfig,
             @NonNull CompatibilityInfo compatInfo,
-            @Nullable ClassLoader classLoader) {
+            @Nullable ClassLoader classLoader,
+            @Nullable List<ResourcesLoader> loaders) {
         try {
             Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
                     "ResourcesManager#createBaseActivityResources");
@@ -831,7 +735,8 @@
                     libDirs,
                     displayId,
                     overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
-                    compatInfo);
+                    compatInfo,
+                    loaders == null ? null : loaders.toArray(new ResourcesLoader[0]));
             classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
 
             if (DEBUG) {
@@ -902,14 +807,6 @@
             } else {
                 ArrayUtils.unstableRemoveIf(mResourceReferences, sEmptyReferencePredicate);
             }
-
-            for (int index = mResourcesWithLoaders.size() - 1; index >= 0; index--) {
-                ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(index);
-                Resources resources = resourcesWithLoaders.resources();
-                if (resources == null) {
-                    mResourcesWithLoaders.remove(index);
-                }
-            }
         }
     }
 
@@ -983,7 +880,8 @@
             int displayId,
             @Nullable Configuration overrideConfig,
             @NonNull CompatibilityInfo compatInfo,
-            @Nullable ClassLoader classLoader) {
+            @Nullable ClassLoader classLoader,
+            @Nullable List<ResourcesLoader> loaders) {
         try {
             Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesManager#getResources");
             final ResourcesKey key = new ResourcesKey(
@@ -993,7 +891,8 @@
                     libDirs,
                     displayId,
                     overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
-                    compatInfo);
+                    compatInfo,
+                    loaders == null ? null : loaders.toArray(new ResourcesLoader[0]));
             classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
 
             cleanupReferences(activityToken);
@@ -1010,10 +909,10 @@
 
     /**
      * Updates an Activity's Resources object with overrideConfig. The Resources object
-     * that was previously returned by
-     * {@link #getResources(IBinder, String, String[], String[], String[], int, Configuration,
-     * CompatibilityInfo, ClassLoader)} is
-     * still valid and will have the updated configuration.
+     * that was previously returned by {@link #getResources(IBinder, String, String[], String[],
+     * String[], int, Configuration, CompatibilityInfo, ClassLoader, List)} is still valid and will
+     * have the updated configuration.
+     *
      * @param activityToken The Activity token.
      * @param overrideConfig The configuration override to update.
      * @param displayId Id of the display where activity currently resides.
@@ -1074,25 +973,6 @@
                             overrideConfig, displayId);
                     updateActivityResources(resources, newKey, false);
                 }
-
-                // Also find loaders that are associated with an Activity
-                final int loaderCount = mResourcesWithLoaders.size();
-                for (int index = loaderCount - 1; index >= 0; index--) {
-                    ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(
-                            index);
-                    Resources resources = resourcesWithLoaders.resources();
-                    if (resources == null
-                            || resourcesWithLoaders.activityToken() != activityToken) {
-                        continue;
-                    }
-
-                    ResourcesKey newKey = rebaseActivityOverrideConfig(resources, oldConfig,
-                            overrideConfig, displayId);
-
-                    updateActivityResources(resources, newKey, true);
-
-                    resourcesWithLoaders.updateKey(newKey);
-                }
             }
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
@@ -1133,9 +1013,8 @@
 
         // Create the new ResourcesKey with the rebased override config.
         final ResourcesKey newKey = new ResourcesKey(oldKey.mResDir,
-                oldKey.mSplitResDirs,
-                oldKey.mOverlayDirs, oldKey.mLibDirs, displayId,
-                rebasedOverrideConfig, oldKey.mCompatInfo);
+                oldKey.mSplitResDirs, oldKey.mOverlayDirs, oldKey.mLibDirs,
+                displayId, rebasedOverrideConfig, oldKey.mCompatInfo, oldKey.mLoaders);
 
         if (DEBUG) {
             Slog.d(TAG, "rebasing ref=" + resources + " from oldKey=" + oldKey
@@ -1214,18 +1093,6 @@
                 }
             }
 
-            for (int index = mResourcesWithLoaders.size() - 1; index >= 0; index--) {
-                ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(index);
-                Resources resources = resourcesWithLoaders.resources();
-                if (resources == null) {
-                    mResourcesWithLoaders.remove(index);
-                    continue;
-                }
-
-                applyConfigurationToResourcesLocked(config, compat, tmpConfig,
-                        resourcesWithLoaders.resourcesKey(), resources.getImpl());
-            }
-
             return changes != 0;
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
@@ -1306,37 +1173,8 @@
                                 newLibAssets,
                                 key.mDisplayId,
                                 key.mOverrideConfiguration,
-                                key.mCompatInfo));
-                    }
-                }
-            }
-
-            final int count = mResourcesWithLoaders.size();
-            for (int index = 0; index < count; index++) {
-                ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(index);
-                Resources resources = resourcesWithLoaders.resources();
-                if (resources == null) {
-                    continue;
-                }
-
-                ResourcesKey key = resourcesWithLoaders.resourcesKey();
-                if (Objects.equals(key.mResDir, assetPath)) {
-                    String[] newLibAssets = key.mLibDirs;
-                    for (String libAsset : libAssets) {
-                        newLibAssets =
-                                ArrayUtils.appendElement(String.class, newLibAssets, libAsset);
-                    }
-
-                    if (!Arrays.equals(newLibAssets, key.mLibDirs)) {
-                        updatedResourceKeys.put(resources.getImpl(),
-                                new ResourcesKey(
-                                        key.mResDir,
-                                        key.mSplitResDirs,
-                                        key.mOverlayDirs,
-                                        newLibAssets,
-                                        key.mDisplayId,
-                                        key.mOverrideConfiguration,
-                                        key.mCompatInfo));
+                                key.mCompatInfo,
+                                key.mLoaders));
                     }
                 }
             }
@@ -1345,72 +1183,6 @@
         }
     }
 
-    /**
-     * Mark a {@link Resources} as containing a {@link ResourceLoader}.
-     *
-     * This removes its {@link ResourcesImpl} from the shared pool and creates it a new one. It is
-     * then tracked separately, kept unique, and restored properly across {@link Activity}
-     * recreations.
-     */
-    public void registerForLoaders(Resources resources) {
-        synchronized (this) {
-            boolean found = false;
-            IBinder activityToken = null;
-
-            // Remove the Resources from the reference list as it's now tracked separately
-            int size = mResourceReferences.size();
-            for (int index = 0; index < size; index++) {
-                WeakReference<Resources> reference = mResourceReferences.get(index);
-                if (reference.get() == resources) {
-                    mResourceReferences.remove(index);
-                    found = true;
-                    break;
-                }
-            }
-
-            if (!found) {
-                // Do the same removal for any Activity Resources
-                for (Map.Entry<IBinder, ActivityResources> entry :
-                        mActivityResourceReferences.entrySet()) {
-                    ArrayList<WeakReference<Resources>> activityResourcesList =
-                            entry.getValue().activityResources;
-                    final int resCount = activityResourcesList.size();
-                    for (int index = 0; index < resCount; index++) {
-                        WeakReference<Resources> reference = activityResourcesList.get(index);
-                        if (reference.get() == resources) {
-                            activityToken = entry.getKey();
-                            activityResourcesList.remove(index);
-                            found = true;
-                            break;
-                        }
-                    }
-
-                    if (found) {
-                        break;
-                    }
-                }
-            }
-
-            if (!found) {
-                throw new IllegalArgumentException("Resources " + resources
-                        + " registered for loaders but was not previously tracked by"
-                        + " ResourcesManager");
-            }
-
-            ResourcesKey key = findKeyForResourceImplLocked(resources.getImpl());
-            ResourcesImpl impl = createResourcesImpl(key);
-
-            if (mResourcesWithLoaders == Collections.EMPTY_LIST) {
-                mResourcesWithLoaders = Collections.synchronizedList(new ArrayList<>());
-            }
-
-            mResourcesWithLoaders.add(new ResourcesWithLoaders(resources, key, activityToken));
-
-            // Set the new Impl, which is now guaranteed to be unique per Resources object
-            resources.setImpl(impl);
-        }
-    }
-
     // TODO(adamlesinski): Make this accept more than just overlay directories.
     final void applyNewResourceDirsLocked(@NonNull final ApplicationInfo appInfo,
             @Nullable final String[] oldPaths) {
@@ -1450,37 +1222,12 @@
                             key.mLibDirs,
                             key.mDisplayId,
                             key.mOverrideConfiguration,
-                            key.mCompatInfo
+                            key.mCompatInfo,
+                            key.mLoaders
                     ));
                 }
             }
 
-            final int count = mResourcesWithLoaders.size();
-            for (int index = 0; index < count; index++) {
-                ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(index);
-                Resources resources = resourcesWithLoaders.resources();
-                if (resources == null) {
-                    continue;
-                }
-
-                ResourcesKey key = resourcesWithLoaders.resourcesKey();
-
-                if (key.mResDir == null
-                        || key.mResDir.equals(baseCodePath)
-                        || ArrayUtils.contains(oldPaths, key.mResDir)) {
-                    updatedResourceKeys.put(resources.getImpl(),
-                            new ResourcesKey(
-                                    baseCodePath,
-                                    copiedSplitDirs,
-                                    copiedResourceDirs,
-                                    key.mLibDirs,
-                                    key.mDisplayId,
-                                    key.mOverrideConfiguration,
-                                    key.mCompatInfo
-                            ));
-                }
-            }
-
             redirectResourcesToNewImplLocked(updatedResourceKeys);
         } finally {
             Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
@@ -1530,25 +1277,62 @@
                 }
             }
         }
+    }
 
-        // Update any references that need to be re-built with loaders. These are intentionally not
-        // part of either of the above lists.
-        final int loaderCount = mResourcesWithLoaders.size();
-        for (int index = loaderCount - 1; index >= 0; index--) {
-            ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(index);
-            Resources resources = resourcesWithLoaders.resources();
-            if (resources == null) {
-                continue;
+    private class UpdateHandler implements Resources.UpdateCallbacks {
+
+        /**
+         * Updates the list of {@link ResourcesLoader ResourcesLoader(s)} that the {@code resources}
+         * instance uses.
+         */
+        @Override
+        public void onLoadersChanged(Resources resources, List<ResourcesLoader> newLoader) {
+            synchronized (ResourcesManager.this) {
+                final ResourcesKey oldKey = findKeyForResourceImplLocked(resources.getImpl());
+                if (oldKey == null) {
+                    throw new IllegalArgumentException("Cannot modify resource loaders of"
+                            + " ResourcesImpl not registered with ResourcesManager");
+                }
+
+                final ResourcesKey newKey = new ResourcesKey(
+                        oldKey.mResDir,
+                        oldKey.mSplitResDirs,
+                        oldKey.mOverlayDirs,
+                        oldKey.mLibDirs,
+                        oldKey.mDisplayId,
+                        oldKey.mOverrideConfiguration,
+                        oldKey.mCompatInfo,
+                        newLoader.toArray(new ResourcesLoader[0]));
+
+                final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(newKey);
+                resources.setImpl(impl);
             }
+        }
 
-            ResourcesKey newKey = updatedResourceKeys.get(resources.getImpl());
-            if (newKey == null) {
-                continue;
+        /**
+         * Refreshes the {@link AssetManager} of all {@link ResourcesImpl} that contain the
+         * {@code loader} to apply any changes of the set of {@link ApkAssets}.
+         **/
+        @Override
+        public void onLoaderUpdated(ResourcesLoader loader) {
+            synchronized (ResourcesManager.this) {
+                final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceImplKeys =
+                        new ArrayMap<>();
+
+                for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
+                    final ResourcesKey key = mResourceImpls.keyAt(i);
+                    final WeakReference<ResourcesImpl> impl = mResourceImpls.valueAt(i);
+                    if (impl == null || impl.get() == null
+                            || !ArrayUtils.contains(key.mLoaders, loader)) {
+                        continue;
+                    }
+
+                    mResourceImpls.remove(key);
+                    updatedResourceImplKeys.put(impl.get(), key);
+                }
+
+                redirectResourcesToNewImplLocked(updatedResourceImplKeys);
             }
-
-            resourcesWithLoaders.updateKey(newKey);
-            resourcesWithLoaders.resources()
-                    .setImpl(createResourcesImpl(newKey));
         }
     }
 }
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index fd4c265..906ca10 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -8519,7 +8519,8 @@
                 Display.DEFAULT_DISPLAY,
                 null,
                 systemResources.getCompatibilityInfo(),
-                systemResources.getClassLoader());
+                systemResources.getClassLoader(),
+                null);
 
         sUseRoundIcon = overlayableRes.getBoolean(com.android.internal.R.bool.config_useRoundIcon);
     }
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index 96fbe91..1b01758 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -27,13 +27,12 @@
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration.NativeConfig;
-import android.content.res.loader.ResourceLoader;
-import android.content.res.loader.ResourceLoaderManager;
+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;
-import android.util.Pair;
 import android.util.SparseArray;
 import android.util.TypedValue;
 
@@ -47,6 +46,7 @@
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
@@ -113,12 +113,7 @@
     @GuardedBy("this") private int mNumRefs = 1;
     @GuardedBy("this") private HashMap<Long, RuntimeException> mRefStacks;
 
-    private ResourceLoaderManager mResourceLoaderManager;
-
-    /** @hide */
-    public void setResourceLoaderManager(ResourceLoaderManager resourceLoaderManager) {
-        mResourceLoaderManager = resourceLoaderManager;
-    }
+    private ResourcesLoader[] mLoaders;
 
     /**
      * A Builder class that helps create an AssetManager with only a single invocation of
@@ -130,32 +125,66 @@
      */
     public static class Builder {
         private ArrayList<ApkAssets> mUserApkAssets = new ArrayList<>();
+        private ArrayList<ResourcesLoader> mLoaders = new ArrayList<>();
 
         public Builder addApkAssets(ApkAssets apkAssets) {
             mUserApkAssets.add(apkAssets);
             return this;
         }
 
+        public Builder addLoader(ResourcesLoader loader) {
+            mLoaders.add(loader);
+            return this;
+        }
+
         public AssetManager build() {
             // Retrieving the system ApkAssets forces their creation as well.
             final ApkAssets[] systemApkAssets = getSystem().getApkAssets();
 
-            final int totalApkAssetCount = systemApkAssets.length + mUserApkAssets.size();
+            // Filter ApkAssets so that assets provided by multiple loaders are only included once
+            // in the AssetManager assets. The last appearance of the ApkAssets dictates its load
+            // order.
+            final ArrayList<ApkAssets> loaderApkAssets = new ArrayList<>();
+            final ArraySet<ApkAssets> uniqueLoaderApkAssets = new ArraySet<>();
+            for (int i = mLoaders.size() - 1; i >= 0; i--) {
+                final List<ApkAssets> currentLoaderApkAssets = mLoaders.get(i).getApkAssets();
+                for (int j = currentLoaderApkAssets.size() - 1; j >= 0; j--) {
+                    final ApkAssets apkAssets = currentLoaderApkAssets.get(j);
+                    if (uniqueLoaderApkAssets.contains(apkAssets)) {
+                        continue;
+                    }
+
+                    uniqueLoaderApkAssets.add(apkAssets);
+                    loaderApkAssets.add(0, apkAssets);
+                }
+            }
+
+            final int totalApkAssetCount = systemApkAssets.length + mUserApkAssets.size()
+                    + loaderApkAssets.size();
             final ApkAssets[] apkAssets = new ApkAssets[totalApkAssetCount];
 
             System.arraycopy(systemApkAssets, 0, apkAssets, 0, systemApkAssets.length);
 
-            final int userApkAssetCount = mUserApkAssets.size();
-            for (int i = 0; i < userApkAssetCount; i++) {
+            // Append user ApkAssets after system ApkAssets.
+            for (int i = 0, n = mUserApkAssets.size(); i < n; i++) {
                 apkAssets[i + systemApkAssets.length] = mUserApkAssets.get(i);
             }
 
+            // Append ApkAssets provided by loaders to the end.
+            for (int i = 0, n = loaderApkAssets.size(); i < n; i++) {
+                apkAssets[i + systemApkAssets.length  + mUserApkAssets.size()] =
+                        loaderApkAssets.get(i);
+            }
+
             // Calling this constructor prevents creation of system ApkAssets, which we took care
             // of in this Builder.
             final AssetManager assetManager = new AssetManager(false /*sentinel*/);
             assetManager.mApkAssets = apkAssets;
             AssetManager.nativeSetApkAssets(assetManager.mObject, apkAssets,
                     false /*invalidateCaches*/);
+            assetManager.mLoaders = mLoaders.isEmpty() ? null
+                    : mLoaders.toArray(new ResourcesLoader[0]);
+
             return assetManager;
         }
     }
@@ -432,6 +461,12 @@
         }
     }
 
+    /** @hide */
+    @NonNull
+    public List<ResourcesLoader> getLoaders() {
+        return mLoaders == null ? Collections.emptyList() : Arrays.asList(mLoaders);
+    }
+
     /**
      * Ensures that the native implementation has not been destroyed.
      * The AssetManager may have been closed, but references to it still exist
@@ -1056,38 +1091,70 @@
         }
     }
 
-    private InputStream searchLoaders(int cookie, @NonNull String fileName, int accessMode)
-            throws IOException {
-        if (mResourceLoaderManager == null) {
+    private ResourcesProvider findResourcesProvider(int assetCookie) {
+        if (mLoaders == null) {
             return null;
         }
 
-        List<Pair<ResourceLoader, ResourcesProvider>> loaders =
-                mResourceLoaderManager.getInternalList();
+        int apkAssetsIndex = assetCookie - 1;
+        if (apkAssetsIndex >= mApkAssets.length || apkAssetsIndex < 0) {
+            return null;
+        }
 
-        // A cookie of 0 means no specific ApkAssets, so search everything
+        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) {
-            for (int index = loaders.size() - 1; index >= 0; index--) {
-                Pair<ResourceLoader, ResourcesProvider> pair = loaders.get(index);
-                try {
-                    InputStream inputStream = pair.first.loadAsset(fileName, accessMode);
-                    if (inputStream != null) {
-                        return inputStream;
+            // 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;
                     }
-                } catch (IOException ignored) {
-                    // When searching, ignore read failures
+
+                    try {
+                        final InputStream inputStream = assetsProvider.loadAsset(
+                                fileName, accessMode);
+                        if (inputStream != null) {
+                            return inputStream;
+                        }
+                    } catch (IOException ignored) {
+                        // When searching, ignore read failures
+                    }
                 }
             }
 
             return null;
         }
 
-        ApkAssets apkAssets = mApkAssets[cookie - 1];
-        for (int index = loaders.size() - 1; index >= 0; index--) {
-            Pair<ResourceLoader, ResourcesProvider> pair = loaders.get(index);
-            if (pair.second.getApkAssets() == apkAssets) {
-                return pair.first.loadAsset(fileName, accessMode);
-            }
+        final ResourcesProvider provider = findResourcesProvider(cookie);
+        if (provider != null && provider.getAssetsProvider() != null) {
+            return provider.getAssetsProvider().loadAsset(
+                    fileName, accessMode);
         }
 
         return null;
@@ -1095,43 +1162,48 @@
 
     private AssetFileDescriptor searchLoadersFd(int cookie, @NonNull String fileName)
             throws IOException {
-        if (mResourceLoaderManager == null) {
+        if (mLoaders == null) {
             return null;
         }
 
-        List<Pair<ResourceLoader, ResourcesProvider>> loaders =
-                mResourceLoaderManager.getInternalList();
-
-        // A cookie of 0 means no specific ApkAssets, so search everything
         if (cookie == 0) {
-            for (int index = loaders.size() - 1; index >= 0; index--) {
-                Pair<ResourceLoader, ResourcesProvider> pair = loaders.get(index);
-                try {
-                    ParcelFileDescriptor fileDescriptor = pair.first.loadAssetFd(fileName);
-                    if (fileDescriptor != null) {
-                        return new AssetFileDescriptor(fileDescriptor, 0,
-                                AssetFileDescriptor.UNKNOWN_LENGTH);
+            // 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;
                     }
-                } catch (IOException ignored) {
-                    // When searching, ignore read failures
+
+                    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;
         }
 
-        ApkAssets apkAssets = mApkAssets[cookie - 1];
-        for (int index = loaders.size() - 1; index >= 0; index--) {
-            Pair<ResourceLoader, ResourcesProvider> pair = loaders.get(index);
-            if (pair.second.getApkAssets() == apkAssets) {
-                ParcelFileDescriptor fileDescriptor = pair.first.loadAssetFd(fileName);
-                if (fileDescriptor != null) {
-                    return new AssetFileDescriptor(fileDescriptor, 0,
-                            AssetFileDescriptor.UNKNOWN_LENGTH);
-                }
-                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;
     }
 
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 4725e0a..471e83c 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -30,7 +30,6 @@
 import android.annotation.DrawableRes;
 import android.annotation.FontRes;
 import android.annotation.FractionRes;
-import android.annotation.IntRange;
 import android.annotation.IntegerRes;
 import android.annotation.LayoutRes;
 import android.annotation.NonNull;
@@ -41,13 +40,10 @@
 import android.annotation.StyleRes;
 import android.annotation.StyleableRes;
 import android.annotation.XmlRes;
-import android.app.ResourcesManager;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ActivityInfo.Config;
-import android.content.res.loader.ResourceLoader;
-import android.content.res.loader.ResourceLoaderManager;
-import android.content.res.loader.ResourcesProvider;
+import android.content.res.loader.ResourcesLoader;
 import android.graphics.Movie;
 import android.graphics.Typeface;
 import android.graphics.drawable.Drawable;
@@ -55,18 +51,17 @@
 import android.graphics.drawable.DrawableInflater;
 import android.os.Build;
 import android.os.Bundle;
+import android.util.ArraySet;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
 import android.util.Log;
 import android.util.LongSparseArray;
-import android.util.Pair;
 import android.util.Pools.SynchronizedPool;
 import android.util.TypedValue;
 import android.view.DisplayAdjustments;
 import android.view.ViewDebug;
 import android.view.ViewHierarchyEncoder;
 
-import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.GrowingArrayUtils;
@@ -117,6 +112,7 @@
     static final String TAG = "Resources";
 
     private static final Object sSync = new Object();
+    private final Object mLock = new Object();
 
     // Used by BridgeResources in layoutlib
     @UnsupportedAppUsage
@@ -143,10 +139,7 @@
     @UnsupportedAppUsage
     final ClassLoader mClassLoader;
 
-    private final Object mResourceLoaderLock = new Object();
-
-    @GuardedBy("mResourceLoaderLock")
-    private ResourceLoaderManager mResourceLoaderManager;
+    private UpdateCallbacks mCallbacks = null;
 
     /**
      * WeakReferences to Themes that were constructed from this Resources object.
@@ -240,6 +233,18 @@
         }
     }
 
+    /** @hide */
+    public interface UpdateCallbacks extends ResourcesLoader.UpdateCallbacks {
+        /**
+         * Invoked when a {@link Resources} instance has a {@link ResourcesLoader} added, removed,
+         * or reordered.
+         *
+         * @param resources the instance being updated
+         * @param newLoaders the new set of loaders for the instance
+         */
+        void onLoadersChanged(Resources resources, List<ResourcesLoader> newLoaders);
+    }
+
     /**
      * Create a new Resources object on top of an existing set of assets in an
      * AssetManager.
@@ -303,12 +308,6 @@
         mBaseApkAssetsSize = ArrayUtils.size(impl.getAssets().getApkAssets());
         mResourcesImpl = impl;
 
-        synchronized (mResourceLoaderLock) {
-            if (mResourceLoaderManager != null) {
-                mResourceLoaderManager.onImplUpdate(mResourcesImpl);
-            }
-        }
-
         // Create new ThemeImpls that are identical to the ones we have.
         synchronized (mThemeRefs) {
             final int count = mThemeRefs.size();
@@ -322,6 +321,15 @@
         }
     }
 
+    /** @hide */
+    public void setCallbacks(UpdateCallbacks callbacks) {
+        if (mCallbacks != null) {
+            throw new IllegalStateException("callback already registered");
+        }
+
+        mCallbacks = callbacks;
+    }
+
     /**
      * @hide
      */
@@ -937,14 +945,6 @@
     @UnsupportedAppUsage
     Drawable loadDrawable(@NonNull TypedValue value, int id, int density, @Nullable Theme theme)
             throws NotFoundException {
-        ResourceLoader loader = findLoader(value.assetCookie);
-        if (loader != null) {
-            Drawable drawable = loader.loadDrawable(value, id, density, theme);
-            if (drawable != null) {
-                return drawable;
-            }
-        }
-
         return mResourcesImpl.loadDrawable(this, value, id, density, theme);
     }
 
@@ -2337,14 +2337,6 @@
     @UnsupportedAppUsage
     XmlResourceParser loadXmlResourceParser(String file, int id, int assetCookie,
                                             String type) throws NotFoundException {
-        ResourceLoader loader = findLoader(assetCookie);
-        if (loader != null) {
-            XmlResourceParser xml = loader.loadXmlResourceParser(file, id);
-            if (xml != null) {
-                return xml;
-            }
-        }
-
         return mResourcesImpl.loadXmlResourceParser(file, id, assetCookie, type);
     }
 
@@ -2371,136 +2363,106 @@
         return theme.obtainStyledAttributes(set, attrs, 0, 0);
     }
 
-    private ResourceLoader findLoader(int assetCookie) {
-        ApkAssets[] apkAssetsArray = mResourcesImpl.getAssets().getApkAssets();
-        int apkAssetsIndex = assetCookie - 1;
-        if (apkAssetsIndex < apkAssetsArray.length && apkAssetsIndex >= 0) {
-            ApkAssets apkAssets = apkAssetsArray[apkAssetsIndex];
-            if (apkAssets.isForLoader()) {
-                List<Pair<ResourceLoader, ResourcesProvider>> loaders;
-                // Since we don't lock the entire resolution path anyways,
-                // only lock here instead of entire method. The list is copied
-                // and effectively a snapshot is used.
-                synchronized (mResourceLoaderLock) {
-                    loaders = mResourceLoaderManager.getInternalList();
-                }
-
-                if (!ArrayUtils.isEmpty(loaders)) {
-                    int size = loaders.size();
-                    for (int index = 0; index < size; index++) {
-                        Pair<ResourceLoader, ResourcesProvider> pair = loaders.get(index);
-                        if (pair.second.getApkAssets() == apkAssets) {
-                            return pair.first;
-                        }
-                    }
-                }
-            }
+    private void checkCallbacksRegistered() {
+        if (mCallbacks == null) {
+            throw new IllegalArgumentException("Cannot modify resource loaders of Resources"
+                    + " instances created outside of ResourcesManager");
         }
-
-        return null;
     }
 
     /**
-     * @return copied list of loaders and providers previously added
+     * Retrieves the list of loaders.
+     *
+     * <p>Loaders are listed in increasing precedence order. A loader will override the resources
+     * and assets of loaders listed before itself.
      */
     @NonNull
-    public List<Pair<ResourceLoader, ResourcesProvider>> getLoaders() {
-        synchronized (mResourceLoaderLock) {
-            return mResourceLoaderManager == null
-                    ? Collections.emptyList()
-                    : mResourceLoaderManager.getLoaders();
-        }
+    public List<ResourcesLoader> getLoaders() {
+        return mResourcesImpl.getAssets().getLoaders();
     }
 
     /**
-     * Add a custom {@link ResourceLoader} which is added to the paths searched by
-     * {@link AssetManager} when resolving a resource.
+     * Appends a loader to the end of the loader list. If the loader is already present in the
+     * loader list, the list will not be modified.
      *
-     * Resources are resolved as if the loader was a resource overlay, meaning the latest
-     * in the list, of equal or better config, is returned.
-     *
-     * {@link ResourcesProvider}s passed in here are not managed and a reference should be held
-     * to remove, re-use, or close them when necessary.
-     *
-     * @param resourceLoader an interface used to resolve file paths for drawables/XML files;
-     *                       a reference should be kept to remove the loader if necessary
-     * @param resourcesProvider an .apk or .arsc file representation
-     * @param index where to add the loader in the list
-     * @throws IllegalArgumentException if the resourceLoader is already added
-     * @throws IndexOutOfBoundsException if the index is invalid
+     * @param loader the loader to add
      */
-    public void addLoader(@NonNull ResourceLoader resourceLoader,
-            @NonNull ResourcesProvider resourcesProvider, @IntRange(from = 0) int index) {
-        synchronized (mResourceLoaderLock) {
-            if (mResourceLoaderManager == null) {
-                ResourcesManager.getInstance().registerForLoaders(this);
-                mResourceLoaderManager = new ResourceLoaderManager(mResourcesImpl);
+    public void addLoader(@NonNull ResourcesLoader loader) {
+        synchronized (mLock) {
+            checkCallbacksRegistered();
+
+            final List<ResourcesLoader> loaders = new ArrayList<>(
+                    mResourcesImpl.getAssets().getLoaders());
+            if (loaders.contains(loader)) {
+                return;
             }
 
-            mResourceLoaderManager.addLoader(resourceLoader, resourcesProvider, index);
+            loaders.add(loader);
+            mCallbacks.onLoadersChanged(this, loaders);
+            loader.registerOnProvidersChangedCallback(this, mCallbacks);
         }
     }
 
     /**
-     * @see #addLoader(ResourceLoader, ResourcesProvider, int).
+     * Removes a loader from the loaders. If the loader is not present in the loader list, the list
+     * will not be modified.
      *
-     * Adds to the end of the list.
-     *
-     * @return index the loader was added at
+     * @param loader the loader to remove
      */
-    public int addLoader(@NonNull ResourceLoader resourceLoader,
-            @NonNull ResourcesProvider resourcesProvider) {
-        synchronized (mResourceLoaderLock) {
-            int index = getLoaders().size();
-            addLoader(resourceLoader, resourcesProvider, index);
-            return index;
-        }
-    }
+    public void removeLoader(@NonNull ResourcesLoader loader) {
+        synchronized (mLock) {
+            checkCallbacksRegistered();
 
-    /**
-     * Remove a loader previously added by
-     * {@link #addLoader(ResourceLoader, ResourcesProvider, int)}
-     *
-     * The caller maintains responsibility for holding a reference to the matching
-     * {@link ResourcesProvider} and closing it after this method has been called.
-     *
-     * @param resourceLoader the same reference passed into [addLoader
-     * @return the index the loader was at in the list, or -1 if the loader was not found
-     */
-    public int removeLoader(@NonNull ResourceLoader resourceLoader) {
-        synchronized (mResourceLoaderLock) {
-            if (mResourceLoaderManager == null) {
-                return -1;
+            final List<ResourcesLoader> loaders = new ArrayList<>(
+                    mResourcesImpl.getAssets().getLoaders());
+            if (!loaders.remove(loader)) {
+                return;
             }
 
-            return mResourceLoaderManager.removeLoader(resourceLoader);
+            mCallbacks.onLoadersChanged(this, loaders);
+            loader.unregisterOnProvidersChangedCallback(this);
         }
     }
 
     /**
-     * Swap the current set of loaders. Preferred to multiple remove/add calls as this doesn't
-     * update the resource data structures after each modification.
+     * Sets the list of loaders.
      *
-     * Set to null or an empty list to clear the set of loaders.
-     *
-     * The caller maintains responsibility for holding references to the added
-     * {@link ResourcesProvider}s and closing them after this method has been called.
-     *
-     * @param resourceLoadersAndProviders a list of pairs to add
+     * @param loaders the new loaders
      */
-    public void setLoaders(
-            @Nullable List<Pair<ResourceLoader, ResourcesProvider>> resourceLoadersAndProviders) {
-        synchronized (mResourceLoaderLock) {
-            if (mResourceLoaderManager == null) {
-                if (ArrayUtils.isEmpty(resourceLoadersAndProviders)) {
-                    return;
+    public void setLoaders(@NonNull List<ResourcesLoader> loaders) {
+        synchronized (mLock) {
+            checkCallbacksRegistered();
+
+            final List<ResourcesLoader> oldLoaders = mResourcesImpl.getAssets().getLoaders();
+            int index = 0;
+            boolean modified = loaders.size() != oldLoaders.size();
+            final ArraySet<ResourcesLoader> seenLoaders = new ArraySet<>();
+            for (final ResourcesLoader loader : loaders) {
+                if (!seenLoaders.add(loader)) {
+                    throw new IllegalArgumentException("Loader " + loader + " present twice");
                 }
 
-                ResourcesManager.getInstance().registerForLoaders(this);
-                mResourceLoaderManager = new ResourceLoaderManager(mResourcesImpl);
+                if (!modified && oldLoaders.get(index++) != loader) {
+                    modified = true;
+                }
             }
 
-            mResourceLoaderManager.setLoaders(resourceLoadersAndProviders);
+            if (!modified) {
+                return;
+            }
+
+            mCallbacks.onLoadersChanged(this, loaders);
+            for (int i = 0, n = oldLoaders.size(); i < n; i++) {
+                oldLoaders.get(i).unregisterOnProvidersChangedCallback(this);
+            }
+            for (ResourcesLoader newLoader : loaders) {
+                newLoader.registerOnProvidersChangedCallback(this, mCallbacks);
+            }
         }
     }
+
+    /** Removes all {@link ResourcesLoader ResourcesLoader(s)}. */
+    public void clearLoaders() {
+        setLoaders(Collections.emptyList());
+    }
 }
diff --git a/core/java/android/content/res/ResourcesKey.java b/core/java/android/content/res/ResourcesKey.java
index a29fea0..9e40f46 100644
--- a/core/java/android/content/res/ResourcesKey.java
+++ b/core/java/android/content/res/ResourcesKey.java
@@ -19,6 +19,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.content.res.loader.ResourcesLoader;
 import android.text.TextUtils;
 
 import java.util.Arrays;
@@ -48,6 +49,9 @@
     @NonNull
     public final CompatibilityInfo mCompatInfo;
 
+    @Nullable
+    public final ResourcesLoader[] mLoaders;
+
     private final int mHash;
 
     @UnsupportedAppUsage
@@ -57,11 +61,13 @@
                         @Nullable String[] libDirs,
                         int displayId,
                         @Nullable Configuration overrideConfig,
-                        @Nullable CompatibilityInfo compatInfo) {
+                        @Nullable CompatibilityInfo compatInfo,
+                        @Nullable ResourcesLoader[] loader) {
         mResDir = resDir;
         mSplitResDirs = splitResDirs;
         mOverlayDirs = overlayDirs;
         mLibDirs = libDirs;
+        mLoaders = (loader != null && loader.length == 0) ? null : loader;
         mDisplayId = displayId;
         mOverrideConfiguration = new Configuration(overrideConfig != null
                 ? overrideConfig : Configuration.EMPTY);
@@ -75,6 +81,7 @@
         hash = 31 * hash + mDisplayId;
         hash = 31 * hash + Objects.hashCode(mOverrideConfiguration);
         hash = 31 * hash + Objects.hashCode(mCompatInfo);
+        hash = 31 * hash + Arrays.hashCode(mLoaders);
         mHash = hash;
     }
 
@@ -140,6 +147,9 @@
         if (!Objects.equals(mCompatInfo, peer.mCompatInfo)) {
             return false;
         }
+        if (!Arrays.equals(mLoaders, peer.mLoaders)) {
+            return false;
+        }
         return true;
     }
 
@@ -167,7 +177,11 @@
         builder.append(" mOverrideConfig=").append(Configuration.resourceQualifierString(
                 mOverrideConfiguration));
         builder.append(" mCompatInfo=").append(mCompatInfo);
-        builder.append("}");
+        builder.append(" mLoaders=[");
+        if (mLoaders != null) {
+            builder.append(TextUtils.join(",", mLoaders));
+        }
+        builder.append("]}");
         return builder.toString();
     }
 }
diff --git a/core/java/android/content/res/loader/AssetsProvider.java b/core/java/android/content/res/loader/AssetsProvider.java
new file mode 100644
index 0000000..c315494
--- /dev/null
+++ b/core/java/android/content/res/loader/AssetsProvider.java
@@ -0,0 +1,68 @@
+/*
+ * 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.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.
+ */
+public interface AssetsProvider {
+
+    /**
+     * Callback that allows the value of a file-based resources or asset to be specified or
+     * overridden.
+     *
+     * <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
+     * {@link AssetManager#open} and {@link AssetManager#openXmlResourceParser}.
+     * <li>AssetManager is resolving the value of a file-based resource provided by the
+     * {@link ResourcesProvider} this instance is associated with.
+     * </ul>
+     *
+     * <p>If the value retrieved from this callback is null, AssetManager will attempt to find the
+     * file-based resource or asset within the APK provided by the ResourcesProvider this instance
+     * is associated with.
+     *
+     * @param path the asset path being loaded
+     * @param accessMode the {@link AssetManager} access mode
+     *
+     * @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 {
+        return null;
+    }
+}
diff --git a/core/java/android/content/res/loader/DirectoryResourceLoader.java b/core/java/android/content/res/loader/DirectoryAssetsProvider.java
similarity index 70%
rename from core/java/android/content/res/loader/DirectoryResourceLoader.java
rename to core/java/android/content/res/loader/DirectoryAssetsProvider.java
index 7d90e72..81c2a4c 100644
--- a/core/java/android/content/res/loader/DirectoryResourceLoader.java
+++ b/core/java/android/content/res/loader/DirectoryAssetsProvider.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * 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.
@@ -26,23 +26,27 @@
 import java.io.InputStream;
 
 /**
- * A {@link ResourceLoader} that searches a directory for assets.
- *
- * Assumes that resource paths are resolvable child paths of the directory passed in.
+ * 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 DirectoryResourceLoader implements ResourceLoader {
+public class DirectoryAssetsProvider implements AssetsProvider {
 
     @NonNull
     private final File mDirectory;
 
-    public DirectoryResourceLoader(@NonNull File directory) {
+    /**
+     * 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 {
-        File file = findFile(path);
+        final File file = findFile(path);
         if (file == null || !file.exists()) {
             return null;
         }
@@ -51,8 +55,8 @@
 
     @Nullable
     @Override
-    public ParcelFileDescriptor loadAssetFd(@NonNull String path) throws IOException {
-        File file = findFile(path);
+    public ParcelFileDescriptor loadAssetParcelFd(@NonNull String path) throws IOException {
+        final File file = findFile(path);
         if (file == null || !file.exists()) {
             return null;
         }
@@ -60,7 +64,9 @@
     }
 
     /**
-     * Find the file for the given path encoded into the resource table.
+     * Finds the file relative to the root directory.
+     *
+     * @param path the relative path of the file
      */
     @Nullable
     public File findFile(@NonNull String path) {
diff --git a/core/java/android/content/res/loader/ResourceLoader.java b/core/java/android/content/res/loader/ResourceLoader.java
deleted file mode 100644
index af32aa2..0000000
--- a/core/java/android/content/res/loader/ResourceLoader.java
+++ /dev/null
@@ -1,116 +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;
-
-import android.annotation.AnyRes;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.res.AssetManager;
-import android.content.res.Resources;
-import android.content.res.XmlResourceParser;
-import android.graphics.drawable.Drawable;
-import android.os.ParcelFileDescriptor;
-import android.util.TypedValue;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * Exposes methods for overriding file-based resource loading from a {@link Resources}.
- *
- * To be used with {@link Resources#addLoader(ResourceLoader, ResourcesProvider, int)} and related
- * methods to override resource loading.
- *
- * Note that this class doesn't actually contain any resource data. Non-file-based resources are
- * loaded directly from the {@link ResourcesProvider}'s .arsc representation.
- *
- * An instance's methods will only be called if its corresponding {@link ResourcesProvider}'s
- * resources table contains an entry for the resource ID being resolved,
- * with the exception of the non-cookie variants of {@link AssetManager}'s openAsset and
- * openNonAsset.
- *
- * Those methods search backwards through all {@link ResourceLoader}s and then any paths provided
- * by the application or system.
- *
- * Otherwise, an ARSC that defines R.drawable.some_id must be provided if a {@link ResourceLoader}
- * wants to point R.drawable.some_id to a different file on disk.
- */
-public interface ResourceLoader {
-
-    /**
-     * Given the value resolved from the string pool of the {@link ResourcesProvider} passed to
-     * {@link Resources#addLoader(ResourceLoader, ResourcesProvider, int)}, return a
-     * {@link Drawable} which should be returned by the parent
-     * {@link Resources#getDrawable(int, Resources.Theme)}.
-     *
-     * @param value   the resolved {@link TypedValue} before it has been converted to a Drawable
-     *                object
-     * @param id      the R.drawable ID this resolution is for
-     * @param density the requested density
-     * @param theme   the {@link Resources.Theme} resolved under
-     * @return null if resolution should try to find an entry inside the {@link ResourcesProvider},
-     * including calling through to {@link #loadAsset(String, int)} or {@link #loadAssetFd(String)}
-     */
-    @Nullable
-    default Drawable loadDrawable(@NonNull TypedValue value, int id, int density,
-            @Nullable Resources.Theme theme) {
-        return null;
-    }
-
-    /**
-     * Given the value resolved from the string pool of the {@link ResourcesProvider} passed to
-     * {@link Resources#addLoader(ResourceLoader, ResourcesProvider, int)}, return an
-     * {@link XmlResourceParser} which should be returned by the parent
-     * {@link Resources#getDrawable(int, Resources.Theme)}.
-     *
-     * @param path the string that was found in the string pool
-     * @param id   the XML ID this resolution is for, can be R.anim, R.layout, or R.xml
-     * @return null if resolution should try to find an entry inside the {@link ResourcesProvider},
-     * including calling through to {@link #loadAssetFd(String)} (String, int)}
-     */
-    @Nullable
-    default XmlResourceParser loadXmlResourceParser(@NonNull String path, @AnyRes int id) {
-        return null;
-    }
-
-    /**
-     * Given the value resolved from the string pool of the {@link ResourcesProvider} passed to
-     * {@link Resources#addLoader(ResourceLoader, ResourcesProvider, int)}, return an
-     * {@link InputStream} which should be returned when an asset is loaded by {@link AssetManager}.
-     * Assets will be loaded from a provider's root, with anything in its assets subpath prefixed
-     * with "assets/".
-     *
-     * @param path       the asset path to load
-     * @param accessMode {@link AssetManager} access mode; does not have to be respected
-     * @return null if resolution should try to find an entry inside the {@link ResourcesProvider}
-     */
-    @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 to load
-     * @return null if resolution should try to find an entry inside the {@link ResourcesProvider}
-     */
-    @Nullable
-    default ParcelFileDescriptor loadAssetFd(@NonNull String path) throws IOException {
-        return null;
-    }
-}
diff --git a/core/java/android/content/res/loader/ResourceLoaderManager.java b/core/java/android/content/res/loader/ResourceLoaderManager.java
deleted file mode 100644
index 592ec09..0000000
--- a/core/java/android/content/res/loader/ResourceLoaderManager.java
+++ /dev/null
@@ -1,190 +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;
-
-import android.annotation.Nullable;
-import android.content.res.ApkAssets;
-import android.content.res.AssetManager;
-import android.content.res.Resources;
-import android.content.res.ResourcesImpl;
-import android.util.Pair;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.ArrayUtils;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * @hide
- */
-public class ResourceLoaderManager {
-
-    private final Object mLock = new Object();
-
-    @GuardedBy("mLock")
-    private final List<Pair<ResourceLoader, ResourcesProvider>> mResourceLoaders =
-            new ArrayList<>();
-
-    @GuardedBy("mLock")
-    private ResourcesImpl mResourcesImpl;
-
-    public ResourceLoaderManager(ResourcesImpl resourcesImpl) {
-        this.mResourcesImpl = resourcesImpl;
-        this.mResourcesImpl.getAssets().setResourceLoaderManager(this);
-    }
-
-    /**
-     * Copies the list to ensure that ongoing mutations don't affect the list if it's being used
-     * as a search set.
-     *
-     * @see Resources#getLoaders()
-     */
-    public List<Pair<ResourceLoader, ResourcesProvider>> getLoaders() {
-        synchronized (mLock) {
-            return new ArrayList<>(mResourceLoaders);
-        }
-    }
-
-    /**
-     * Returns a list for searching for a loader. Locks and copies the list to ensure that
-     * ongoing mutations don't affect the search set.
-     */
-    public List<Pair<ResourceLoader, ResourcesProvider>> getInternalList() {
-        synchronized (mLock) {
-            return new ArrayList<>(mResourceLoaders);
-        }
-    }
-
-    /**
-     * TODO(b/136251855): Consider optional boolean ignoreConfigurations to allow ResourceLoader
-     * to override every configuration in the target package
-     *
-     * @see Resources#addLoader(ResourceLoader, ResourcesProvider)
-     */
-    public void addLoader(ResourceLoader resourceLoader, ResourcesProvider resourcesProvider,
-            int index) {
-        synchronized (mLock) {
-            for (int listIndex = 0; listIndex < mResourceLoaders.size(); listIndex++) {
-                if (Objects.equals(mResourceLoaders.get(listIndex).first, resourceLoader)) {
-                    throw new IllegalArgumentException("Cannot add the same ResourceLoader twice");
-                }
-            }
-
-            mResourceLoaders.add(index, Pair.create(resourceLoader, resourcesProvider));
-            updateLoaders();
-        }
-    }
-
-    /**
-     * @see Resources#removeLoader(ResourceLoader)
-     */
-    public int removeLoader(ResourceLoader resourceLoader) {
-        synchronized (mLock) {
-            int indexOfLoader = -1;
-
-            for (int index = 0; index < mResourceLoaders.size(); index++) {
-                if (mResourceLoaders.get(index).first == resourceLoader) {
-                    indexOfLoader = index;
-                    break;
-                }
-            }
-
-            if (indexOfLoader < 0) {
-                return indexOfLoader;
-            }
-
-            mResourceLoaders.remove(indexOfLoader);
-            updateLoaders();
-            return indexOfLoader;
-        }
-    }
-
-    /**
-     * @see Resources#setLoaders(List)
-     */
-    public void setLoaders(
-            @Nullable List<Pair<ResourceLoader, ResourcesProvider>> newLoadersAndProviders) {
-        synchronized (mLock) {
-            if (ArrayUtils.isEmpty(newLoadersAndProviders)) {
-                mResourceLoaders.clear();
-                updateLoaders();
-                return;
-            }
-
-            int size = newLoadersAndProviders.size();
-            for (int newIndex = 0; newIndex < size; newIndex++) {
-                ResourceLoader resourceLoader = newLoadersAndProviders.get(newIndex).first;
-                for (int oldIndex = 0; oldIndex < mResourceLoaders.size(); oldIndex++) {
-                    if (Objects.equals(mResourceLoaders.get(oldIndex).first, resourceLoader)) {
-                        throw new IllegalArgumentException(
-                                "Cannot add the same ResourceLoader twice");
-                    }
-                }
-            }
-
-            mResourceLoaders.clear();
-            mResourceLoaders.addAll(newLoadersAndProviders);
-
-            updateLoaders();
-        }
-    }
-
-    /**
-     * Swap the tracked {@link ResourcesImpl} and reattach any loaders to it.
-     */
-    public void onImplUpdate(ResourcesImpl resourcesImpl) {
-        synchronized (mLock) {
-            this.mResourcesImpl = resourcesImpl;
-            this.mResourcesImpl.getAssets().setResourceLoaderManager(this);
-            updateLoaders();
-        }
-    }
-
-    private void updateLoaders() {
-        synchronized (mLock) {
-            AssetManager assetManager = mResourcesImpl.getAssets();
-            ApkAssets[] existingApkAssets = assetManager.getApkAssets();
-            int baseApkAssetsSize = 0;
-            for (int index = existingApkAssets.length - 1; index >= 0; index--) {
-                // Loaders are always last, so the first non-loader is the end of the base assets
-                if (!existingApkAssets[index].isForLoader()) {
-                    baseApkAssetsSize = index + 1;
-                    break;
-                }
-            }
-
-            List<ApkAssets> newAssets = new ArrayList<>();
-            for (int index = 0; index < baseApkAssetsSize; index++) {
-                newAssets.add(existingApkAssets[index]);
-            }
-
-            int size = mResourceLoaders.size();
-            for (int index = 0; index < size; index++) {
-                ApkAssets apkAssets = mResourceLoaders.get(index).second.getApkAssets();
-                newAssets.add(apkAssets);
-            }
-
-            assetManager.setApkAssets(newAssets.toArray(new ApkAssets[0]), true);
-
-            // Short of resolving every resource, it's too difficult to determine what has changed
-            // when a resource loader is changed, so just clear everything.
-            mResourcesImpl.clearAllCaches();
-        }
-    }
-}
diff --git a/core/java/android/content/res/loader/ResourcesLoader.java b/core/java/android/content/res/loader/ResourcesLoader.java
new file mode 100644
index 0000000..69dacee
--- /dev/null
+++ b/core/java/android/content/res/loader/ResourcesLoader.java
@@ -0,0 +1,253 @@
+/*
+ * 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;
+
+import android.annotation.NonNull;
+import android.content.res.ApkAssets;
+import android.content.res.Resources;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
+
+import java.lang.ref.WeakReference;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A container for supplying {@link ResourcesProvider ResourcesProvider(s)} to {@link Resources}
+ * objects.
+ *
+ * <p>{@link ResourcesLoader ResourcesLoader(s)} are added to Resources objects to supply
+ * additional resources and assets or modify the values of existing resources and assets. Multiple
+ * Resources objects can share the same ResourcesLoaders and ResourcesProviders. Changes to the list
+ * of {@link ResourcesProvider ResourcesProvider(s)} a loader contains propagates to all Resources
+ * objects that use the loader.
+ *
+ * <p>Loaders retrieved with {@link Resources#getLoaders()} are listed in increasing precedence
+ * order. A loader will override the resources and assets of loaders listed before itself.
+ *
+ * <p>Providers retrieved with {@link #getProviders()} are listed in increasing precedence order. A
+ * provider will override the resources and assets of providers listed before itself.
+ */
+public class ResourcesLoader {
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private ApkAssets[] mApkAssets;
+
+    @GuardedBy("mLock")
+    private ResourcesProvider[] mPreviousProviders;
+
+    @GuardedBy("mLock")
+    private ResourcesProvider[] mProviders;
+
+    @GuardedBy("mLock")
+    private ArrayMap<WeakReference<Object>, UpdateCallbacks> mChangeCallbacks = new ArrayMap<>();
+
+    /** @hide */
+    public interface UpdateCallbacks {
+
+        /**
+         * Invoked when a {@link ResourcesLoader} has a {@link ResourcesProvider} added, removed,
+         * or reordered.
+         *
+         * @param loader the loader that was updated
+         */
+        void onLoaderUpdated(@NonNull ResourcesLoader loader);
+    }
+
+    /**
+     * Retrieves the list of providers loaded into this instance. Providers are listed in increasing
+     * precedence order. A provider will override the values of providers listed before itself.
+     */
+    @NonNull
+    public List<ResourcesProvider> getProviders() {
+        synchronized (mLock) {
+            return mProviders == null ? Collections.emptyList() : Arrays.asList(mProviders);
+        }
+    }
+
+    /**
+     * Appends a provider to the end of the provider list. If the provider is already present in the
+     * loader list, the list will not be modified.
+     *
+     * @param resourcesProvider the provider to add
+     */
+    public void addProvider(@NonNull ResourcesProvider resourcesProvider) {
+        synchronized (mLock) {
+            mProviders = ArrayUtils.appendElement(ResourcesProvider.class, mProviders,
+                    resourcesProvider);
+            notifyProvidersChangedLocked();
+        }
+    }
+
+    /**
+     * Removes a provider from the provider list. If the provider is not present in the provider
+     * list, the list will not be modified.
+     *
+     * @param resourcesProvider the provider to remove
+     */
+    public void removeProvider(@NonNull ResourcesProvider resourcesProvider) {
+        synchronized (mLock) {
+            mProviders = ArrayUtils.removeElement(ResourcesProvider.class, mProviders,
+                    resourcesProvider);
+            notifyProvidersChangedLocked();
+        }
+    }
+
+    /**
+     * Sets the list of providers.
+     *
+     * @param resourcesProviders the new providers
+     */
+    public void setProviders(@NonNull List<ResourcesProvider> resourcesProviders) {
+        synchronized (mLock) {
+            mProviders = resourcesProviders.toArray(new ResourcesProvider[0]);
+            notifyProvidersChangedLocked();
+        }
+    }
+
+    /** Removes all {@link ResourcesProvider ResourcesProvider(s)}. */
+    public void clearProviders() {
+        synchronized (mLock) {
+            mProviders = null;
+            notifyProvidersChangedLocked();
+        }
+    }
+
+    /**
+     * Retrieves the list of {@link ApkAssets} used by the providers.
+     *
+     * @hide
+     */
+    @NonNull
+    public List<ApkAssets> getApkAssets() {
+        synchronized (mLock) {
+            if (mApkAssets == null) {
+                return Collections.emptyList();
+            }
+            return Arrays.asList(mApkAssets);
+        }
+    }
+
+    /**
+     * Registers a callback to be invoked when {@link ResourcesProvider ResourcesProvider(s)}
+     * change.
+     * @param instance the instance tied to the callback
+     * @param callbacks the callback to invoke
+     *
+     * @hide
+     */
+    public void registerOnProvidersChangedCallback(@NonNull Object instance,
+            @NonNull UpdateCallbacks callbacks) {
+        synchronized (mLock) {
+            mChangeCallbacks.put(new WeakReference<>(instance), callbacks);
+        }
+    }
+
+    /**
+     * Removes a previously registered callback.
+     * @param instance the instance tied to the callback
+     *
+     * @hide
+     */
+    public void unregisterOnProvidersChangedCallback(@NonNull Object instance) {
+        synchronized (mLock) {
+            for (int i = 0, n = mChangeCallbacks.size(); i < n; i++) {
+                final WeakReference<Object> key = mChangeCallbacks.keyAt(i);
+                if (instance == key.get()) {
+                    mChangeCallbacks.removeAt(i);
+                    return;
+                }
+            }
+        }
+    }
+
+    /** Returns whether the arrays contain the same provider instances in the same order. */
+    private static boolean arrayEquals(ResourcesProvider[] a1, ResourcesProvider[] a2) {
+        if (a1 == a2) {
+            return true;
+        }
+
+        if (a1 == null || a2 == null) {
+            return false;
+        }
+
+        if (a1.length != a2.length) {
+            return false;
+        }
+
+        // Check that the arrays contain the exact same instances in the same order. Providers do
+        // not have any form of equivalence checking of whether the contents of two providers have
+        // equivalent apk assets.
+        for (int i = 0, n = a1.length; i < n; i++) {
+            if (a1[i] != a2[i]) {
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+
+    /**
+     * Invokes registered callbacks when the list of {@link ResourcesProvider} instances this loader
+     * uses changes.
+     */
+    private void notifyProvidersChangedLocked() {
+        final ArraySet<UpdateCallbacks> uniqueCallbacks = new ArraySet<>();
+        if (arrayEquals(mPreviousProviders, mProviders)) {
+            return;
+        }
+
+        if (mProviders == null || mProviders.length == 0) {
+            mApkAssets = null;
+        } else {
+            mApkAssets = new ApkAssets[mProviders.length];
+            for (int i = 0, n = mProviders.length; i < n; i++) {
+                mProviders[i].incrementRefCount();
+                mApkAssets[i] = mProviders[i].getApkAssets();
+            }
+        }
+
+        // Decrement the ref count after incrementing the new provider ref count so providers
+        // present before and after this method do not drop to zero references.
+        if (mPreviousProviders != null) {
+            for (ResourcesProvider provider : mPreviousProviders) {
+                provider.decrementRefCount();
+            }
+        }
+
+        mPreviousProviders = mProviders;
+
+        for (int i = mChangeCallbacks.size() - 1; i >= 0; i--) {
+            final WeakReference<Object> key = mChangeCallbacks.keyAt(i);
+            if (key.get() == null) {
+                mChangeCallbacks.removeAt(i);
+            } else {
+                uniqueCallbacks.add(mChangeCallbacks.valueAt(i));
+            }
+        }
+
+        for (int i = 0, n = uniqueCallbacks.size(); i < n; i++) {
+            uniqueCallbacks.valueAt(i).onLoaderUpdated(this);
+        }
+    }
+}
diff --git a/core/java/android/content/res/loader/ResourcesProvider.java b/core/java/android/content/res/loader/ResourcesProvider.java
index 050aeb7..419ec78 100644
--- a/core/java/android/content/res/loader/ResourcesProvider.java
+++ b/core/java/android/content/res/loader/ResourcesProvider.java
@@ -17,84 +17,144 @@
 package android.content.res.loader;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.res.ApkAssets;
-import android.content.res.Resources;
 import android.os.ParcelFileDescriptor;
 import android.os.SharedMemory;
+import android.util.Log;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.ArrayUtils;
 
 import java.io.Closeable;
 import java.io.IOException;
 
 /**
- * Provides methods to load resources from an .apk or .arsc file to pass to
- * {@link Resources#addLoader(ResourceLoader, ResourcesProvider, int)}.
- *
- * It is the responsibility of the app to close any instances.
+ * Provides methods to load resources data from APKs ({@code .apk}) and resources tables
+ * {@code .arsc} for use with {@link ResourcesLoader ResourcesLoader(s)}.
  */
-public final class ResourcesProvider implements AutoCloseable, Closeable {
+public class ResourcesProvider implements AutoCloseable, Closeable {
+    private static final String TAG = "ResourcesProvider";
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private boolean mOpen = true;
+
+    @GuardedBy("mLock")
+    private int mOpenCount = 0;
+
+    @GuardedBy("mLock")
+    private final ApkAssets mApkAssets;
+
+    private final AssetsProvider mAssetsProvider;
 
     /**
-     * Contains no data, assuming that any resource loading behavior will be handled in the
-     * corresponding {@link ResourceLoader}.
+     * Creates an empty ResourcesProvider with no resource data. This is useful for loading assets
+     * that are not associated with resource identifiers.
+     *
+     * @param assetsProvider the assets provider that overrides the loading of file-based resources
      */
     @NonNull
-    public static ResourcesProvider empty() {
-        return new ResourcesProvider(ApkAssets.loadEmptyForLoader());
+    public static ResourcesProvider empty(@NonNull AssetsProvider assetsProvider) {
+        return new ResourcesProvider(ApkAssets.loadEmptyForLoader(), assetsProvider);
     }
 
     /**
-     * Read from an .apk file descriptor.
+     * Creates a ResourcesProvider from an APK ({@code .apk}) file descriptor.
      *
-     * The file descriptor is duplicated and the one passed in may be closed by the application
-     * at any time.
+     * The file descriptor is duplicated and the original may be closed by the application at any
+     * time without affecting the ResourcesProvider.
+     *
+     * @param fileDescriptor the file descriptor of the APK to load
      */
     @NonNull
     public static ResourcesProvider loadFromApk(@NonNull ParcelFileDescriptor fileDescriptor)
             throws IOException {
-        return new ResourcesProvider(
-                ApkAssets.loadApkForLoader(fileDescriptor.getFileDescriptor()));
+        return loadFromApk(fileDescriptor, null);
     }
 
     /**
-     * Read from an .apk file representation in memory.
+     * Creates a ResourcesProvider from an APK ({@code .apk}) file descriptor.
+     *
+     * The file descriptor is duplicated and the original may be closed by the application at any
+     * time without affecting the ResourcesProvider.
+     *
+     * @param fileDescriptor the file descriptor of the APK to load
+     * @param assetsProvider the assets provider that overrides the loading of file-based resources
+     */
+    @NonNull
+    public static ResourcesProvider loadFromApk(@NonNull ParcelFileDescriptor fileDescriptor,
+            @Nullable AssetsProvider assetsProvider)
+            throws IOException {
+        return new ResourcesProvider(
+                ApkAssets.loadApkForLoader(fileDescriptor.getFileDescriptor()), assetsProvider);
+    }
+
+    /**
+     * Creates a ResourcesProvider from an {@code .apk} file representation in memory.
+     *
+     * @param sharedMemory the shared memory containing the data of the APK to load
      */
     @NonNull
     public static ResourcesProvider loadFromApk(@NonNull SharedMemory sharedMemory)
             throws IOException {
-        return new ResourcesProvider(
-                ApkAssets.loadApkForLoader(sharedMemory.getFileDescriptor()));
+        return loadFromApk(sharedMemory, null);
     }
 
     /**
-     * Read from an .arsc file descriptor.
+     * Creates a ResourcesProvider from an {@code .apk} file representation in memory.
      *
-     * The file descriptor is duplicated and the one passed in may be closed by the application
-     * at any time.
+     * @param sharedMemory the shared memory containing the data of the APK to load
+     * @param assetsProvider the assets provider that implements the loading of file-based resources
      */
     @NonNull
-    public static ResourcesProvider loadFromArsc(@NonNull ParcelFileDescriptor fileDescriptor)
+    public static ResourcesProvider loadFromApk(@NonNull SharedMemory sharedMemory,
+            @Nullable AssetsProvider assetsProvider)
             throws IOException {
         return new ResourcesProvider(
-                ApkAssets.loadArscForLoader(fileDescriptor.getFileDescriptor()));
+                ApkAssets.loadApkForLoader(sharedMemory.getFileDescriptor()), assetsProvider);
     }
 
     /**
-     * Read from an .arsc file representation in memory.
+     * Creates a ResourcesProvider from a resources table ({@code .arsc}) file descriptor.
+     *
+     * The file descriptor is duplicated and the original may be closed by the application at any
+     * time without affecting the ResourcesProvider.
+     *
+     * @param fileDescriptor the file descriptor of the resources table to load
+     * @param assetsProvider the assets provider that implements the loading of file-based resources
      */
     @NonNull
-    public static ResourcesProvider loadFromArsc(@NonNull SharedMemory sharedMemory)
+    public static ResourcesProvider loadFromTable(@NonNull ParcelFileDescriptor fileDescriptor,
+            @Nullable AssetsProvider assetsProvider)
             throws IOException {
         return new ResourcesProvider(
-                ApkAssets.loadArscForLoader(sharedMemory.getFileDescriptor()));
+                ApkAssets.loadArscForLoader(fileDescriptor.getFileDescriptor()), assetsProvider);
+    }
+
+    /**
+     * Creates a ResourcesProvider from a resources table ({@code .arsc}) file representation in
+     * memory.
+     *
+     * @param sharedMemory the shared memory containing the data of the resources table to load
+     * @param assetsProvider the assets provider that overrides the loading of file-based resources
+     */
+    @NonNull
+    public static ResourcesProvider loadFromTable(@NonNull SharedMemory sharedMemory,
+            @Nullable AssetsProvider assetsProvider)
+            throws IOException {
+        return new ResourcesProvider(
+                ApkAssets.loadArscForLoader(sharedMemory.getFileDescriptor()), assetsProvider);
     }
 
     /**
      * Read from a split installed alongside the application, which may not have been
      * loaded initially because the application requested isolated split loading.
+     *
+     * @param context a context of the package that contains the split
+     * @param splitName the name of the split to load
      */
     @NonNull
     public static ResourcesProvider loadFromSplit(@NonNull Context context,
@@ -106,15 +166,18 @@
         }
 
         String splitPath = appInfo.getSplitCodePaths()[splitIndex];
-        return new ResourcesProvider(ApkAssets.loadApkForLoader(splitPath));
+        return new ResourcesProvider(ApkAssets.loadApkForLoader(splitPath), null);
     }
 
-
-    @NonNull
-    private final ApkAssets mApkAssets;
-
-    private ResourcesProvider(@NonNull ApkAssets apkAssets) {
+    private ResourcesProvider(@NonNull ApkAssets apkAssets,
+            @Nullable AssetsProvider assetsProvider) {
         this.mApkAssets = apkAssets;
+        this.mAssetsProvider = assetsProvider;
+    }
+
+    @Nullable
+    public AssetsProvider getAssetsProvider() {
+        return mAssetsProvider;
     }
 
     /** @hide */
@@ -123,8 +186,41 @@
         return mApkAssets;
     }
 
+    final void incrementRefCount() {
+        synchronized (mLock) {
+            if (!mOpen) {
+                throw new IllegalStateException("Operation failed: resources provider is closed");
+            }
+            mOpenCount++;
+        }
+    }
+
+    final void decrementRefCount() {
+        synchronized (mLock) {
+            mOpenCount--;
+        }
+    }
+
+    /**
+     * Frees internal data structures. Closed providers can no longer be added to
+     * {@link ResourcesLoader ResourcesLoader(s)}.
+     *
+     * @throws IllegalStateException if provider is currently used by a ResourcesLoader
+     */
     @Override
     public void close() {
+        synchronized (mLock) {
+            if (!mOpen) {
+                return;
+            }
+
+            if (mOpenCount != 0) {
+                throw new IllegalStateException("Failed to close provider used by " + mOpenCount
+                        + " ResourcesLoader instances");
+            }
+            mOpen = false;
+        }
+
         try {
             mApkAssets.close();
         } catch (Throwable ignored) {
@@ -133,7 +229,16 @@
 
     @Override
     protected void finalize() throws Throwable {
-        close();
-        super.finalize();
+        synchronized (mLock) {
+            if (mOpenCount != 0) {
+                Log.w(TAG, "ResourcesProvider " + this + " finalized with non-zero refs: "
+                        + mOpenCount);
+            }
+
+            if (mOpen) {
+                mOpen = false;
+                mApkAssets.close();
+            }
+        }
     }
 }
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index f8df883..e975bb6 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -2747,7 +2747,8 @@
                 mDisplayContent.getDisplayId(),
                 null /* overrideConfig */,
                 uiContext.getResources().getCompatibilityInfo(),
-                null /* classLoader */);
+                null /* classLoader */,
+                null /* loaders */);
     }
 
     @VisibleForTesting