diff options
author | Ryan Mitchell <rtmitchell@google.com> | 2020-01-08 16:29:11 -0800 |
---|---|---|
committer | Ryan Mitchell <rtmitchell@google.com> | 2020-01-28 15:35:14 -0800 |
commit | 4579c0aea24d74e0bb262b9019ab14a1fc96c1bb (patch) | |
tree | 6ec9871314af18661da59e8a23bdadfa7a74a546 | |
parent | 12ce8338cc178e7683386c026709a74f2733abb7 (diff) |
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
16 files changed, 849 insertions, 868 deletions
diff --git a/api/current.txt b/api/current.txt index 4c3110c9189c..2303f8d34f0a 100644 --- a/api/current.txt +++ b/api/current.txt @@ -12635,8 +12635,8 @@ package android.content.res { 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 @@ package android.content.res { 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 @@ package android.content.res { 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 { 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 2ca5b1d5c76f..247ddb647665 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -2206,7 +2206,7 @@ public final class ActivityThread extends ClientTransactionHandler { 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 b7555ee1c04e..136c84eaf543 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -46,6 +46,7 @@ import android.content.res.CompatResources; 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.lang.annotation.RetentionPolicy; 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 @@ class ContextImpl extends Context { } 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 @@ class ContextImpl extends Context { displayId, overrideConfig, compatInfo, - classLoader); + classLoader, + resourcesLoader); } @Override @@ -2249,7 +2253,7 @@ class ContextImpl extends Context { 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 @@ class ContextImpl extends Context { 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 @@ class ContextImpl extends Context { displayId, null, mPackageInfo.getCompatibilityInfo(), - classLoader)); + classLoader, + mResources.getLoaders())); return context; } @@ -2342,8 +2347,10 @@ class ContextImpl extends Context { 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 @@ class ContextImpl extends Context { 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 @@ class ContextImpl extends Context { 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 @@ class ContextImpl extends Context { 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 f0d0e98b841f..44c248637f49 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -365,7 +365,8 @@ public final class LoadedApk { 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 @@ public final class LoadedApk { 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 7ab85a4a7468..d09f0bcf4275 100644 --- a/core/java/android/app/ResourcesManager.java +++ b/core/java/android/app/ResourcesManager.java @@ -32,7 +32,7 @@ import android.content.res.Configuration; 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.util.Slog; 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.lang.ref.WeakReference; 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 @@ public class ResourcesManager { 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 @@ public class ResourcesManager { 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 @@ public class ResourcesManager { } } - 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 @@ public class ResourcesManager { } } + if (key.mLoaders != null) { + for (final ResourcesLoader loader : key.mLoaders) { + builder.addLoader(loader); + } + } + return builder.build(); } @@ -570,16 +514,6 @@ public class ResourcesManager { 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 @@ public class ResourcesManager { */ 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 @@ public class ResourcesManager { @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 @@ public class ResourcesManager { 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 @@ public class ResourcesManager { 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 @@ public class ResourcesManager { /** * 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 @@ public class ResourcesManager { 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 @@ public class ResourcesManager { 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 @@ public class ResourcesManager { } 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 @@ public class ResourcesManager { 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 @@ public class ResourcesManager { 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 @@ public class ResourcesManager { /** * 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 @@ public class ResourcesManager { 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 @@ public class ResourcesManager { // 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 @@ public class ResourcesManager { } } - 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 @@ public class ResourcesManager { 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 @@ public class ResourcesManager { } } - /** - * 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 @@ public class ResourcesManager { 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 @@ public class ResourcesManager { } } } + } - // 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"); + } - ResourcesKey newKey = updatedResourceKeys.get(resources.getImpl()); - if (newKey == null) { - continue; + 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); } + } + + /** + * 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); + } - resourcesWithLoaders.updateKey(newKey); - resourcesWithLoaders.resources() - .setImpl(createResourcesImpl(newKey)); + redirectResourcesToNewImplLocked(updatedResourceImplKeys); + } } } } diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index fd4c26569873..906ca10b531d 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -8519,7 +8519,8 @@ public class PackageParser { 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 96fbe9109c18..1b0175812949 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -27,13 +27,12 @@ import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.pm.ActivityInfo; import android.content.res.Configuration.NativeConfig; -import android.content.res.loader.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.io.InputStream; 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 @@ public final class AssetManager implements AutoCloseable { @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 final class AssetManager implements AutoCloseable { */ 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 @@ public final class AssetManager implements AutoCloseable { } } + /** @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 @@ public final class AssetManager implements AutoCloseable { } } + private ResourcesProvider findResourcesProvider(int assetCookie) { + if (mLoaders == null) { + return null; + } + + int apkAssetsIndex = assetCookie - 1; + if (apkAssetsIndex >= mApkAssets.length || apkAssetsIndex < 0) { + return null; + } + + final ApkAssets apkAssets = mApkAssets[apkAssetsIndex]; + if (!apkAssets.isForLoader()) { + return null; + } + + for (int i = mLoaders.length - 1; i >= 0; i--) { + final ResourcesLoader loader = mLoaders[i]; + for (int j = 0, n = loader.getProviders().size(); j < n; j++) { + final ResourcesProvider provider = loader.getProviders().get(j); + if (apkAssets == provider.getApkAssets()) { + return provider; + } + } + } + + return null; + } + private InputStream searchLoaders(int cookie, @NonNull String fileName, int accessMode) throws IOException { - if (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 { - 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; + } + + try { + final InputStream inputStream = assetsProvider.loadAsset( + fileName, accessMode); + if (inputStream != null) { + return inputStream; + } + } catch (IOException ignored) { + // When searching, ignore read failures } - } 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 @@ public final class AssetManager implements AutoCloseable { 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; + } + + 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 } - } 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 4725e0af4eec..471e83c4c3eb 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -30,7 +30,6 @@ import android.annotation.DimenRes; 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.StringRes; 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.Drawable.ConstantState; 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 @@ public class Resources { 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 @@ public class Resources { @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 @@ public class Resources { } } + /** @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 @@ public class Resources { 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 @@ public class Resources { } } + /** @hide */ + public void setCallbacks(UpdateCallbacks callbacks) { + if (mCallbacks != null) { + throw new IllegalStateException("callback already registered"); + } + + mCallbacks = callbacks; + } + /** * @hide */ @@ -937,14 +945,6 @@ public class Resources { @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 @@ public class Resources { @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 @@ public class Resources { 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. - * - * 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. + * 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. * - * @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). - * - * Adds to the end of the list. + * Removes a loader from the loaders. If the loader is not present in the loader list, the list + * will not be modified. * - * @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. - * - * Set to null or an empty list to clear the set of loaders. + * Sets the list 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"); + } + + if (!modified && oldLoaders.get(index++) != loader) { + modified = true; } + } - ResourcesManager.getInstance().registerForLoaders(this); - mResourceLoaderManager = new ResourceLoaderManager(mResourcesImpl); + if (!modified) { + return; } - mResourceLoaderManager.setLoaders(resourceLoadersAndProviders); + 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 a29fea09c5ce..9e40f46c3c21 100644 --- a/core/java/android/content/res/ResourcesKey.java +++ b/core/java/android/content/res/ResourcesKey.java @@ -19,6 +19,7 @@ package android.content.res; 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 @@ public final class ResourcesKey { @NonNull public final CompatibilityInfo mCompatInfo; + @Nullable + public final ResourcesLoader[] mLoaders; + private final int mHash; @UnsupportedAppUsage @@ -57,11 +61,13 @@ public final class ResourcesKey { @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 @@ public final class ResourcesKey { 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 @@ public final class ResourcesKey { if (!Objects.equals(mCompatInfo, peer.mCompatInfo)) { return false; } + if (!Arrays.equals(mLoaders, peer.mLoaders)) { + return false; + } return true; } @@ -167,7 +177,11 @@ public final class ResourcesKey { 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 000000000000..c315494cf728 --- /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 index 7d90e72ab07e..81c2a4c1b4d6 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.IOException; 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 @@ public class DirectoryResourceLoader implements ResourceLoader { @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 @@ public class DirectoryResourceLoader implements ResourceLoader { } /** - * 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 af32aa2c6875..000000000000 --- 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 592ec09aa730..000000000000 --- 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 000000000000..69daceeaffc2 --- /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 050aeb7c5fda..419ec7882f3d 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 original may be closed by the application at any + * time without affecting the ResourcesProvider. * - * The file descriptor is duplicated and the one passed in may be closed by the application - * at any time. + * @param fileDescriptor the file descriptor of the APK to load */ @NonNull public static ResourcesProvider loadFromApk(@NonNull ParcelFileDescriptor fileDescriptor) throws IOException { + return loadFromApk(fileDescriptor, null); + } + + /** + * 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())); + ApkAssets.loadApkForLoader(fileDescriptor.getFileDescriptor()), assetsProvider); } /** - * Read from an .apk file representation in memory. + * 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 loadFromApk(sharedMemory, null); + } + + /** + * Creates a ResourcesProvider from an {@code .apk} file representation in memory. + * + * @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 loadFromApk(@NonNull SharedMemory sharedMemory, + @Nullable AssetsProvider assetsProvider) + throws IOException { return new ResourcesProvider( - ApkAssets.loadApkForLoader(sharedMemory.getFileDescriptor())); + ApkAssets.loadApkForLoader(sharedMemory.getFileDescriptor()), assetsProvider); } /** - * Read from an .arsc file descriptor. + * 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. * - * The file descriptor is duplicated and the one passed in may be closed by the application - * at any time. + * @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 ParcelFileDescriptor fileDescriptor) + public static ResourcesProvider loadFromTable(@NonNull ParcelFileDescriptor fileDescriptor, + @Nullable AssetsProvider assetsProvider) throws IOException { return new ResourcesProvider( - ApkAssets.loadArscForLoader(fileDescriptor.getFileDescriptor())); + ApkAssets.loadArscForLoader(fileDescriptor.getFileDescriptor()), assetsProvider); } /** - * Read from an .arsc file representation in memory. + * 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 loadFromArsc(@NonNull SharedMemory sharedMemory) + public static ResourcesProvider loadFromTable(@NonNull SharedMemory sharedMemory, + @Nullable AssetsProvider assetsProvider) throws IOException { return new ResourcesProvider( - ApkAssets.loadArscForLoader(sharedMemory.getFileDescriptor())); + 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 @@ public final class ResourcesProvider implements AutoCloseable, Closeable { } 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 @@ public final class ResourcesProvider implements AutoCloseable, Closeable { 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 @@ public final class ResourcesProvider implements AutoCloseable, Closeable { @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 f8df883a3e1c..e975bb672060 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 @@ public class DisplayPolicy { mDisplayContent.getDisplayId(), null /* overrideConfig */, uiContext.getResources().getCompatibilityInfo(), - null /* classLoader */); + null /* classLoader */, + null /* loaders */); } @VisibleForTesting |