diff options
author | Ryan Mitchell <rtmitchell@google.com> | 2020-01-29 06:57:47 +0000 |
---|---|---|
committer | Android (Google) Code Review <android-gerrit@google.com> | 2020-01-29 06:57:47 +0000 |
commit | 1e80cd76ed4b92ad71da3cd277cebff6d82e24b1 (patch) | |
tree | 31bc8c681341e9516c3cebe8a749b359c8345e73 | |
parent | b37b54cca901c37e5d3c6d7aa280fdf1fdb0ba4f (diff) | |
parent | 6115858c21f6b21b78c5e20d502572af58a69268 (diff) |
Merge changes from topic "res-loader"
* changes:
Refactor ResourcesLoader Tests
Refactor ResourcesLoader APIs
59 files changed, 1819 insertions, 1896 deletions
diff --git a/apct-tests/perftests/core/src/android/app/ResourcesManagerPerfTest.java b/apct-tests/perftests/core/src/android/app/ResourcesManagerPerfTest.java index 2955d2ca7d0e..050fecde8213 100644 --- a/apct-tests/perftests/core/src/android/app/ResourcesManagerPerfTest.java +++ b/apct-tests/perftests/core/src/android/app/ResourcesManagerPerfTest.java @@ -80,7 +80,7 @@ public class ResourcesManagerPerfTest { private void getResourcesForPath(String path) { ResourcesManager.getInstance().getResources(null, path, null, null, null, Display.DEFAULT_DISPLAY, null, sContext.getResources().getCompatibilityInfo(), - null); + null, null); } @Test diff --git a/apct-tests/perftests/core/src/android/app/ResourcesThemePerfTest.java b/apct-tests/perftests/core/src/android/app/ResourcesThemePerfTest.java index 6123e69b584e..f4c0a172710b 100644 --- a/apct-tests/perftests/core/src/android/app/ResourcesThemePerfTest.java +++ b/apct-tests/perftests/core/src/android/app/ResourcesThemePerfTest.java @@ -96,7 +96,7 @@ public class ResourcesThemePerfTest { Resources destResources = resourcesManager.getResources(null, ai.sourceDir, ai.splitSourceDirs, ai.resourceDirs, ai.sharedLibraryFiles, Display.DEFAULT_DISPLAY, - c, mContext.getResources().getCompatibilityInfo(), null); + c, mContext.getResources().getCompatibilityInfo(), null, null); Assert.assertNotEquals(destResources.getAssets(), mContext.getAssets()); Resources.Theme destTheme = destResources.newTheme(); diff --git a/api/current.txt b/api/current.txt index 12d8b70327dd..77dec70dd8e5 100644 --- a/api/current.txt +++ b/api/current.txt @@ -12653,8 +12653,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; @@ -12681,7 +12681,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; @@ -12709,8 +12709,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 } @@ -12780,27 +12780,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 d90e81f09800..c901d2a29821 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -2198,7 +2198,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 dbfc65066c11..430241aac072 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -8521,7 +8521,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/core/tests/ResourceLoaderTests/Android.bp b/core/tests/ResourceLoaderTests/Android.bp index 53db8322f7b8..fec4628106a5 100644 --- a/core/tests/ResourceLoaderTests/Android.bp +++ b/core/tests/ResourceLoaderTests/Android.bp @@ -32,15 +32,16 @@ android_test { "truth-prebuilt", ], resource_zips: [ ":FrameworksResourceLoaderTestsAssets" ], + platform_apis: true, test_suites: ["device-tests"], - sdk_version: "test_current", aaptflags: [ "--no-compress", ], data: [ - ":FrameworksResourceLoaderTestsOverlay", ":FrameworksResourceLoaderTestsSplitOne", ":FrameworksResourceLoaderTestsSplitTwo", + ":FrameworksResourceLoaderTestsSplitThree", + ":FrameworksResourceLoaderTestsSplitFour", ], java_resources: [ "NonAsset.txt" ] } diff --git a/core/tests/ResourceLoaderTests/AndroidTest.xml b/core/tests/ResourceLoaderTests/AndroidTest.xml index 702151d01110..d732132bef02 100644 --- a/core/tests/ResourceLoaderTests/AndroidTest.xml +++ b/core/tests/ResourceLoaderTests/AndroidTest.xml @@ -22,13 +22,7 @@ <option name="cleanup-apks" value="true" /> <!-- The following value cannot be multi-line as whitespace is parsed by the installer --> <option name="split-apk-file-names" - value="FrameworksResourceLoaderTests.apk,FrameworksResourceLoaderTestsSplitOne.apk,FrameworksResourceLoaderTestsSplitTwo.apk" /> - <option name="test-file-name" value="FrameworksResourceLoaderTestsOverlay.apk" /> - </target_preparer> - - <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer"> - <option name="run-command" - value="cmd overlay disable android.content.res.loader.test.overlay" /> + value="FrameworksResourceLoaderTests.apk,FrameworksResourceLoaderTestsSplitOne.apk,FrameworksResourceLoaderTestsSplitTwo.apk,FrameworksResourceLoaderTestsSplitThree.apk,FrameworksResourceLoaderTestsSplitFour.apk" /> </target_preparer> <test class="com.android.tradefed.testtype.AndroidJUnitTest"> diff --git a/core/tests/ResourceLoaderTests/res/drawable-nodpi/non_asset_bitmap.png b/core/tests/ResourceLoaderTests/res/drawable-nodpi/non_asset_bitmap.png Binary files differindex efd71ee039e2..8102d1539d53 100644 --- a/core/tests/ResourceLoaderTests/res/drawable-nodpi/non_asset_bitmap.png +++ b/core/tests/ResourceLoaderTests/res/drawable-nodpi/non_asset_bitmap.png diff --git a/core/tests/ResourceLoaderTests/res/layout/layout.xml b/core/tests/ResourceLoaderTests/res/layout/layout.xml index d59059b453d6..05499ed35e50 100644 --- a/core/tests/ResourceLoaderTests/res/layout/layout.xml +++ b/core/tests/ResourceLoaderTests/res/layout/layout.xml @@ -15,7 +15,7 @@ ~ limitations under the License. --> -<FrameLayout +<MysteryLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" diff --git a/core/tests/ResourceLoaderTests/resources/compileAndLink.sh b/core/tests/ResourceLoaderTests/resources/compileAndLink.sh index 885f681f4261..8e05aefccd39 100755 --- a/core/tests/ResourceLoaderTests/resources/compileAndLink.sh +++ b/core/tests/ResourceLoaderTests/resources/compileAndLink.sh @@ -68,9 +68,13 @@ mkdir -p "$genDir"/temp/res/layout compileAndLink stringOne BOTH AndroidManifestFramework.xml res/values/string_one.xml compileAndLink stringTwo BOTH AndroidManifestFramework.xml res/values/string_two.xml +compileAndLink stringThree BOTH AndroidManifestFramework.xml res/values/string_three.xml +compileAndLink stringFour BOTH AndroidManifestFramework.xml res/values/string_four.xml compileAndLink dimenOne BOTH AndroidManifestFramework.xml res/values/dimen_one.xml compileAndLink dimenTwo BOTH AndroidManifestFramework.xml res/values/dimen_two.xml +compileAndLink dimenThree BOTH AndroidManifestFramework.xml res/values/dimen_three.xml +compileAndLink dimenFour BOTH AndroidManifestFramework.xml res/values/dimen_four.xml compileAndLink drawableMdpiWithoutFile BOTH_WITHOUT_FILE AndroidManifestFramework.xml res/values/drawable_one.xml res/drawable-mdpi/ic_delete.png compileAndLink drawableMdpiWithFile APK AndroidManifestFramework.xml res/values/drawable_one.xml res/drawable-mdpi/ic_delete.png @@ -86,6 +90,14 @@ cp -f "$inDir"/res/layout/layout_two.xml "$genDir"/temp/res/layout/layout.xml compileAndLink layoutTwo ARSC AndroidManifestApp.xml "$genDir"/temp/res/layout/layout.xml res/values/layout_id.xml cp -f "$genDir"/out/layoutTwo/unzip/res/layout/layout.xml "$genDir"/output/raw/layoutTwo.xml +cp -f "$inDir"/res/layout/layout_three.xml "$genDir"/temp/res/layout/layout.xml +compileAndLink layoutThree ARSC AndroidManifestApp.xml "$genDir"/temp/res/layout/layout.xml res/values/layout_id.xml +cp -f "$genDir"/out/layoutThree/unzip/res/layout/layout.xml "$genDir"/output/raw/layoutThree.xml + +cp -f "$inDir"/res/layout/layout_four.xml "$genDir"/temp/res/layout/layout.xml +compileAndLink layoutFour ARSC AndroidManifestApp.xml "$genDir"/temp/res/layout/layout.xml res/values/layout_id.xml +cp -f "$genDir"/out/layoutFour/unzip/res/layout/layout.xml "$genDir"/output/raw/layoutFour.xml + drawableNoDpi="/res/drawable-nodpi" inDirDrawableNoDpi="$inDir$drawableNoDpi" @@ -97,6 +109,18 @@ cp -f "$inDirDrawableNoDpi"/nonAssetDrawableTwo.xml "$genDir"/temp/res/drawable- compileAndLink nonAssetDrawableTwo ARSC AndroidManifestApp.xml "$genDir"/temp/res/drawable-nodpi/non_asset_drawable.xml res/values/non_asset_drawable_id.xml cp -f "$genDir"/out/nonAssetDrawableTwo/unzip/res/drawable-nodpi-v4/non_asset_drawable.xml "$genDir"/output/raw/nonAssetDrawableTwo.xml +cp -f "$inDirDrawableNoDpi"/nonAssetDrawableThree.xml "$genDir"/temp/res/drawable-nodpi/non_asset_drawable.xml +compileAndLink nonAssetDrawableThree ARSC AndroidManifestApp.xml "$genDir"/temp/res/drawable-nodpi/non_asset_drawable.xml res/values/non_asset_drawable_id.xml +cp -f "$genDir"/out/nonAssetDrawableThree/unzip/res/drawable-nodpi-v4/non_asset_drawable.xml "$genDir"/output/raw/nonAssetDrawableThree.xml + +cp -f "$inDirDrawableNoDpi"/nonAssetDrawableFour.xml "$genDir"/temp/res/drawable-nodpi/non_asset_drawable.xml +compileAndLink nonAssetDrawableFour ARSC AndroidManifestApp.xml "$genDir"/temp/res/drawable-nodpi/non_asset_drawable.xml res/values/non_asset_drawable_id.xml +cp -f "$genDir"/out/nonAssetDrawableFour/unzip/res/drawable-nodpi-v4/non_asset_drawable.xml "$genDir"/output/raw/nonAssetDrawableFour.xml + +cp -f "$inDirDrawableNoDpi"/nonAssetBitmapRed.png "$genDir"/temp/res/drawable-nodpi/non_asset_bitmap.png +compileAndLink nonAssetBitmapRed BOTH AndroidManifestApp.xml "$genDir"/temp/res/drawable-nodpi/non_asset_bitmap.png res/values/non_asset_bitmap_id.xml +cp -f "$genDir"/out/nonAssetBitmapRed/unzip/res/drawable-nodpi-v4/non_asset_bitmap.png "$genDir"/output/raw/nonAssetBitmapRed.png + cp -f "$inDirDrawableNoDpi"/nonAssetBitmapGreen.png "$genDir"/temp/res/drawable-nodpi/non_asset_bitmap.png compileAndLink nonAssetBitmapGreen BOTH AndroidManifestApp.xml "$genDir"/temp/res/drawable-nodpi/non_asset_bitmap.png res/values/non_asset_bitmap_id.xml cp -f "$genDir"/out/nonAssetBitmapGreen/unzip/res/drawable-nodpi-v4/non_asset_bitmap.png "$genDir"/output/raw/nonAssetBitmapGreen.png @@ -105,4 +129,8 @@ cp -f "$inDirDrawableNoDpi"/nonAssetBitmapBlue.png "$genDir"/temp/res/drawable-n compileAndLink nonAssetBitmapBlue ARSC AndroidManifestApp.xml "$genDir"/temp/res/drawable-nodpi/non_asset_bitmap.png res/values/non_asset_bitmap_id.xml cp -f "$genDir"/out/nonAssetBitmapBlue/unzip/res/drawable-nodpi-v4/non_asset_bitmap.png "$genDir"/output/raw/nonAssetBitmapBlue.png +cp -f "$inDirDrawableNoDpi"/nonAssetBitmapWhite.png "$genDir"/temp/res/drawable-nodpi/non_asset_bitmap.png +compileAndLink nonAssetBitmapWhite ARSC AndroidManifestApp.xml "$genDir"/temp/res/drawable-nodpi/non_asset_bitmap.png res/values/non_asset_bitmap_id.xml +cp -f "$genDir"/out/nonAssetBitmapWhite/unzip/res/drawable-nodpi-v4/non_asset_bitmap.png "$genDir"/output/raw/nonAssetBitmapWhite.png + $soong_zip -o "$genDir"/out.zip -C "$genDir"/output/ -D "$genDir"/output/ diff --git a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapRed.png b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapRed.png Binary files differnew file mode 100644 index 000000000000..4eb8ca3537ea --- /dev/null +++ b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapRed.png diff --git a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapWhite.png b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapWhite.png Binary files differnew file mode 100644 index 000000000000..e9a4cfcef316 --- /dev/null +++ b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapWhite.png diff --git a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableFour.xml b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableFour.xml new file mode 100644 index 000000000000..0623245c6152 --- /dev/null +++ b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableFour.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<color + xmlns:android="http://schemas.android.com/apk/res/android" + android:color="#000004" + /> diff --git a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableOne.xml b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableOne.xml index f1a93d2d2f21..57a8cf1b86de 100644 --- a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableOne.xml +++ b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableOne.xml @@ -17,5 +17,5 @@ <color xmlns:android="http://schemas.android.com/apk/res/android" - android:color="#A3C3E3" + android:color="#000001" /> diff --git a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableThree.xml b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableThree.xml new file mode 100644 index 000000000000..41095d4a158b --- /dev/null +++ b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableThree.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<color + xmlns:android="http://schemas.android.com/apk/res/android" + android:color="#000003" + /> diff --git a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableTwo.xml b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableTwo.xml index 7c455a57fb0b..333fe346998c 100644 --- a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableTwo.xml +++ b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableTwo.xml @@ -17,5 +17,5 @@ <color xmlns:android="http://schemas.android.com/apk/res/android" - android:color="#3A3C3E" + android:color="#000002" /> diff --git a/core/tests/ResourceLoaderTests/resources/res/layout/layout_four.xml b/core/tests/ResourceLoaderTests/resources/res/layout/layout_four.xml new file mode 100644 index 000000000000..ab9e26529fe7 --- /dev/null +++ b/core/tests/ResourceLoaderTests/resources/res/layout/layout_four.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<TableLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + /> + diff --git a/core/tests/ResourceLoaderTests/resources/res/layout/layout_three.xml b/core/tests/ResourceLoaderTests/resources/res/layout/layout_three.xml new file mode 100644 index 000000000000..d58d3db12ad4 --- /dev/null +++ b/core/tests/ResourceLoaderTests/resources/res/layout/layout_three.xml @@ -0,0 +1,23 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<FrameLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + /> + diff --git a/core/tests/ResourceLoaderTests/overlay/res/values/strings.xml b/core/tests/ResourceLoaderTests/resources/res/values/dimen_four.xml index 348bb353611a..5b30eba5953b 100644 --- a/core/tests/ResourceLoaderTests/overlay/res/values/strings.xml +++ b/core/tests/ResourceLoaderTests/resources/res/values/dimen_four.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - ~ 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. @@ -16,7 +16,6 @@ --> <resources> - - <string name="loader_path_change_test">Overlaid</string> - + <public type="dimen" name="app_icon_size" id="0x01050000" /> + <dimen name="app_icon_size">400dp</dimen> </resources> diff --git a/core/tests/ResourceLoaderTests/resources/res/values/dimen_one.xml b/core/tests/ResourceLoaderTests/resources/res/values/dimen_one.xml index 69ecf2316284..b17ec1c66717 100644 --- a/core/tests/ResourceLoaderTests/resources/res/values/dimen_one.xml +++ b/core/tests/ResourceLoaderTests/resources/res/values/dimen_one.xml @@ -17,5 +17,5 @@ <resources> <public type="dimen" name="app_icon_size" id="0x01050000" /> - <dimen name="app_icon_size">564716dp</dimen> + <dimen name="app_icon_size">100dp</dimen> </resources> diff --git a/core/tests/ResourceLoaderTests/resources/res/values/dimen_three.xml b/core/tests/ResourceLoaderTests/resources/res/values/dimen_three.xml new file mode 100644 index 000000000000..07a35cedd886 --- /dev/null +++ b/core/tests/ResourceLoaderTests/resources/res/values/dimen_three.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<resources> + <public type="dimen" name="app_icon_size" id="0x01050000" /> + <dimen name="app_icon_size">300dp</dimen> +</resources> diff --git a/core/tests/ResourceLoaderTests/resources/res/values/dimen_two.xml b/core/tests/ResourceLoaderTests/resources/res/values/dimen_two.xml index 4d55deffbd2a..570b40aa7a7a 100644 --- a/core/tests/ResourceLoaderTests/resources/res/values/dimen_two.xml +++ b/core/tests/ResourceLoaderTests/resources/res/values/dimen_two.xml @@ -17,5 +17,5 @@ <resources> <public type="dimen" name="app_icon_size" id="0x01050000" /> - <dimen name="app_icon_size">565717dp</dimen> + <dimen name="app_icon_size">200dp</dimen> </resources> diff --git a/core/tests/ResourceLoaderTests/resources/res/values/string_four.xml b/core/tests/ResourceLoaderTests/resources/res/values/string_four.xml new file mode 100644 index 000000000000..8789bcdb066c --- /dev/null +++ b/core/tests/ResourceLoaderTests/resources/res/values/string_four.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<resources> + <public type="string" name="cancel" id="0x01040000" /> + <string name="cancel">SomeRidiculouslyUnlikelyStringFour</string> +</resources> diff --git a/core/tests/ResourceLoaderTests/resources/res/values/string_three.xml b/core/tests/ResourceLoaderTests/resources/res/values/string_three.xml new file mode 100644 index 000000000000..82cd6ec7270f --- /dev/null +++ b/core/tests/ResourceLoaderTests/resources/res/values/string_three.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<resources> + <public type="string" name="cancel" id="0x01040000" /> + <string name="cancel">SomeRidiculouslyUnlikelyStringThree</string> +</resources> diff --git a/core/tests/ResourceLoaderTests/overlay/Android.bp b/core/tests/ResourceLoaderTests/splits/SplitFour/Android.bp index 63e7e61d797a..eb4d8e1ac7f2 100644 --- a/core/tests/ResourceLoaderTests/overlay/Android.bp +++ b/core/tests/ResourceLoaderTests/splits/SplitFour/Android.bp @@ -1,4 +1,5 @@ -// Copyright (C) 2018 The Android Open Source Project +// +// 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. @@ -11,10 +12,8 @@ // 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. +// -android_test { - name: "FrameworksResourceLoaderTestsOverlay", - sdk_version: "current", - - aaptflags: ["--no-resource-removal"], +android_test_helper_app { + name: "FrameworksResourceLoaderTestsSplitFour" } diff --git a/core/tests/ResourceLoaderTests/overlay/AndroidManifest.xml b/core/tests/ResourceLoaderTests/splits/SplitFour/AndroidManifest.xml index 942f7da9aa27..24a0a2a64afb 100644 --- a/core/tests/ResourceLoaderTests/overlay/AndroidManifest.xml +++ b/core/tests/ResourceLoaderTests/splits/SplitFour/AndroidManifest.xml @@ -1,6 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <!-- - ~ 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. @@ -17,11 +17,11 @@ <manifest xmlns:android="http://schemas.android.com/apk/res/android" - package="android.content.res.loader.test.overlay" + package="android.content.res.loader.test" + split="split_four" > + <uses-sdk android:minSdkVersion="1" android:targetSdkVersion="1" /> <application android:hasCode="false" /> - <overlay android:targetPackage="android.content.res.loader.test" /> - </manifest> diff --git a/core/tests/ResourceLoaderTests/splits/SplitFour/res/values/string_split.xml b/core/tests/ResourceLoaderTests/splits/SplitFour/res/values/string_split.xml new file mode 100644 index 000000000000..4759db978dcb --- /dev/null +++ b/core/tests/ResourceLoaderTests/splits/SplitFour/res/values/string_split.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<resources> + <public type="string" name="split_overlaid" id="0x7f040001" /> + <string name="split_overlaid">Split FOUR Overlaid</string> +</resources> diff --git a/core/tests/ResourceLoaderTests/SplitOne/Android.bp b/core/tests/ResourceLoaderTests/splits/SplitOne/Android.bp index 897897fbf254..897897fbf254 100644 --- a/core/tests/ResourceLoaderTests/SplitOne/Android.bp +++ b/core/tests/ResourceLoaderTests/splits/SplitOne/Android.bp diff --git a/core/tests/ResourceLoaderTests/SplitOne/AndroidManifest.xml b/core/tests/ResourceLoaderTests/splits/SplitOne/AndroidManifest.xml index b14bd8600f31..b14bd8600f31 100644 --- a/core/tests/ResourceLoaderTests/SplitOne/AndroidManifest.xml +++ b/core/tests/ResourceLoaderTests/splits/SplitOne/AndroidManifest.xml diff --git a/core/tests/ResourceLoaderTests/SplitOne/res/values/string_split_one.xml b/core/tests/ResourceLoaderTests/splits/SplitOne/res/values/string_split.xml index 3c215ebc287c..3c215ebc287c 100644 --- a/core/tests/ResourceLoaderTests/SplitOne/res/values/string_split_one.xml +++ b/core/tests/ResourceLoaderTests/splits/SplitOne/res/values/string_split.xml diff --git a/core/tests/ResourceLoaderTests/splits/SplitThree/Android.bp b/core/tests/ResourceLoaderTests/splits/SplitThree/Android.bp new file mode 100644 index 000000000000..bf98a740cd88 --- /dev/null +++ b/core/tests/ResourceLoaderTests/splits/SplitThree/Android.bp @@ -0,0 +1,19 @@ +// +// 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. +// + +android_test_helper_app { + name: "FrameworksResourceLoaderTestsSplitThree" +} diff --git a/core/tests/ResourceLoaderTests/splits/SplitThree/AndroidManifest.xml b/core/tests/ResourceLoaderTests/splits/SplitThree/AndroidManifest.xml new file mode 100644 index 000000000000..ae1579b178f3 --- /dev/null +++ b/core/tests/ResourceLoaderTests/splits/SplitThree/AndroidManifest.xml @@ -0,0 +1,27 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<manifest + xmlns:android="http://schemas.android.com/apk/res/android" + package="android.content.res.loader.test" + split="split_three" + > + + <uses-sdk android:minSdkVersion="1" android:targetSdkVersion="1" /> + <application android:hasCode="false" /> + +</manifest> diff --git a/core/tests/ResourceLoaderTests/splits/SplitThree/res/values/string_spli.xml b/core/tests/ResourceLoaderTests/splits/SplitThree/res/values/string_spli.xml new file mode 100644 index 000000000000..97682aa1b5cf --- /dev/null +++ b/core/tests/ResourceLoaderTests/splits/SplitThree/res/values/string_spli.xml @@ -0,0 +1,21 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + ~ 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. + --> + +<resources> + <public type="string" name="split_overlaid" id="0x7f040001" /> + <string name="split_overlaid">Split THREE Overlaid</string> +</resources> diff --git a/core/tests/ResourceLoaderTests/splits/Android.bp b/core/tests/ResourceLoaderTests/splits/SplitTwo/Android.bp index 4582808934df..4582808934df 100644 --- a/core/tests/ResourceLoaderTests/splits/Android.bp +++ b/core/tests/ResourceLoaderTests/splits/SplitTwo/Android.bp diff --git a/core/tests/ResourceLoaderTests/splits/AndroidManifest.xml b/core/tests/ResourceLoaderTests/splits/SplitTwo/AndroidManifest.xml index aad8c27a1a3b..aad8c27a1a3b 100644 --- a/core/tests/ResourceLoaderTests/splits/AndroidManifest.xml +++ b/core/tests/ResourceLoaderTests/splits/SplitTwo/AndroidManifest.xml diff --git a/core/tests/ResourceLoaderTests/splits/res/values/string_split_two.xml b/core/tests/ResourceLoaderTests/splits/SplitTwo/res/values/string_split.xml index a367063dd43e..a367063dd43e 100644 --- a/core/tests/ResourceLoaderTests/splits/res/values/string_split_two.xml +++ b/core/tests/ResourceLoaderTests/splits/SplitTwo/res/values/string_split.xml diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryResourceLoaderTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryAssetsProviderTest.kt index b1bdc967e68f..9e94bdc8a081 100644 --- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryResourceLoaderTest.kt +++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryAssetsProviderTest.kt @@ -16,8 +16,9 @@ package android.content.res.loader.test -import android.content.res.loader.DirectoryResourceLoader -import android.content.res.loader.ResourceLoader +import android.content.res.loader.AssetsProvider +import android.content.res.loader.DirectoryAssetsProvider +import android.content.res.loader.ResourcesLoader import android.graphics.Color import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.ColorDrawable @@ -29,18 +30,21 @@ import org.junit.Test import org.junit.rules.TestName import java.io.File -class DirectoryResourceLoaderTest : ResourceLoaderTestBase() { +class DirectoryAssetsProviderTest : ResourceLoaderTestBase() { @get:Rule val testName = TestName() private lateinit var testDir: File - private lateinit var loader: ResourceLoader + private lateinit var assetsProvider: AssetsProvider + private lateinit var loader: ResourcesLoader @Before fun setUpTestDir() { - testDir = context.filesDir.resolve("DirectoryResourceLoaderTest_${testName.methodName}") - loader = DirectoryResourceLoader(testDir) + testDir = context.filesDir.resolve("DirectoryAssetsProvider_${testName.methodName}") + assetsProvider = DirectoryAssetsProvider(testDir) + loader = ResourcesLoader() + resources.addLoader(loader) } @After @@ -51,29 +55,29 @@ class DirectoryResourceLoaderTest : ResourceLoaderTestBase() { @Test fun loadDrawableXml() { "nonAssetDrawableOne" writeTo "res/drawable-nodpi-v4/non_asset_drawable.xml" - val provider = openArsc("nonAssetDrawableOne") + val provider = openArsc("nonAssetDrawableOne", assetsProvider) fun getValue() = (resources.getDrawable(R.drawable.non_asset_drawable) as ColorDrawable) .color assertThat(getValue()).isEqualTo(Color.parseColor("#B2D2F2")) - addLoader(loader to provider) + loader.addProvider(provider) - assertThat(getValue()).isEqualTo(Color.parseColor("#A3C3E3")) + assertThat(getValue()).isEqualTo(Color.parseColor("#000001")) } @Test fun loadDrawableBitmap() { "nonAssetBitmapGreen" writeTo "res/drawable-nodpi-v4/non_asset_bitmap.png" - val provider = openArsc("nonAssetBitmapGreen") + val provider = openArsc("nonAssetBitmapGreen", assetsProvider) fun getValue() = (resources.getDrawable(R.drawable.non_asset_bitmap) as BitmapDrawable) .bitmap.getColor(0, 0).toArgb() - assertThat(getValue()).isEqualTo(Color.RED) + assertThat(getValue()).isEqualTo(Color.MAGENTA) - addLoader(loader to provider) + loader.addProvider(provider) assertThat(getValue()).isEqualTo(Color.GREEN) } @@ -81,13 +85,13 @@ class DirectoryResourceLoaderTest : ResourceLoaderTestBase() { @Test fun loadXml() { "layoutOne" writeTo "res/layout/layout.xml" - val provider = openArsc("layoutOne") + val provider = openArsc("layoutOne", assetsProvider) fun getValue() = resources.getLayout(R.layout.layout).advanceToRoot().name - assertThat(getValue()).isEqualTo("FrameLayout") + assertThat(getValue()).isEqualTo("MysteryLayout") - addLoader(loader to provider) + loader.addProvider(provider) assertThat(getValue()).isEqualTo("RelativeLayout") } diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetTest.kt deleted file mode 100644 index a6a83789c082..000000000000 --- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetTest.kt +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.content.res.loader.test - -import android.content.res.AssetManager -import android.content.res.loader.DirectoryResourceLoader -import android.content.res.loader.ResourceLoader -import android.content.res.loader.ResourcesProvider -import com.google.common.truth.Truth.assertThat -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.rules.TestName -import org.junit.runner.RunWith -import org.junit.runners.Parameterized -import org.mockito.Mockito.anyInt -import org.mockito.Mockito.anyString -import org.mockito.Mockito.doAnswer -import org.mockito.Mockito.doReturn -import org.mockito.Mockito.eq -import org.mockito.Mockito.inOrder -import org.mockito.Mockito.mock -import java.io.File -import java.io.FileNotFoundException -import java.io.IOException -import java.nio.file.Paths - -@RunWith(Parameterized::class) -class ResourceLoaderAssetTest : ResourceLoaderTestBase() { - - companion object { - private const val BASE_TEST_PATH = "android/content/res/loader/test/file.txt" - private const val TEST_TEXT = "some text" - - @JvmStatic - @Parameterized.Parameters(name = "{0}") - fun parameters(): Array<Array<out Any?>> { - val fromInputStream: ResourceLoader.(String) -> Any? = { - loadAsset(eq(it), anyInt()) - } - - val fromFileDescriptor: ResourceLoader.(String) -> Any? = { - loadAssetFd(eq(it)) - } - - val openAsset: AssetManager.() -> String? = { - open(BASE_TEST_PATH).reader().readText() - } - - val openNonAsset: AssetManager.() -> String? = { - openNonAssetFd(BASE_TEST_PATH).readText() - } - - return arrayOf( - arrayOf("assets", fromInputStream, openAsset), - arrayOf("", fromFileDescriptor, openNonAsset) - ) - } - } - - @get:Rule - val testName = TestName() - - @JvmField - @field:Parameterized.Parameter(0) - var prefix: String? = null - - @field:Parameterized.Parameter(1) - lateinit var loadAssetFunction: ResourceLoader.(String) -> Any? - - @field:Parameterized.Parameter(2) - lateinit var openAssetFunction: AssetManager.() -> String? - - private val testPath: String - get() = Paths.get(prefix.orEmpty(), BASE_TEST_PATH).toString() - - private fun ResourceLoader.loadAsset() = loadAssetFunction(testPath) - - private fun AssetManager.openAsset() = openAssetFunction() - - private lateinit var testDir: File - - @Before - fun setUpTestDir() { - testDir = context.filesDir.resolve("DirectoryResourceLoaderTest_${testName.methodName}") - testDir.resolve(testPath).apply { parentFile!!.mkdirs() }.writeText(TEST_TEXT) - } - - @Test - fun multipleLoadersSearchesBackwards() { - // DirectoryResourceLoader relies on a private field and can't be spied directly, so wrap it - val loader = DirectoryResourceLoader(testDir) - val loaderWrapper = mock(ResourceLoader::class.java).apply { - doAnswer { loader.loadAsset(it.arguments[0] as String, it.arguments[1] as Int) } - .`when`(this).loadAsset(anyString(), anyInt()) - doAnswer { loader.loadAssetFd(it.arguments[0] as String) } - .`when`(this).loadAssetFd(anyString()) - } - - val one = loaderWrapper to ResourcesProvider.empty() - val two = mockLoader { - doReturn(null).`when`(it).loadAsset() - } - - addLoader(one, two) - - assertOpenedAsset() - inOrder(two.first, one.first).apply { - verify(two.first).loadAsset() - verify(one.first).loadAsset() - } - } - - @Test(expected = FileNotFoundException::class) - fun failToFindThrowsFileNotFound() { - val one = mockLoader { - doReturn(null).`when`(it).loadAsset() - } - val two = mockLoader { - doReturn(null).`when`(it).loadAsset() - } - - addLoader(one, two) - - assertOpenedAsset() - } - - @Test - fun throwingIOExceptionIsSkipped() { - val one = DirectoryResourceLoader(testDir) to ResourcesProvider.empty() - val two = mockLoader { - doAnswer { throw IOException() }.`when`(it).loadAsset() - } - - addLoader(one, two) - - assertOpenedAsset() - } - - @Test(expected = IllegalStateException::class) - fun throwingNonIOExceptionCausesFailure() { - val one = DirectoryResourceLoader(testDir) to ResourcesProvider.empty() - val two = mockLoader { - doAnswer { throw IllegalStateException() }.`when`(it).loadAsset() - } - - addLoader(one, two) - - assertOpenedAsset() - } - - private fun assertOpenedAsset() { - assertThat(resources.assets.openAsset()).isEqualTo(TEST_TEXT) - } -} diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetsTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetsTest.kt new file mode 100644 index 000000000000..e3ba93d64b0f --- /dev/null +++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderAssetsTest.kt @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.content.res.loader.test + +import android.content.res.AssetManager +import android.content.res.loader.AssetsProvider +import android.content.res.loader.DirectoryAssetsProvider +import android.content.res.loader.ResourcesLoader +import android.content.res.loader.ResourcesProvider +import com.google.common.truth.Truth.assertThat +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TestName +import org.junit.runner.RunWith +import org.junit.runners.Parameterized +import org.mockito.Mockito.anyInt +import org.mockito.Mockito.anyString +import org.mockito.Mockito.doAnswer +import org.mockito.Mockito.doReturn +import org.mockito.Mockito.eq +import org.mockito.Mockito.inOrder +import org.mockito.Mockito.mock +import java.io.File +import java.io.FileNotFoundException +import java.io.IOException +import java.nio.file.Paths + +@RunWith(Parameterized::class) +class ResourceLoaderAssetsTest : ResourceLoaderTestBase() { + + companion object { + private const val BASE_TEST_PATH = "android/content/res/loader/test/file.txt" + private const val TEST_TEXT = "some text" + + @JvmStatic + @Parameterized.Parameters(name = "{0}") + fun parameters(): Array<Array<out Any?>> { + val fromInputStream: AssetsProvider.(String) -> Any? = { + loadAsset(eq(it), anyInt()) + } + + val fromFileDescriptor: AssetsProvider.(String) -> Any? = { + loadAssetParcelFd(eq(it)) + } + + val openAsset: AssetManager.() -> String? = { + open(BASE_TEST_PATH).reader().readText() + } + + val openNonAsset: AssetManager.() -> String? = { + openNonAssetFd(BASE_TEST_PATH).readText() + } + + return arrayOf( + arrayOf("assets", fromInputStream, openAsset), + arrayOf("", fromFileDescriptor, openNonAsset) + ) + } + } + + @get:Rule + val testName = TestName() + + @JvmField + @field:Parameterized.Parameter(0) + var prefix: String? = null + + @field:Parameterized.Parameter(1) + lateinit var loadAssetFunction: AssetsProvider.(String) -> Any? + + @field:Parameterized.Parameter(2) + lateinit var openAssetFunction: AssetManager.() -> String? + + private val testPath: String + get() = Paths.get(prefix.orEmpty(), BASE_TEST_PATH).toString() + + private fun AssetsProvider.loadAsset() = loadAssetFunction(testPath) + + private fun AssetManager.openAsset() = openAssetFunction() + + private lateinit var testDir: File + + @Before + fun setUpTestDir() { + testDir = context.filesDir.resolve("DirectoryAssetsProvider_${testName.methodName}") + testDir.resolve(testPath).apply { parentFile!!.mkdirs() }.writeText(TEST_TEXT) + } + + @Test + fun multipleProvidersSearchesBackwards() { + // DirectoryResourceLoader relies on a private field and can't be spied directly, so wrap it + val assetsProvider = DirectoryAssetsProvider(testDir) + val assetProviderWrapper = mock(AssetsProvider::class.java).apply { + doAnswer { assetsProvider.loadAsset(it.arguments[0] as String, it.arguments[1] as Int) } + .`when`(this).loadAsset(anyString(), anyInt()) + doAnswer { assetsProvider.loadAssetParcelFd(it.arguments[0] as String) } + .`when`(this).loadAssetParcelFd(anyString()) + } + + val one = ResourcesProvider.empty(assetProviderWrapper) + val two = mockProvider { + doReturn(null).`when`(it).loadAsset() + } + + val loader = ResourcesLoader() + loader.providers = listOf(one, two) + resources.addLoader(loader) + + assertOpenedAsset() + inOrder(two.assetsProvider, one.assetsProvider).apply { + verify(two.assetsProvider)?.loadAsset() + verify(one.assetsProvider)?.loadAsset() + } + } + + @Test + fun multipleLoadersSearchesBackwards() { + // DirectoryResourceLoader relies on a private field and can't be spied directly, so wrap it + val assetsProvider = DirectoryAssetsProvider(testDir) + val assetProviderWrapper = mock(AssetsProvider::class.java).apply { + doAnswer { assetsProvider.loadAsset(it.arguments[0] as String, it.arguments[1] as Int) } + .`when`(this).loadAsset(anyString(), anyInt()) + doAnswer { assetsProvider.loadAssetParcelFd(it.arguments[0] as String) } + .`when`(this).loadAssetParcelFd(anyString()) + } + + val one = ResourcesProvider.empty(assetProviderWrapper) + val two = mockProvider { + doReturn(null).`when`(it).loadAsset() + } + + val loader1 = ResourcesLoader() + loader1.addProvider(one) + val loader2 = ResourcesLoader() + loader2.addProvider(two) + + resources.loaders = listOf(loader1, loader2) + + assertOpenedAsset() + inOrder(two.assetsProvider, one.assetsProvider).apply { + verify(two.assetsProvider)?.loadAsset() + verify(one.assetsProvider)?.loadAsset() + } + } + + @Test(expected = FileNotFoundException::class) + fun failToFindThrowsFileNotFound() { + val assetsProvider1 = mock(AssetsProvider::class.java).apply { + doReturn(null).`when`(this).loadAsset() + } + val assetsProvider2 = mock(AssetsProvider::class.java).apply { + doReturn(null).`when`(this).loadAsset() + } + + val loader = ResourcesLoader() + val one = ResourcesProvider.empty(assetsProvider1) + val two = ResourcesProvider.empty(assetsProvider2) + resources.addLoader(loader) + loader.providers = listOf(one, two) + + assertOpenedAsset() + } + + @Test + fun throwingIOExceptionIsSkipped() { + val assetsProvider1 = DirectoryAssetsProvider(testDir) + val assetsProvider2 = mock(AssetsProvider::class.java).apply { + doAnswer { throw IOException() }.`when`(this).loadAsset() + } + + val loader = ResourcesLoader() + val one = ResourcesProvider.empty(assetsProvider1) + val two = ResourcesProvider.empty(assetsProvider2) + resources.addLoader(loader) + loader.providers = listOf(one, two) + + assertOpenedAsset() + } + + @Test(expected = IllegalStateException::class) + fun throwingNonIOExceptionCausesFailure() { + val assetsProvider1 = DirectoryAssetsProvider(testDir) + val assetsProvider2 = mock(AssetsProvider::class.java).apply { + doAnswer { throw IllegalStateException() }.`when`(this).loadAsset() + } + + val loader = ResourcesLoader() + val one = ResourcesProvider.empty(assetsProvider1) + val two = ResourcesProvider.empty(assetsProvider2) + resources.addLoader(loader) + loader.providers = listOf(one, two) + + assertOpenedAsset() + } + + private fun mockProvider(block: (AssetsProvider) -> Unit = {}): ResourcesProvider { + return ResourcesProvider.empty(mock(AssetsProvider::class.java).apply { + block.invoke(this) + }) + } + + private fun assertOpenedAsset() { + assertThat(resources.assets.openAsset()).isEqualTo(TEST_TEXT) + } +} diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderChangesTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderChangesTest.kt deleted file mode 100644 index 0c3d34e686dd..000000000000 --- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderChangesTest.kt +++ /dev/null @@ -1,241 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package android.content.res.loader.test - -import android.app.Activity -import android.app.Instrumentation -import android.app.UiAutomation -import android.content.res.Configuration -import android.content.res.Resources -import android.graphics.Color -import android.os.Bundle -import android.os.ParcelFileDescriptor -import android.widget.FrameLayout -import androidx.test.InstrumentationRegistry -import androidx.test.rule.ActivityTestRule -import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry -import androidx.test.runner.lifecycle.Stage -import com.google.common.truth.Truth.assertThat -import org.junit.After -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.Parameterized -import java.util.Arrays -import java.util.concurrent.CountDownLatch -import java.util.concurrent.Executor -import java.util.concurrent.FutureTask -import java.util.concurrent.TimeUnit - -@RunWith(Parameterized::class) -class ResourceLoaderChangesTest : ResourceLoaderTestBase() { - - companion object { - private const val TIMEOUT = 30L - private const val OVERLAY_PACKAGE = "android.content.res.loader.test.overlay" - - @JvmStatic - @Parameterized.Parameters(name = "{0}") - fun data() = arrayOf(DataType.APK, DataType.ARSC) - } - - @field:Parameterized.Parameter(0) - override lateinit var dataType: DataType - - @get:Rule - val activityRule: ActivityTestRule<TestActivity> = - ActivityTestRule<TestActivity>(TestActivity::class.java, false, true) - - // Redirect to the Activity's resources - override val resources: Resources - get() = activityRule.getActivity().resources - - private val activity: TestActivity - get() = activityRule.getActivity() - - private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation() - - @Before - @After - fun disableOverlay() { - enableOverlay(OVERLAY_PACKAGE, false) - } - - @Test - fun activityRecreate() = verifySameBeforeAndAfter { - val oldActivity = activity - var newActivity: Activity? = null - instrumentation.runOnMainSync { oldActivity.recreate() } - instrumentation.waitForIdleSync() - instrumentation.runOnMainSync { - newActivity = ActivityLifecycleMonitorRegistry.getInstance() - .getActivitiesInStage(Stage.RESUMED) - .single() - } - - assertThat(newActivity).isNotNull() - assertThat(newActivity).isNotSameAs(oldActivity) - - // Return the new resources to assert on - return@verifySameBeforeAndAfter newActivity!!.resources - } - - @Test - fun activityHandledOrientationChange() = verifySameBeforeAndAfter { - val latch = CountDownLatch(1) - val oldConfig = Configuration().apply { setTo(resources.configuration) } - var changedConfig: Configuration? = null - - activity.callback = object : TestActivity.Callback { - override fun onConfigurationChanged(newConfig: Configuration) { - changedConfig = newConfig - latch.countDown() - } - } - - val isPortrait = resources.displayMetrics.run { widthPixels < heightPixels } - val newRotation = if (isPortrait) { - UiAutomation.ROTATION_FREEZE_90 - } else { - UiAutomation.ROTATION_FREEZE_0 - } - - instrumentation.uiAutomation.setRotation(newRotation) - - assertThat(latch.await(TIMEOUT, TimeUnit.SECONDS)).isTrue() - assertThat(changedConfig).isNotEqualTo(oldConfig) - return@verifySameBeforeAndAfter activity.resources - } - - @Test - fun enableOverlayCausingPathChange() = verifySameBeforeAndAfter { - assertThat(getString(R.string.loader_path_change_test)).isEqualTo("Not overlaid") - - enableOverlay(OVERLAY_PACKAGE, true) - - assertThat(getString(R.string.loader_path_change_test)).isEqualTo("Overlaid") - - return@verifySameBeforeAndAfter activity.resources - } - - @Test - fun enableOverlayChildContextUnaffected() { - val childContext = activity.createConfigurationContext(Configuration()) - val childResources = childContext.resources - val originalValue = childResources.getString(android.R.string.cancel) - assertThat(childResources.getString(R.string.loader_path_change_test)) - .isEqualTo("Not overlaid") - - verifySameBeforeAndAfter { - enableOverlay(OVERLAY_PACKAGE, true) - return@verifySameBeforeAndAfter activity.resources - } - - // Loader not applied, but overlay change propagated - assertThat(childResources.getString(android.R.string.cancel)).isEqualTo(originalValue) - assertThat(childResources.getString(R.string.loader_path_change_test)) - .isEqualTo("Overlaid") - } - - // All these tests assert for the exact same loaders/values, so extract that logic out - private fun verifySameBeforeAndAfter(block: () -> Resources) { - fun Resources.resource() = this.getString(android.R.string.cancel) - fun Resources.asset() = this.assets.open("Asset.txt").reader().readText() - - val originalResource = resources.resource() - val originalAsset = resources.asset() - - val loaderResource = "stringOne".openLoader() - val loaderAsset = "assetOne".openLoader(dataType = DataType.ASSET) - addLoader(loaderResource) - addLoader(loaderAsset) - - val oldLoaders = resources.loaders - val oldResource = resources.resource() - val oldAsset = resources.asset() - - assertThat(oldResource).isNotEqualTo(originalResource) - assertThat(oldAsset).isNotEqualTo(originalAsset) - - val newResources = block() - - val newLoaders = newResources.loaders - val newResource = newResources.resource() - val newAsset = newResources.asset() - - assertThat(newResource).isEqualTo(oldResource) - assertThat(newAsset).isEqualTo(oldAsset) - assertThat(newLoaders).isEqualTo(oldLoaders) - } - - // Copied from overlaytests LocalOverlayManager - private fun enableOverlay(packageName: String, enable: Boolean) { - val executor = Executor { Thread(it).start() } - val pattern = (if (enable) "[x]" else "[ ]") + " " + packageName - if (executeShellCommand("cmd overlay list").contains(pattern)) { - // nothing to do, overlay already in the requested state - return - } - - val oldApkPaths = resources.assets.apkPaths - val task = FutureTask { - while (true) { - if (!Arrays.equals(oldApkPaths, resources.assets.apkPaths)) { - return@FutureTask true - } - Thread.sleep(10) - } - - @Suppress("UNREACHABLE_CODE") - return@FutureTask false - } - - val command = if (enable) "enable" else "disable" - executeShellCommand("cmd overlay $command $packageName") - executor.execute(task) - assertThat(task.get(TIMEOUT, TimeUnit.SECONDS)).isTrue() - } - - private fun executeShellCommand(command: String): String { - val uiAutomation = instrumentation.uiAutomation - val pfd = uiAutomation.executeShellCommand(command) - return ParcelFileDescriptor.AutoCloseInputStream(pfd).use { it.reader().readText() } - } -} - -class TestActivity : Activity() { - - var callback: Callback? = null - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - - setContentView(FrameLayout(this).apply { - setBackgroundColor(Color.BLUE) - }) - } - - override fun onConfigurationChanged(newConfig: Configuration) { - super.onConfigurationChanged(newConfig) - callback?.onConfigurationChanged(newConfig) - } - - interface Callback { - fun onConfigurationChanged(newConfig: Configuration) - } -} diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderDrawableTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderDrawableTest.kt deleted file mode 100644 index 09fd27e02b59..000000000000 --- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderDrawableTest.kt +++ /dev/null @@ -1,183 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package android.content.res.loader.test - -import android.content.res.Resources -import android.content.res.loader.ResourceLoader -import android.content.res.loader.ResourcesProvider -import android.graphics.Color -import android.graphics.drawable.ColorDrawable -import com.google.common.truth.Truth.assertThat -import org.hamcrest.CoreMatchers.not -import org.junit.Assume.assumeThat -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.Parameterized -import org.mockito.ArgumentMatchers.anyInt -import org.mockito.Mockito.`when` -import org.mockito.Mockito.any -import org.mockito.Mockito.argThat -import org.mockito.Mockito.eq -import org.mockito.Mockito.never -import org.mockito.Mockito.verify - -@RunWith(Parameterized::class) -class ResourceLoaderDrawableTest : ResourceLoaderTestBase() { - - companion object { - - @JvmStatic - @Parameterized.Parameters(name = "{0}") - fun data() = arrayOf(DataType.APK, DataType.ARSC) - } - - @field:Parameterized.Parameter(0) - override lateinit var dataType: DataType - - @Test - fun matchingConfig() { - val original = getDrawable(android.R.drawable.ic_delete) - val loader = "drawableMdpiWithoutFile".openLoader() - `when`(loader.first.loadDrawable(any(), anyInt(), anyInt(), any())) - .thenReturn(ColorDrawable(Color.BLUE)) - - addLoader(loader) - - updateConfiguration { densityDpi = 160 /* mdpi */ } - - val drawable = getDrawable(android.R.drawable.ic_delete) - - loader.verifyLoadDrawableCalled() - - assertThat(drawable).isNotEqualTo(original) - assertThat(drawable).isInstanceOf(ColorDrawable::class.java) - assertThat((drawable as ColorDrawable).color).isEqualTo(Color.BLUE) - } - - @Test - fun worseConfig() { - val loader = "drawableMdpiWithoutFile".openLoader() - addLoader(loader) - - updateConfiguration { densityDpi = 480 /* xhdpi */ } - - getDrawable(android.R.drawable.ic_delete) - - verify(loader.first, never()).loadDrawable(any(), anyInt(), anyInt(), any()) - } - - @Test - fun multipleLoaders() { - val original = getDrawable(android.R.drawable.ic_delete) - val loaderOne = "drawableMdpiWithoutFile".openLoader() - val loaderTwo = "drawableMdpiWithoutFile".openLoader() - - `when`(loaderTwo.first.loadDrawable(any(), anyInt(), anyInt(), any())) - .thenReturn(ColorDrawable(Color.BLUE)) - - addLoader(loaderOne, loaderTwo) - - updateConfiguration { densityDpi = 160 /* mdpi */ } - - val drawable = getDrawable(android.R.drawable.ic_delete) - loaderOne.verifyLoadDrawableNotCalled() - loaderTwo.verifyLoadDrawableCalled() - - assertThat(drawable).isNotEqualTo(original) - assertThat(drawable).isInstanceOf(ColorDrawable::class.java) - assertThat((drawable as ColorDrawable).color).isEqualTo(Color.BLUE) - } - - @Test(expected = Resources.NotFoundException::class) - fun multipleLoadersNoReturnWithoutFile() { - val loaderOne = "drawableMdpiWithoutFile".openLoader() - val loaderTwo = "drawableMdpiWithoutFile".openLoader() - - addLoader(loaderOne, loaderTwo) - - updateConfiguration { densityDpi = 160 /* mdpi */ } - - try { - getDrawable(android.R.drawable.ic_delete) - } finally { - // We expect the call to fail because at least the loader won't resolve the overridden - // drawable, but we should still verify that both loaders were called before allowing - // the exception to propagate. - loaderOne.verifyLoadDrawableNotCalled() - loaderTwo.verifyLoadDrawableCalled() - } - } - - @Test - fun multipleLoadersReturnWithFile() { - // Can't return a file if an ARSC - assumeThat(dataType, not(DataType.ARSC)) - - val original = getDrawable(android.R.drawable.ic_delete) - val loaderOne = "drawableMdpiWithFile".openLoader() - val loaderTwo = "drawableMdpiWithFile".openLoader() - - addLoader(loaderOne, loaderTwo) - - updateConfiguration { densityDpi = 160 /* mdpi */ } - - val drawable = getDrawable(android.R.drawable.ic_delete) - loaderOne.verifyLoadDrawableNotCalled() - loaderTwo.verifyLoadDrawableCalled() - - assertThat(drawable).isNotNull() - assertThat(drawable).isInstanceOf(original.javaClass) - } - - @Test - fun unhandledResourceIgnoresLoaders() { - val loader = "drawableMdpiWithoutFile".openLoader() - `when`(loader.first.loadDrawable(any(), anyInt(), anyInt(), any())) - .thenReturn(ColorDrawable(Color.BLUE)) - addLoader(loader) - - getDrawable(android.R.drawable.ic_menu_add) - - loader.verifyLoadDrawableNotCalled() - - getDrawable(android.R.drawable.ic_delete) - - loader.verifyLoadDrawableCalled() - } - - private fun Pair<ResourceLoader, ResourcesProvider>.verifyLoadDrawableCalled() { - verify(first).loadDrawable( - argThat { - it.density == 160 && - it.resourceId == android.R.drawable.ic_delete && - it.string == "res/drawable-mdpi-v4/ic_delete.png" - }, - eq(android.R.drawable.ic_delete), - eq(0), - any() - ) - } - - private fun Pair<ResourceLoader, ResourcesProvider>.verifyLoadDrawableNotCalled() { - verify(first, never()).loadDrawable( - any(), - anyInt(), - anyInt(), - any() - ) - } -} diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderLayoutTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderLayoutTest.kt deleted file mode 100644 index 1ec209486c18..000000000000 --- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderLayoutTest.kt +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License - */ - -package android.content.res.loader.test - -import android.content.res.Resources -import android.content.res.XmlResourceParser -import android.content.res.loader.ResourceLoader -import android.content.res.loader.ResourcesProvider -import com.google.common.truth.Truth.assertThat -import org.hamcrest.CoreMatchers.not -import org.junit.Assume.assumeThat -import org.junit.Test -import org.junit.runner.RunWith -import org.junit.runners.Parameterized -import org.mockito.ArgumentMatchers.anyString -import org.mockito.Mockito.`when` -import org.mockito.Mockito.any -import org.mockito.Mockito.anyInt -import org.mockito.Mockito.mock -import org.mockito.Mockito.never -import org.mockito.Mockito.verify - -@RunWith(Parameterized::class) -class ResourceLoaderLayoutTest : ResourceLoaderTestBase() { - - companion object { - - @JvmStatic - @Parameterized.Parameters(name = "{0}") - fun data() = arrayOf(DataType.APK, DataType.ARSC) - } - - @field:Parameterized.Parameter(0) - override lateinit var dataType: DataType - - @Test - fun singleLoader() { - val original = getLayout(android.R.layout.activity_list_item) - val mockXml = mock(XmlResourceParser::class.java) - val loader = "layoutWithoutFile".openLoader() - `when`(loader.first.loadXmlResourceParser(any(), anyInt())) - .thenReturn(mockXml) - - addLoader(loader) - - val layout = getLayout(android.R.layout.activity_list_item) - loader.verifyLoadLayoutCalled() - - assertThat(layout).isNotEqualTo(original) - assertThat(layout).isSameAs(mockXml) - } - - @Test - fun multipleLoaders() { - val original = getLayout(android.R.layout.activity_list_item) - val loaderOne = "layoutWithoutFile".openLoader() - val loaderTwo = "layoutWithoutFile".openLoader() - - val mockXml = mock(XmlResourceParser::class.java) - `when`(loaderTwo.first.loadXmlResourceParser(any(), anyInt())) - .thenReturn(mockXml) - - addLoader(loaderOne, loaderTwo) - - val layout = getLayout(android.R.layout.activity_list_item) - loaderOne.verifyLoadLayoutNotCalled() - loaderTwo.verifyLoadLayoutCalled() - - assertThat(layout).isNotEqualTo(original) - assertThat(layout).isSameAs(mockXml) - } - - @Test(expected = Resources.NotFoundException::class) - fun multipleLoadersNoReturnWithoutFile() { - val loaderOne = "layoutWithoutFile".openLoader() - val loaderTwo = "layoutWithoutFile".openLoader() - - addLoader(loaderOne, loaderTwo) - - try { - getLayout(android.R.layout.activity_list_item) - } finally { - // We expect the call to fail because at least one loader must resolve the overridden - // layout, but we should still verify that both loaders were called before allowing - // the exception to propagate. - loaderOne.verifyLoadLayoutNotCalled() - loaderTwo.verifyLoadLayoutCalled() - } - } - - @Test - fun multipleLoadersReturnWithFile() { - // Can't return a file if an ARSC - assumeThat(dataType, not(DataType.ARSC)) - - val loaderOne = "layoutWithFile".openLoader() - val loaderTwo = "layoutWithFile".openLoader() - - addLoader(loaderOne, loaderTwo) - - val xml = getLayout(android.R.layout.activity_list_item) - loaderOne.verifyLoadLayoutNotCalled() - loaderTwo.verifyLoadLayoutCalled() - - assertThat(xml).isNotNull() - } - - @Test - fun unhandledResourceIgnoresLoaders() { - val loader = "layoutWithoutFile".openLoader() - val mockXml = mock(XmlResourceParser::class.java) - `when`(loader.first.loadXmlResourceParser(any(), anyInt())) - .thenReturn(mockXml) - addLoader(loader) - - getLayout(android.R.layout.preference_category) - - verify(loader.first, never()) - .loadXmlResourceParser(anyString(), anyInt()) - - getLayout(android.R.layout.activity_list_item) - - loader.verifyLoadLayoutCalled() - } - - private fun Pair<ResourceLoader, ResourcesProvider>.verifyLoadLayoutCalled() { - verify(first).loadXmlResourceParser( - "res/layout/activity_list_item.xml", - android.R.layout.activity_list_item - ) - } - - private fun Pair<ResourceLoader, ResourcesProvider>.verifyLoadLayoutNotCalled() { - verify(first, never()).loadXmlResourceParser( - anyString(), - anyInt() - ) - } -} diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderTestBase.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderTestBase.kt index 5af453d526e4..4c62955e41a5 100644 --- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderTestBase.kt +++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderTestBase.kt @@ -18,25 +18,16 @@ package android.content.res.loader.test import android.content.Context import android.content.res.AssetManager -import android.content.res.Configuration import android.content.res.Resources -import android.content.res.loader.ResourceLoader +import android.content.res.loader.AssetsProvider import android.content.res.loader.ResourcesProvider import android.os.ParcelFileDescriptor -import android.util.TypedValue -import androidx.annotation.DimenRes -import androidx.annotation.DrawableRes -import androidx.annotation.LayoutRes -import androidx.annotation.StringRes import androidx.test.InstrumentationRegistry import org.junit.After import org.junit.Before -import org.mockito.ArgumentMatchers import org.mockito.ArgumentMatchers.anyInt -import org.mockito.ArgumentMatchers.argThat import org.mockito.ArgumentMatchers.eq import org.mockito.Mockito.doAnswer -import org.mockito.Mockito.doReturn import org.mockito.Mockito.mock import java.io.Closeable @@ -60,7 +51,8 @@ abstract class ResourceLoaderTestBase { @After fun removeAllLoaders() { - resources.setLoaders(null) + resources.clearLoaders() + context.applicationContext.resources.clearLoaders() openedObjects.forEach { try { it.close() @@ -69,149 +61,73 @@ abstract class ResourceLoaderTestBase { } } - protected fun getString(@StringRes stringRes: Int, debugLog: Boolean = false) = - logResolution(debugLog) { getString(stringRes) } - - protected fun getDrawable(@DrawableRes drawableRes: Int, debugLog: Boolean = false) = - logResolution(debugLog) { getDrawable(drawableRes) } - - protected fun getLayout(@LayoutRes layoutRes: Int, debugLog: Boolean = false) = - logResolution(debugLog) { getLayout(layoutRes) } - - protected fun getDimensionPixelSize(@DimenRes dimenRes: Int, debugLog: Boolean = false) = - logResolution(debugLog) { getDimensionPixelSize(dimenRes) } - - private fun <T> logResolution(debugLog: Boolean = false, block: Resources.() -> T): T { - if (debugLog) { - resources.assets.setResourceResolutionLoggingEnabled(true) - } - - var thrown = false - - try { - return resources.block() - } catch (t: Throwable) { - // No good way to log to test output other than throwing an exception - if (debugLog) { - thrown = true - throw IllegalStateException(resources.assets.lastResourceResolution, t) - } else { - throw t - } - } finally { - if (!thrown && debugLog) { - throw IllegalStateException(resources.assets.lastResourceResolution) - } - } - } - - protected fun updateConfiguration(block: Configuration.() -> Unit) { - val configuration = Configuration().apply { - setTo(resources.configuration) - block() - } - - resources.updateConfiguration(configuration, resources.displayMetrics) - } - - protected fun String.openLoader( + protected fun String.openProvider( dataType: DataType = this@ResourceLoaderTestBase.dataType - ): Pair<ResourceLoader, ResourcesProvider> = when (dataType) { + ): ResourcesProvider = when (dataType) { DataType.APK -> { - mock(ResourceLoader::class.java) to context.copiedRawFile("${this}Apk").use { - ResourcesProvider.loadFromApk(it) + context.copiedRawFile("${this}Apk").use { + ResourcesProvider.loadFromApk(it, mock(AssetsProvider::class.java)) }.also { openedObjects += it } } DataType.ARSC -> { - mock(ResourceLoader::class.java) to openArsc(this) + openArsc(this, mock(AssetsProvider::class.java)) } DataType.SPLIT -> { - mock(ResourceLoader::class.java) to ResourcesProvider.loadFromSplit(context, this) + ResourcesProvider.loadFromSplit(context, this) } - DataType.ASSET -> mockLoader { - doAnswer { byteInputStream() }.`when`(it) + DataType.ASSET -> { + val assetsProvider = mock(AssetsProvider::class.java) + doAnswer { byteInputStream() }.`when`(assetsProvider) .loadAsset(eq("assets/Asset.txt"), anyInt()) + ResourcesProvider.empty(assetsProvider) } - DataType.ASSET_FD -> mockLoader { + DataType.ASSET_FD -> { + val assetsProvider = mock(AssetsProvider::class.java) doAnswer { val file = context.filesDir.resolve("Asset.txt") file.writeText(this) ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY) - }.`when`(it).loadAssetFd("assets/Asset.txt") + }.`when`(assetsProvider).loadAssetParcelFd("assets/Asset.txt") + ResourcesProvider.empty(assetsProvider) } - DataType.NON_ASSET -> mockLoader { + DataType.NON_ASSET -> { + val assetsProvider = mock(AssetsProvider::class.java) doAnswer { val file = context.filesDir.resolve("NonAsset.txt") file.writeText(this) ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY) - }.`when`(it).loadAssetFd("NonAsset.txt") + }.`when`(assetsProvider).loadAssetParcelFd("NonAsset.txt") + ResourcesProvider.empty(assetsProvider) } - DataType.NON_ASSET_DRAWABLE -> mockLoader(openArsc(this)) { - doReturn(null).`when`(it).loadDrawable(argThat { value -> - value.type == TypedValue.TYPE_STRING && - value.resourceId == 0x7f010001 && - value.string == "res/drawable-nodpi-v4/non_asset_drawable.xml" - }, eq(0x7f010001), anyInt(), ArgumentMatchers.any()) - - doAnswer { context.copiedRawFile(this) }.`when`(it) - .loadAssetFd("res/drawable-nodpi-v4/non_asset_drawable.xml") + DataType.NON_ASSET_DRAWABLE -> { + val assetsProvider = mock(AssetsProvider::class.java) + doAnswer { context.copiedRawFile(this) }.`when`(assetsProvider) + .loadAssetParcelFd("res/drawable-nodpi-v4/non_asset_drawable.xml") + openArsc(this, assetsProvider) } - DataType.NON_ASSET_BITMAP -> mockLoader(openArsc(this)) { - doReturn(null).`when`(it).loadDrawable(argThat { value -> - value.type == TypedValue.TYPE_STRING && - value.resourceId == 0x7f010000 && - value.string == "res/drawable-nodpi-v4/non_asset_bitmap.png" - }, eq(0x7f010000), anyInt(), ArgumentMatchers.any()) - - doAnswer { resources.openRawResourceFd(rawFile(this)).createInputStream() } - .`when`(it) + DataType.NON_ASSET_BITMAP -> { + val assetsProvider = mock(AssetsProvider::class.java) + doAnswer { resources.openRawResource(rawFile(this)) } + .`when`(assetsProvider) .loadAsset(eq("res/drawable-nodpi-v4/non_asset_bitmap.png"), anyInt()) + openArsc(this, assetsProvider) } - DataType.NON_ASSET_LAYOUT -> mockLoader(openArsc(this)) { - doReturn(null).`when`(it) - .loadXmlResourceParser("res/layout/layout.xml", 0x7f020000) - - doAnswer { context.copiedRawFile(this) }.`when`(it) - .loadAssetFd("res/layout/layout.xml") + DataType.NON_ASSET_LAYOUT -> { + val assetsProvider = mock(AssetsProvider::class.java) + doAnswer { resources.openRawResource(rawFile(this)) }.`when`(assetsProvider) + .loadAsset(eq("res/layout/layout.xml"), anyInt()) + doAnswer { context.copiedRawFile(this) }.`when`(assetsProvider) + .loadAssetParcelFd("res/layout/layout.xml") + openArsc(this, assetsProvider) } } - protected fun mockLoader( - provider: ResourcesProvider = ResourcesProvider.empty(), - block: (ResourceLoader) -> Unit = {} - ): Pair<ResourceLoader, ResourcesProvider> { - return mock(ResourceLoader::class.java, Utils.ANSWER_THROWS) - .apply(block) to provider - } - - protected fun openArsc(rawName: String): ResourcesProvider { + protected fun openArsc(rawName: String, assetsProvider: AssetsProvider): ResourcesProvider { return context.copiedRawFile("${rawName}Arsc") - .use { ResourcesProvider.loadFromArsc(it) } + .use { ResourcesProvider.loadFromTable(it, assetsProvider) } .also { openedObjects += it } } - // This specifically uses addLoader so both behaviors are tested - protected fun addLoader(vararg pairs: Pair<out ResourceLoader, ResourcesProvider>) { - pairs.forEach { resources.addLoader(it.first, it.second) } - } - - protected fun setLoaders(vararg pairs: Pair<out ResourceLoader, ResourcesProvider>) { - resources.setLoaders(pairs.map { android.util.Pair(it.first, it.second) }) - } - - protected fun addLoader(pair: Pair<out ResourceLoader, ResourcesProvider>, index: Int) { - resources.addLoader(pair.first, pair.second, index) - } - - protected fun removeLoader(vararg pairs: Pair<out ResourceLoader, ResourcesProvider>) { - pairs.forEach { resources.removeLoader(it.first) } - } - - protected fun getLoaders(): MutableList<Pair<ResourceLoader, ResourcesProvider>> { - // Cast instead of toMutableList to maintain the same object - return resources.getLoaders() as MutableList<Pair<ResourceLoader, ResourcesProvider>> - } - enum class DataType { APK, ARSC, diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt index 017552a02152..0cc56d721651 100644 --- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt +++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/ResourceLoaderValuesTest.kt @@ -16,15 +16,24 @@ package android.content.res.loader.test +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.content.res.Configuration +import android.content.res.Resources +import android.content.res.loader.ResourcesLoader import android.graphics.Color import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.ColorDrawable -import com.google.common.truth.Truth.assertThat +import android.os.IBinder +import androidx.test.rule.ActivityTestRule import org.junit.Assert.assertEquals import org.junit.Assert.assertNotEquals +import org.junit.Rule import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized +import java.util.Collections /** * Tests generic ResourceLoader behavior. Intentionally abstract in its test methodology because @@ -36,6 +45,9 @@ import org.junit.runners.Parameterized @RunWith(Parameterized::class) class ResourceLoaderValuesTest : ResourceLoaderTestBase() { + @get:Rule + private val mTestActivityRule = ActivityTestRule<TestActivity>(TestActivity::class.java) + companion object { @Parameterized.Parameters(name = "{1} {0}") @JvmStatic @@ -47,14 +59,18 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() { { getString(android.R.string.cancel) }, "stringOne", { "SomeRidiculouslyUnlikelyStringOne" }, "stringTwo", { "SomeRidiculouslyUnlikelyStringTwo" }, + "stringThree", { "SomeRidiculouslyUnlikelyStringThree" }, + "stringFour", { "SomeRidiculouslyUnlikelyStringFour" }, listOf(DataType.APK, DataType.ARSC) ) // R.dimen parameters += Parameter( - { resources.getDimensionPixelSize(android.R.dimen.app_icon_size) }, - "dimenOne", { 564716.dpToPx(resources) }, - "dimenTwo", { 565717.dpToPx(resources) }, + { getDimensionPixelSize(android.R.dimen.app_icon_size) }, + "dimenOne", { 100.dpToPx(resources) }, + "dimenTwo", { 200.dpToPx(resources) }, + "dimenThree", { 300.dpToPx(resources) }, + "dimenFour", { 400.dpToPx(resources) }, listOf(DataType.APK, DataType.ARSC) ) @@ -63,6 +79,8 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() { { assets.open("Asset.txt").reader().readText() }, "assetOne", { "assetOne" }, "assetTwo", { "assetTwo" }, + "assetFour", { "assetFour" }, + "assetThree", { "assetThree" }, listOf(DataType.ASSET) ) @@ -71,6 +89,8 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() { { assets.openFd("Asset.txt").readText() }, "assetOne", { "assetOne" }, "assetTwo", { "assetTwo" }, + "assetFour", { "assetFour" }, + "assetThree", { "assetThree" }, listOf(DataType.ASSET_FD) ) @@ -79,14 +99,18 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() { { assets.openNonAssetFd("NonAsset.txt").readText() }, "NonAssetOne", { "NonAssetOne" }, "NonAssetTwo", { "NonAssetTwo" }, + "NonAssetThree", { "NonAssetThree" }, + "NonAssetFour", { "NonAssetFour" }, listOf(DataType.NON_ASSET) ) // Asset as compiled XML drawable parameters += Parameter( { (getDrawable(R.drawable.non_asset_drawable) as ColorDrawable).color }, - "nonAssetDrawableOne", { Color.parseColor("#A3C3E3") }, - "nonAssetDrawableTwo", { Color.parseColor("#3A3C3E") }, + "nonAssetDrawableOne", { Color.parseColor("#000001") }, + "nonAssetDrawableTwo", { Color.parseColor("#000002") }, + "nonAssetDrawableThree", { Color.parseColor("#000003") }, + "nonAssetDrawableFour", { Color.parseColor("#000004") }, listOf(DataType.NON_ASSET_DRAWABLE) ) @@ -96,8 +120,10 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() { (getDrawable(R.drawable.non_asset_bitmap) as BitmapDrawable) .bitmap.getColor(0, 0).toArgb() }, + "nonAssetBitmapRed", { Color.RED }, "nonAssetBitmapGreen", { Color.GREEN }, "nonAssetBitmapBlue", { Color.BLUE }, + "nonAssetBitmapWhite", { Color.WHITE }, listOf(DataType.NON_ASSET_BITMAP) ) @@ -106,6 +132,8 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() { { getLayout(R.layout.layout).advanceToRoot().name }, "layoutOne", { "RelativeLayout" }, "layoutTwo", { "LinearLayout" }, + "layoutThree", { "FrameLayout" }, + "layoutFour", { "TableLayout" }, listOf(DataType.NON_ASSET_LAYOUT) ) @@ -114,6 +142,8 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() { { getString(R.string.split_overlaid) }, "split_one", { "Split ONE Overlaid" }, "split_two", { "Split TWO Overlaid" }, + "split_three", { "Split THREE Overlaid" }, + "split_four", { "Split FOUR Overlaid" }, listOf(DataType.SPLIT) ) @@ -134,221 +164,482 @@ class ResourceLoaderValuesTest : ResourceLoaderTestBase() { private val valueOne by lazy { parameter.valueOne(this) } private val valueTwo by lazy { parameter.valueTwo(this) } + private val valueThree by lazy { parameter.valueThree(this) } + private val valueFour by lazy { parameter.valueFour(this) } - private fun openOne() = parameter.loaderOne.openLoader() - private fun openTwo() = parameter.loaderTwo.openLoader() + private fun openOne() = parameter.providerOne.openProvider() + private fun openTwo() = parameter.providerTwo.openProvider() + private fun openThree() = parameter.providerThree.openProvider() + private fun openFour() = parameter.providerFour.openProvider() // Class method for syntax highlighting purposes - private fun getValue() = parameter.getValue(this) + private fun getValue(c: Context = context) = parameter.getValue(c.resources) @Test - fun verifyValueUniqueness() { + fun assertValueUniqueness() { // Ensure the parameters are valid in case of coding errors - assertNotEquals(valueOne, getValue()) - assertNotEquals(valueTwo, getValue()) - assertNotEquals(valueOne, valueTwo) + val original = getValue() + assertNotEquals(valueOne, original) + assertNotEquals(valueTwo, original) + assertNotEquals(valueThree, original) + assertNotEquals(valueFour, original) + assertNotEquals(valueTwo, valueOne) + assertNotEquals(valueThree, valueOne) + assertNotEquals(valueFour, valueOne) + assertNotEquals(valueThree, valueTwo) + assertNotEquals(valueFour, valueTwo) + assertNotEquals(valueFour, valueThree) } @Test - fun addMultipleLoaders() { + fun addMultipleProviders() { val originalValue = getValue() val testOne = openOne() val testTwo = openTwo() + val loader = ResourcesLoader() - addLoader(testOne, testTwo) + resources.addLoader(loader) + loader.addProvider(testOne) + assertEquals(valueOne, getValue()) + loader.addProvider(testTwo) assertEquals(valueTwo, getValue()) - removeLoader(testTwo) - - assertEquals(valueOne, getValue()) - - removeLoader(testOne) + loader.removeProvider(testOne) + assertEquals(valueTwo, getValue()) + loader.removeProvider(testTwo) assertEquals(originalValue, getValue()) } @Test - fun setMultipleLoaders() { + fun addMultipleLoaders() { val originalValue = getValue() val testOne = openOne() val testTwo = openTwo() + val loader1 = ResourcesLoader() + val loader2 = ResourcesLoader() - setLoaders(testOne, testTwo) + resources.addLoader(loader1) + loader1.addProvider(testOne) + assertEquals(valueOne, getValue()) + resources.addLoader(loader2) + loader2.addProvider(testTwo) assertEquals(valueTwo, getValue()) - removeLoader(testTwo) - - assertEquals(valueOne, getValue()) - - setLoaders() + resources.removeLoader(loader1) + assertEquals(valueTwo, getValue()) + resources.removeLoader(loader2) assertEquals(originalValue, getValue()) } @Test - fun getLoadersContainsAll() { + fun setMultipleProviders() { + val originalValue = getValue() val testOne = openOne() val testTwo = openTwo() + val loader = ResourcesLoader() - addLoader(testOne, testTwo) + resources.addLoader(loader) + loader.providers = listOf(testOne, testTwo) + assertEquals(valueTwo, getValue()) - assertThat(getLoaders()).containsAllOf(testOne, testTwo) + loader.removeProvider(testTwo) + assertEquals(valueOne, getValue()) + + loader.providers = Collections.emptyList() + assertEquals(originalValue, getValue()) } @Test - fun getLoadersDoesNotLeakMutability() { + fun setMultipleLoaders() { val originalValue = getValue() - val testOne = openOne() - val testTwo = openTwo() - - addLoader(testOne) - - assertEquals(valueOne, getValue()) + val loader1 = ResourcesLoader() + loader1.addProvider(openOne()) + val loader2 = ResourcesLoader() + loader2.addProvider(openTwo()) - val loaders = getLoaders() - loaders += testTwo + resources.loaders = listOf(loader1, loader2) + assertEquals(valueTwo, getValue()) + resources.removeLoader(loader2) assertEquals(valueOne, getValue()) - removeLoader(testOne) - + resources.loaders = Collections.emptyList() assertEquals(originalValue, getValue()) } - @Test(expected = IllegalArgumentException::class) - fun alreadyAddedThrows() { + @Test(expected = UnsupportedOperationException::class) + fun getProvidersDoesNotLeakMutability() { val testOne = openOne() - val testTwo = openTwo() + val loader = ResourcesLoader() + val providers = loader.providers + providers += testOne + } - addLoader(testOne) - addLoader(testTwo) - addLoader(testOne) + @Test(expected = UnsupportedOperationException::class) + fun getLoadersDoesNotLeakMutability() { + val loaders = resources.loaders + loaders += ResourcesLoader() } - @Test(expected = IllegalArgumentException::class) - fun alreadyAddedAndSetThrows() { + @Test + fun alreadyAddedProviderNoOps() { val testOne = openOne() val testTwo = openTwo() + val loader = ResourcesLoader() + + resources.addLoader(loader) + loader.addProvider(testOne) + loader.addProvider(testTwo) + loader.addProvider(testOne) - addLoader(testOne) - addLoader(testTwo) - setLoaders(testTwo) + assertEquals(2, loader.providers.size) + assertEquals(loader.providers[0], testOne) + assertEquals(loader.providers[1], testTwo) } @Test - fun repeatedRemoveSucceeds() { - val originalValue = getValue() + fun alreadyAddedLoaderNoOps() { + val loader1 = ResourcesLoader() + loader1.addProvider(openOne()) + val loader2 = ResourcesLoader() + loader2.addProvider(openTwo()) + + resources.addLoader(loader1) + resources.addLoader(loader2) + resources.addLoader(loader1) + + assertEquals(2, resources.loaders.size) + assertEquals(resources.loaders[0], loader1) + assertEquals(resources.loaders[1], loader2) + } + + @Test + fun repeatedRemoveProviderNoOps() { val testOne = openOne() + val testTwo = openTwo() + val loader = ResourcesLoader() + + resources.addLoader(loader) + loader.addProvider(testOne) + loader.addProvider(testTwo) - addLoader(testOne) + loader.removeProvider(testOne) + loader.removeProvider(testOne) - assertNotEquals(originalValue, getValue()) + assertEquals(1, loader.providers.size) + assertEquals(loader.providers[0], testTwo) + } - removeLoader(testOne) + @Test + fun repeatedRemoveLoaderNoOps() { + val loader1 = ResourcesLoader() + loader1.addProvider(openOne()) + val loader2 = ResourcesLoader() + loader2.addProvider(openTwo()) + + resources.loaders = listOf(loader1, loader2) + resources.removeLoader(loader1) + resources.removeLoader(loader1) + + assertEquals(1, resources.loaders.size) + assertEquals(resources.loaders[0], loader2) + } - assertEquals(originalValue, getValue()) + @Test + fun repeatedSetProvider() { + val testOne = openOne() + val testTwo = openTwo() + val loader = ResourcesLoader() - removeLoader(testOne) + resources.addLoader(loader) + loader.providers = listOf(testOne, testTwo) + loader.providers = listOf(testOne, testTwo) - assertEquals(originalValue, getValue()) + assertEquals(2, loader.providers.size) + assertEquals(loader.providers[0], testOne) + assertEquals(loader.providers[1], testTwo) + } + + @Test + fun repeatedSetLoaders() { + val loader1 = ResourcesLoader() + loader1.addProvider(openOne()) + val loader2 = ResourcesLoader() + loader2.addProvider(openTwo()) + + resources.loaders = listOf(loader1, loader2) + resources.loaders = listOf(loader1, loader2) + + assertEquals(2, resources.loaders.size) + assertEquals(resources.loaders[0], loader1) + assertEquals(resources.loaders[1], loader2) } @Test - fun addToFront() { + fun reorderProviders() { + val originalValue = getValue() val testOne = openOne() val testTwo = openTwo() + val loader = ResourcesLoader() - addLoader(testOne) + resources.addLoader(loader) + loader.addProvider(testOne) + loader.addProvider(testTwo) + assertEquals(valueTwo, getValue()) - assertEquals(valueOne, getValue()) + loader.removeProvider(testOne) + assertEquals(valueTwo, getValue()) - addLoader(testTwo, 0) + loader.addProvider(testOne) + assertEquals(valueOne, getValue()) + loader.removeProvider(testTwo) assertEquals(valueOne, getValue()) - // Remove top loader, so previously added to front should now resolve - removeLoader(testOne) - assertEquals(valueTwo, getValue()) + loader.removeProvider(testOne) + assertEquals(originalValue, getValue()) } @Test - fun addToEnd() { + fun reorderLoaders() { + val originalValue = getValue() val testOne = openOne() val testTwo = openTwo() + val loader1 = ResourcesLoader() + loader1.addProvider(testOne) + val loader2 = ResourcesLoader() + loader2.addProvider(testTwo) - addLoader(testOne) + resources.addLoader(loader1) + resources.addLoader(loader2) + assertEquals(valueTwo, getValue()) + resources.removeLoader(loader1) + assertEquals(valueTwo, getValue()) + + resources.addLoader(loader1) assertEquals(valueOne, getValue()) - addLoader(testTwo, 1) + resources.removeLoader(loader2) + assertEquals(valueOne, getValue()) - assertEquals(valueTwo, getValue()) + resources.removeLoader(loader1) + assertEquals(originalValue, getValue()) } - @Test(expected = IndexOutOfBoundsException::class) - fun addPastEnd() { + @Test + fun reorderMultipleLoadersAndProviders() { val testOne = openOne() val testTwo = openTwo() + val testThree = openThree() + val testFour = openFour() - addLoader(testOne) + val loader1 = ResourcesLoader() + loader1.providers = listOf(testOne, testTwo) + val loader2 = ResourcesLoader() + loader2.providers = listOf(testThree, testFour) + + resources.loaders = listOf(loader1, loader2) + assertEquals(valueFour, getValue()) + + resources.loaders = listOf(loader2, loader1) + assertEquals(valueTwo, getValue()) + + loader1.removeProvider(testTwo) assertEquals(valueOne, getValue()) - addLoader(testTwo, 2) + loader1.removeProvider(testOne) + assertEquals(valueFour, getValue()) } - @Test(expected = IndexOutOfBoundsException::class) - fun addBeforeFront() { - val testOne = openOne() - val testTwo = openTwo() + private fun createContext(context: Context, id: Int): Context { + val overrideConfig = Configuration() + overrideConfig.orientation = Int.MAX_VALUE - id + return context.createConfigurationContext(overrideConfig) + } - addLoader(testOne) + @Test + fun copyContextLoaders() { + val originalValue = getValue() + val loader1 = ResourcesLoader() + loader1.addProvider(openOne()) + val loader2 = ResourcesLoader() + loader2.addProvider(openTwo()) + + resources.loaders = listOf(loader1) + assertEquals(valueOne, getValue()) + + // The child context should include the loaders of the original context. + val childContext = createContext(context, 0) + assertEquals(valueOne, getValue(childContext)) + // Changing the loaders of the child context should not affect the original context. + childContext.resources.loaders = listOf(loader1, loader2) assertEquals(valueOne, getValue()) + assertEquals(valueTwo, getValue(childContext)) - addLoader(testTwo, -1) + // Changing the loaders of the original context should not affect the child context. + resources.removeLoader(loader1) + assertEquals(originalValue, getValue()) + assertEquals(valueTwo, getValue(childContext)) + + // A new context created from the original after an update to the original's loaders should + // have the updated loaders. + val originalPrime = createContext(context, 2) + assertEquals(originalValue, getValue(originalPrime)) + + // A new context created from the child context after an update to the child's loaders + // should have the updated loaders. + val childPrime = createContext(childContext, 1) + assertEquals(valueTwo, getValue(childPrime)) } @Test - fun reorder() { + fun loaderUpdatesAffectContexts() { val originalValue = getValue() val testOne = openOne() val testTwo = openTwo() + val loader = ResourcesLoader() + + resources.addLoader(loader) + loader.addProvider(testOne) + assertEquals(valueOne, getValue()) - addLoader(testOne, testTwo) + val childContext = createContext(context, 0) + assertEquals(valueOne, getValue(childContext)) + // Adding a provider to a loader affects all contexts that use the loader. + loader.addProvider(testTwo) assertEquals(valueTwo, getValue()) + assertEquals(valueTwo, getValue(childContext)) - removeLoader(testOne) + // Changes to the loaders for a context do not affect providers. + resources.clearLoaders() + assertEquals(originalValue, getValue()) + assertEquals(valueTwo, getValue(childContext)) - assertEquals(valueTwo, getValue()) + val childContext2 = createContext(context, 1) + assertEquals(originalValue, getValue()) + assertEquals(originalValue, getValue(childContext2)) - addLoader(testOne) + childContext2.resources.addLoader(loader) + assertEquals(originalValue, getValue()) + assertEquals(valueTwo, getValue(childContext)) + assertEquals(valueTwo, getValue(childContext2)) + } - assertEquals(valueOne, getValue()) + @Test + fun appLoadersIncludedInActivityContexts() { + val loader = ResourcesLoader() + loader.addProvider(openOne()) + + val applicationContext = context.applicationContext + applicationContext.resources.addLoader(loader) + assertEquals(valueOne, getValue(applicationContext)) - removeLoader(testTwo) + val activity = mTestActivityRule.launchActivity(Intent()) + assertEquals(valueOne, getValue(activity)) + applicationContext.resources.clearLoaders() + } + + @Test + fun loadersApplicationInfoChanged() { + val loader1 = ResourcesLoader() + loader1.addProvider(openOne()) + val loader2 = ResourcesLoader() + loader2.addProvider(openTwo()) + + val applicationContext = context.applicationContext + applicationContext.resources.addLoader(loader1) + assertEquals(valueOne, getValue(applicationContext)) + + var token: IBinder? = null + val activity = mTestActivityRule.launchActivity(Intent()) + mTestActivityRule.runOnUiThread(Runnable { + token = activity.activityToken + val at = activity.activityThread + + // The activity should have the loaders from the application. + assertEquals(valueOne, getValue(applicationContext)) + assertEquals(valueOne, getValue(activity)) + + activity.resources.addLoader(loader2) + assertEquals(valueOne, getValue(applicationContext)) + assertEquals(valueTwo, getValue(activity)) + + // Relaunches the activity. + at.handleApplicationInfoChanged(activity.applicationInfo) + }) + + mTestActivityRule.runOnUiThread(Runnable { + val activityThread = activity.activityThread + val newActivity = activityThread.getActivity(token) + + // The loader added to the activity loaders should not be persisted. + assertEquals(valueOne, getValue(applicationContext)) + assertEquals(valueOne, getValue(newActivity)) + }) + + applicationContext.resources.clearLoaders() + } + + @Test + fun multipleLoadersHaveSameProviders() { + val provider1 = openOne() + val loader1 = ResourcesLoader() + loader1.addProvider(provider1) + val loader2 = ResourcesLoader() + loader2.addProvider(provider1) + loader2.addProvider(openTwo()) + + resources.loaders = listOf(loader1, loader2) + assertEquals(valueTwo, getValue()) + + resources.loaders = listOf(loader2, loader1) assertEquals(valueOne, getValue()) - removeLoader(testOne) + assertEquals(2, resources.assets.apkAssets.count { apkAssets -> apkAssets.isForLoader }) + } - assertEquals(originalValue, getValue()) + @Test(expected = IllegalStateException::class) + fun cannotUseClosedProvider() { + val provider = openOne() + provider.close() + val loader = ResourcesLoader() + loader.addProvider(provider) + } + + @Test(expected = IllegalStateException::class) + fun cannotCloseUsedProvider() { + val provider = openOne() + val loader = ResourcesLoader() + loader.addProvider(provider) + provider.close() } data class Parameter( - val getValue: ResourceLoaderValuesTest.() -> Any, - val loaderOne: String, + val getValue: Resources.() -> Any, + val providerOne: String, val valueOne: ResourceLoaderValuesTest.() -> Any, - val loaderTwo: String, + val providerTwo: String, val valueTwo: ResourceLoaderValuesTest.() -> Any, + val providerThree: String, + val valueThree: ResourceLoaderValuesTest.() -> Any, + val providerFour: String, + val valueFour: ResourceLoaderValuesTest.() -> Any, val dataTypes: List<DataType> ) { override fun toString(): String { - val prefix = loaderOne.commonPrefixWith(loaderTwo) - return "$prefix${loaderOne.removePrefix(prefix)}|${loaderTwo.removePrefix(prefix)}" + val prefix = providerOne.commonPrefixWith(providerTwo) + return "$prefix${providerOne.removePrefix(prefix)}|${providerTwo.removePrefix(prefix)}" } } } + +class TestActivity : Activity()
\ No newline at end of file diff --git a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/Utils.kt b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/Utils.kt index df2d09adf503..4e8ee5cf3c3b 100644 --- a/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/Utils.kt +++ b/core/tests/ResourceLoaderTests/src/android/content/res/loader/test/Utils.kt @@ -26,10 +26,6 @@ import org.mockito.stubbing.Answer import org.xmlpull.v1.XmlPullParser import java.io.File -// Enforce use of [android.util.Pair] instead of Kotlin's so it matches the ResourceLoader APIs -typealias Pair<F, S> = android.util.Pair<F, S> -infix fun <A, B> A.to(that: B): Pair<A, B> = Pair.create(this, that)!! - object Utils { val ANSWER_THROWS = Answer<Any> { when (val name = it.method.name) { diff --git a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java index a2dab996a8d7..df5c9d2df90b 100644 --- a/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java +++ b/core/tests/coretests/src/android/content/res/ResourcesManagerTest.java @@ -72,12 +72,12 @@ public class ResourcesManagerTest extends TestCase { public void testMultipleCallsWithIdenticalParametersCacheReference() { Resources resources = mResourcesManager.getResources( null, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null, - CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); + CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(resources); Resources newResources = mResourcesManager.getResources( null, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null, - CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); + CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(newResources); assertSame(resources, newResources); } @@ -86,14 +86,14 @@ public class ResourcesManagerTest extends TestCase { public void testMultipleCallsWithDifferentParametersReturnDifferentReferences() { Resources resources = mResourcesManager.getResources( null, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null, - CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); + CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(resources); Configuration overrideConfig = new Configuration(); overrideConfig.smallestScreenWidthDp = 200; Resources newResources = mResourcesManager.getResources( null, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, overrideConfig, - CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); + CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(newResources); assertNotSame(resources, newResources); } @@ -102,12 +102,13 @@ public class ResourcesManagerTest extends TestCase { public void testAddingASplitCreatesANewImpl() { Resources resources1 = mResourcesManager.getResources( null, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null, - CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); + CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(resources1); Resources resources2 = mResourcesManager.getResources( null, APP_ONE_RES_DIR, new String[] { APP_ONE_RES_SPLIT_DIR }, null, null, - Display.DEFAULT_DISPLAY, null, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); + Display.DEFAULT_DISPLAY, null, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO,null, + null); assertNotNull(resources2); assertNotSame(resources1, resources2); @@ -118,12 +119,12 @@ public class ResourcesManagerTest extends TestCase { public void testUpdateConfigurationUpdatesAllAssetManagers() { Resources resources1 = mResourcesManager.getResources( null, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null, - CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); + CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(resources1); Resources resources2 = mResourcesManager.getResources( null, APP_TWO_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null, - CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); + CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(resources2); Binder activity = new Binder(); @@ -131,7 +132,7 @@ public class ResourcesManagerTest extends TestCase { overrideConfig.orientation = Configuration.ORIENTATION_LANDSCAPE; Resources resources3 = mResourcesManager.getResources( activity, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, - overrideConfig, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); + overrideConfig, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(resources3); // No Resources object should be the same. @@ -164,13 +165,13 @@ public class ResourcesManagerTest extends TestCase { Binder activity1 = new Binder(); Resources resources1 = mResourcesManager.getResources( activity1, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null, - CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); + CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(resources1); Binder activity2 = new Binder(); Resources resources2 = mResourcesManager.getResources( activity2, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null, - CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); + CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(resources1); // The references themselves should be unique. @@ -185,7 +186,7 @@ public class ResourcesManagerTest extends TestCase { Binder activity1 = new Binder(); Resources resources1 = mResourcesManager.createBaseActivityResources( activity1, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null, - CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); + CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(resources1); Resources.Theme theme = resources1.newTheme(); @@ -218,7 +219,7 @@ public class ResourcesManagerTest extends TestCase { config1.densityDpi = 280; Resources resources1 = mResourcesManager.createBaseActivityResources( activity1, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, config1, - CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); + CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(resources1); // Create a Resources based on the Activity. @@ -226,7 +227,7 @@ public class ResourcesManagerTest extends TestCase { config2.screenLayout |= Configuration.SCREENLAYOUT_ROUND_YES; Resources resources2 = mResourcesManager.getResources( activity1, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, config2, - CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); + CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null, null); assertNotNull(resources2); assertNotSame(resources1, resources2); diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index d211cfd83549..c6ccd4af06c5 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -2758,7 +2758,8 @@ public class DisplayPolicy { mDisplayContent.getDisplayId(), null /* overrideConfig */, uiContext.getResources().getCompatibilityInfo(), - null /* classLoader */); + null /* classLoader */, + null /* loaders */); } @VisibleForTesting |