Merge changes from topic "res-loader"
* changes:
Refactor ResourcesLoader Tests
Refactor ResourcesLoader APIs
diff --git a/apct-tests/perftests/core/src/android/app/ResourcesManagerPerfTest.java b/apct-tests/perftests/core/src/android/app/ResourcesManagerPerfTest.java
index 2955d2c..050fecd 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 @@
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 6123e69..f4c0a17 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 @@
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 12d8b70..77dec70 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -12653,8 +12653,8 @@
public class Resources {
ctor @Deprecated public Resources(android.content.res.AssetManager, android.util.DisplayMetrics, android.content.res.Configuration);
- method public void addLoader(@NonNull android.content.res.loader.ResourceLoader, @NonNull android.content.res.loader.ResourcesProvider, @IntRange(from=0) int);
- method public int addLoader(@NonNull android.content.res.loader.ResourceLoader, @NonNull android.content.res.loader.ResourcesProvider);
+ method public void addLoader(@NonNull android.content.res.loader.ResourcesLoader);
+ method public void clearLoaders();
method public final void finishPreloading();
method public final void flushLayoutCache();
method @NonNull public android.content.res.XmlResourceParser getAnimation(@AnimRes @AnimatorRes int) throws android.content.res.Resources.NotFoundException;
@@ -12681,7 +12681,7 @@
method @NonNull public int[] getIntArray(@ArrayRes int) throws android.content.res.Resources.NotFoundException;
method public int getInteger(@IntegerRes int) throws android.content.res.Resources.NotFoundException;
method @NonNull public android.content.res.XmlResourceParser getLayout(@LayoutRes int) throws android.content.res.Resources.NotFoundException;
- method @NonNull public java.util.List<android.util.Pair<android.content.res.loader.ResourceLoader,android.content.res.loader.ResourcesProvider>> getLoaders();
+ method @NonNull public java.util.List<android.content.res.loader.ResourcesLoader> getLoaders();
method @Deprecated public android.graphics.Movie getMovie(@RawRes int) throws android.content.res.Resources.NotFoundException;
method @NonNull public String getQuantityString(@PluralsRes int, int, java.lang.Object...) throws android.content.res.Resources.NotFoundException;
method @NonNull public String getQuantityString(@PluralsRes int, int) throws android.content.res.Resources.NotFoundException;
@@ -12709,8 +12709,8 @@
method public android.content.res.AssetFileDescriptor openRawResourceFd(@RawRes int) throws android.content.res.Resources.NotFoundException;
method public void parseBundleExtra(String, android.util.AttributeSet, android.os.Bundle) throws org.xmlpull.v1.XmlPullParserException;
method public void parseBundleExtras(android.content.res.XmlResourceParser, android.os.Bundle) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
- method public int removeLoader(@NonNull android.content.res.loader.ResourceLoader);
- method public void setLoaders(@Nullable java.util.List<android.util.Pair<android.content.res.loader.ResourceLoader,android.content.res.loader.ResourcesProvider>>);
+ method public void removeLoader(@NonNull android.content.res.loader.ResourcesLoader);
+ method public void setLoaders(@NonNull java.util.List<android.content.res.loader.ResourcesLoader>);
method @Deprecated public void updateConfiguration(android.content.res.Configuration, android.util.DisplayMetrics);
field @AnyRes public static final int ID_NULL = 0; // 0x0
}
@@ -12780,27 +12780,37 @@
package android.content.res.loader {
- public class DirectoryResourceLoader implements android.content.res.loader.ResourceLoader {
- ctor public DirectoryResourceLoader(@NonNull java.io.File);
+ public interface AssetsProvider {
+ method @Nullable public default java.io.InputStream loadAsset(@NonNull String, int) throws java.io.IOException;
+ method @Nullable public default android.os.ParcelFileDescriptor loadAssetParcelFd(@NonNull String) throws java.io.IOException;
+ }
+
+ public class DirectoryAssetsProvider implements android.content.res.loader.AssetsProvider {
+ ctor public DirectoryAssetsProvider(@NonNull java.io.File);
method @Nullable public java.io.File findFile(@NonNull String);
method @NonNull public java.io.File getDirectory();
}
- public interface ResourceLoader {
- method @Nullable public default java.io.InputStream loadAsset(@NonNull String, int) throws java.io.IOException;
- method @Nullable public default android.os.ParcelFileDescriptor loadAssetFd(@NonNull String) throws java.io.IOException;
- method @Nullable public default android.graphics.drawable.Drawable loadDrawable(@NonNull android.util.TypedValue, int, int, @Nullable android.content.res.Resources.Theme);
- method @Nullable public default android.content.res.XmlResourceParser loadXmlResourceParser(@NonNull String, @AnyRes int);
+ public class ResourcesLoader {
+ ctor public ResourcesLoader();
+ method public void addProvider(@NonNull android.content.res.loader.ResourcesProvider);
+ method public void clearProviders();
+ method @NonNull public java.util.List<android.content.res.loader.ResourcesProvider> getProviders();
+ method public void removeProvider(@NonNull android.content.res.loader.ResourcesProvider);
+ method public void setProviders(@NonNull java.util.List<android.content.res.loader.ResourcesProvider>);
}
- public final class ResourcesProvider implements java.lang.AutoCloseable java.io.Closeable {
+ public class ResourcesProvider implements java.lang.AutoCloseable java.io.Closeable {
method public void close();
- method @NonNull public static android.content.res.loader.ResourcesProvider empty();
+ method @NonNull public static android.content.res.loader.ResourcesProvider empty(@NonNull android.content.res.loader.AssetsProvider);
+ method @Nullable public android.content.res.loader.AssetsProvider getAssetsProvider();
method @NonNull public static android.content.res.loader.ResourcesProvider loadFromApk(@NonNull android.os.ParcelFileDescriptor) throws java.io.IOException;
+ method @NonNull public static android.content.res.loader.ResourcesProvider loadFromApk(@NonNull android.os.ParcelFileDescriptor, @Nullable android.content.res.loader.AssetsProvider) throws java.io.IOException;
method @NonNull public static android.content.res.loader.ResourcesProvider loadFromApk(@NonNull android.os.SharedMemory) throws java.io.IOException;
- method @NonNull public static android.content.res.loader.ResourcesProvider loadFromArsc(@NonNull android.os.ParcelFileDescriptor) throws java.io.IOException;
- method @NonNull public static android.content.res.loader.ResourcesProvider loadFromArsc(@NonNull android.os.SharedMemory) throws java.io.IOException;
+ method @NonNull public static android.content.res.loader.ResourcesProvider loadFromApk(@NonNull android.os.SharedMemory, @Nullable android.content.res.loader.AssetsProvider) throws java.io.IOException;
method @NonNull public static android.content.res.loader.ResourcesProvider loadFromSplit(@NonNull android.content.Context, @NonNull String) throws java.io.IOException;
+ method @NonNull public static android.content.res.loader.ResourcesProvider loadFromTable(@NonNull android.os.ParcelFileDescriptor, @Nullable android.content.res.loader.AssetsProvider) throws java.io.IOException;
+ method @NonNull public static android.content.res.loader.ResourcesProvider loadFromTable(@NonNull android.os.SharedMemory, @Nullable android.content.res.loader.AssetsProvider) throws java.io.IOException;
}
}
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index d90e81f..c901d2a 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -2198,7 +2198,7 @@
Resources getTopLevelResources(String resDir, String[] splitResDirs, String[] overlayDirs,
String[] libDirs, int displayId, LoadedApk pkgInfo) {
return mResourcesManager.getResources(null, resDir, splitResDirs, overlayDirs, libDirs,
- displayId, null, pkgInfo.getCompatibilityInfo(), pkgInfo.getClassLoader());
+ displayId, null, pkgInfo.getCompatibilityInfo(), pkgInfo.getClassLoader(), null);
}
@UnsupportedAppUsage
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index b7555ee..136c84e 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -46,6 +46,7 @@
import android.content.res.CompatibilityInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
+import android.content.res.loader.ResourcesLoader;
import android.database.DatabaseErrorHandler;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
@@ -100,6 +101,7 @@
import java.nio.ByteOrder;
import java.nio.file.Path;
import java.util.ArrayList;
+import java.util.List;
import java.util.Objects;
import java.util.concurrent.Executor;
@@ -2217,7 +2219,8 @@
}
private static Resources createResources(IBinder activityToken, LoadedApk pi, String splitName,
- int displayId, Configuration overrideConfig, CompatibilityInfo compatInfo) {
+ int displayId, Configuration overrideConfig, CompatibilityInfo compatInfo,
+ List<ResourcesLoader> resourcesLoader) {
final String[] splitResDirs;
final ClassLoader classLoader;
try {
@@ -2234,7 +2237,8 @@
displayId,
overrideConfig,
compatInfo,
- classLoader);
+ classLoader,
+ resourcesLoader);
}
@Override
@@ -2249,7 +2253,7 @@
final int displayId = getDisplayId();
c.setResources(createResources(mToken, pi, null, displayId, null,
- getDisplayAdjustments(displayId).getCompatibilityInfo()));
+ getDisplayAdjustments(displayId).getCompatibilityInfo(), null));
if (c.mResources != null) {
return c;
}
@@ -2284,7 +2288,7 @@
final int displayId = getDisplayId();
c.setResources(createResources(mToken, pi, null, displayId, null,
- getDisplayAdjustments(displayId).getCompatibilityInfo()));
+ getDisplayAdjustments(displayId).getCompatibilityInfo(), null));
if (c.mResources != null) {
return c;
}
@@ -2328,7 +2332,8 @@
displayId,
null,
mPackageInfo.getCompatibilityInfo(),
- classLoader));
+ classLoader,
+ mResources.getLoaders()));
return context;
}
@@ -2342,8 +2347,10 @@
mSplitName, mToken, mUser, mFlags, mClassLoader, null);
final int displayId = getDisplayId();
+
context.setResources(createResources(mToken, mPackageInfo, mSplitName, displayId,
- overrideConfiguration, getDisplayAdjustments(displayId).getCompatibilityInfo()));
+ overrideConfiguration, getDisplayAdjustments(displayId).getCompatibilityInfo(),
+ mResources.getLoaders()));
return context;
}
@@ -2357,8 +2364,10 @@
mSplitName, mToken, mUser, mFlags, mClassLoader, null);
final int displayId = display.getDisplayId();
+
context.setResources(createResources(mToken, mPackageInfo, mSplitName, displayId,
- null, getDisplayAdjustments(displayId).getCompatibilityInfo()));
+ null, getDisplayAdjustments(displayId).getCompatibilityInfo(),
+ mResources.getLoaders()));
context.mDisplay = display;
return context;
}
@@ -2564,7 +2573,7 @@
ContextImpl context = new ContextImpl(null, systemContext.mMainThread, packageInfo, null,
null, null, null, 0, null, null);
context.setResources(createResources(null, packageInfo, null, displayId, null,
- packageInfo.getCompatibilityInfo()));
+ packageInfo.getCompatibilityInfo(), null));
context.updateDisplay(displayId);
context.mIsSystemOrSystemUiContext = true;
return context;
@@ -2637,7 +2646,8 @@
displayId,
overrideConfiguration,
compatInfo,
- classLoader));
+ classLoader,
+ packageInfo.getApplication().getResources().getLoaders()));
context.mDisplay = resourcesManager.getAdjustedDisplay(displayId,
context.getResources());
return context;
diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java
index f0d0e98..44c2486 100644
--- a/core/java/android/app/LoadedApk.java
+++ b/core/java/android/app/LoadedApk.java
@@ -365,7 +365,8 @@
mResources = ResourcesManager.getInstance().getResources(null, mResDir,
splitPaths, mOverlayDirs, mApplicationInfo.sharedLibraryFiles,
Display.DEFAULT_DISPLAY, null, getCompatibilityInfo(),
- getClassLoader());
+ getClassLoader(), mApplication == null ? null
+ : mApplication.getResources().getLoaders());
}
}
mAppComponentFactory = createAppFactory(aInfo, mDefaultClassLoader);
@@ -1158,7 +1159,7 @@
mResources = ResourcesManager.getInstance().getResources(null, mResDir,
splitPaths, mOverlayDirs, mApplicationInfo.sharedLibraryFiles,
Display.DEFAULT_DISPLAY, null, getCompatibilityInfo(),
- getClassLoader());
+ getClassLoader(), null);
}
return mResources;
}
diff --git a/core/java/android/app/ResourcesManager.java b/core/java/android/app/ResourcesManager.java
index 7ab85a4..d09f0bc 100644
--- a/core/java/android/app/ResourcesManager.java
+++ b/core/java/android/app/ResourcesManager.java
@@ -32,7 +32,7 @@
import android.content.res.Resources;
import android.content.res.ResourcesImpl;
import android.content.res.ResourcesKey;
-import android.content.res.loader.ResourceLoader;
+import android.content.res.loader.ResourcesLoader;
import android.hardware.display.DisplayManagerGlobal;
import android.os.IBinder;
import android.os.Process;
@@ -46,7 +46,6 @@
import android.view.Display;
import android.view.DisplayAdjustments;
-import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.IndentingPrintWriter;
@@ -57,9 +56,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
-import java.util.Collections;
import java.util.List;
-import java.util.Map;
import java.util.Objects;
import java.util.WeakHashMap;
import java.util.function.Predicate;
@@ -98,52 +95,6 @@
new ArrayMap<>();
/**
- * A list of {@link Resources} that contain unique {@link ResourcesImpl}s.
- *
- * These are isolated so that {@link ResourceLoader}s can be added and removed without
- * affecting other instances.
- *
- * When a reference is added here, it is guaranteed that the {@link ResourcesImpl}
- * it contains is unique to itself and will never be set to a shared reference.
- */
- @GuardedBy("this")
- private List<ResourcesWithLoaders> mResourcesWithLoaders = Collections.emptyList();
-
- private static class ResourcesWithLoaders {
-
- private WeakReference<Resources> mResources;
- private ResourcesKey mResourcesKey;
-
- @Nullable
- private WeakReference<IBinder> mActivityToken;
-
- ResourcesWithLoaders(Resources resources, ResourcesKey resourcesKey,
- IBinder activityToken) {
- this.mResources = new WeakReference<>(resources);
- this.mResourcesKey = resourcesKey;
- this.mActivityToken = new WeakReference<>(activityToken);
- }
-
- @Nullable
- Resources resources() {
- return mResources.get();
- }
-
- @Nullable
- IBinder activityToken() {
- return mActivityToken == null ? null : mActivityToken.get();
- }
-
- ResourcesKey resourcesKey() {
- return mResourcesKey;
- }
-
- void updateKey(ResourcesKey newKey) {
- mResourcesKey = newKey;
- }
- }
-
- /**
* A list of Resource references that can be reused.
*/
@UnsupportedAppUsage
@@ -219,6 +170,11 @@
private final ArrayMap<Pair<Integer, DisplayAdjustments>, WeakReference<Display>>
mAdjustedDisplays = new ArrayMap<>();
+ /**
+ * Callback implementation for handling updates to Resources objects.
+ */
+ private final UpdateHandler mUpdateCallbacks = new UpdateHandler();
+
@UnsupportedAppUsage
public ResourcesManager() {
}
@@ -253,24 +209,6 @@
}
}
- for (int i = mResourcesWithLoaders.size() - 1; i >= 0; i--) {
- ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(i);
- Resources resources = resourcesWithLoaders.resources();
- if (resources == null) {
- continue;
- }
-
- final ResourcesKey key = resourcesWithLoaders.resourcesKey();
- if (key.isPathReferenced(path)) {
- mResourcesWithLoaders.remove(i);
- ResourcesImpl impl = resources.getImpl();
- if (impl != null) {
- impl.flushLayoutCache();
- }
- count++;
- }
- }
-
Log.i(TAG, "Invalidated " + count + " asset managers that referenced " + path);
for (int i = mCachedApkAssets.size() - 1; i >= 0; i--) {
@@ -513,6 +451,12 @@
}
}
+ if (key.mLoaders != null) {
+ for (final ResourcesLoader loader : key.mLoaders) {
+ builder.addLoader(loader);
+ }
+ }
+
return builder.build();
}
@@ -570,16 +514,6 @@
pw.print("resource impls: ");
pw.println(countLiveReferences(mResourceImpls.values()));
-
- int resourcesWithLoadersCount = 0;
- for (int index = 0; index < mResourcesWithLoaders.size(); index++) {
- if (mResourcesWithLoaders.get(index).resources() != null) {
- resourcesWithLoadersCount++;
- }
- }
-
- pw.print("resources with loaders: ");
- pw.println(resourcesWithLoadersCount);
}
}
@@ -660,19 +594,6 @@
*/
private @Nullable ResourcesKey findKeyForResourceImplLocked(
@NonNull ResourcesImpl resourceImpl) {
- int size = mResourcesWithLoaders.size();
- for (int index = 0; index < size; index++) {
- ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(index);
- Resources resources = resourcesWithLoaders.resources();
- if (resources == null) {
- continue;
- }
-
- if (resourceImpl == resources.getImpl()) {
- return resourcesWithLoaders.resourcesKey();
- }
- }
-
int refCount = mResourceImpls.size();
for (int i = 0; i < refCount; i++) {
WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
@@ -722,30 +643,10 @@
@Nullable
private Resources findResourcesForActivityLocked(@NonNull IBinder targetActivityToken,
@NonNull ResourcesKey targetKey, @NonNull ClassLoader targetClassLoader) {
- int size = mResourcesWithLoaders.size();
- for (int index = 0; index < size; index++) {
- ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(index);
- Resources resources = resourcesWithLoaders.resources();
- if (resources == null) {
- continue;
- }
-
- IBinder activityToken = resourcesWithLoaders.activityToken();
- ResourcesKey key = resourcesWithLoaders.resourcesKey();
-
- ClassLoader classLoader = resources.getClassLoader();
-
- if (Objects.equals(activityToken, targetActivityToken)
- && Objects.equals(key, targetKey)
- && Objects.equals(classLoader, targetClassLoader)) {
- return resources;
- }
- }
-
ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
targetActivityToken);
- size = activityResources.activityResources.size();
+ final int size = activityResources.activityResources.size();
for (int index = 0; index < size; index++) {
WeakReference<Resources> ref = activityResources.activityResources.get(index);
Resources resources = ref.get();
@@ -771,6 +672,7 @@
Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
: new Resources(classLoader);
resources.setImpl(impl);
+ resources.setCallbacks(mUpdateCallbacks);
activityResources.activityResources.add(new WeakReference<>(resources));
if (DEBUG) {
Slog.d(TAG, "- creating new ref=" + resources);
@@ -784,6 +686,7 @@
Resources resources = compatInfo.needsCompatResources() ? new CompatResources(classLoader)
: new Resources(classLoader);
resources.setImpl(impl);
+ resources.setCallbacks(mUpdateCallbacks);
mResourceReferences.add(new WeakReference<>(resources));
if (DEBUG) {
Slog.d(TAG, "- creating new ref=" + resources);
@@ -795,7 +698,7 @@
/**
* Creates base resources for an Activity. Calls to
* {@link #getResources(IBinder, String, String[], String[], String[], int, Configuration,
- * CompatibilityInfo, ClassLoader)} with the same activityToken will have their override
+ * CompatibilityInfo, ClassLoader, List)} with the same activityToken will have their override
* configurations merged with the one specified here.
*
* @param activityToken Represents an Activity.
@@ -820,7 +723,8 @@
int displayId,
@Nullable Configuration overrideConfig,
@NonNull CompatibilityInfo compatInfo,
- @Nullable ClassLoader classLoader) {
+ @Nullable ClassLoader classLoader,
+ @Nullable List<ResourcesLoader> loaders) {
try {
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES,
"ResourcesManager#createBaseActivityResources");
@@ -831,7 +735,8 @@
libDirs,
displayId,
overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
- compatInfo);
+ compatInfo,
+ loaders == null ? null : loaders.toArray(new ResourcesLoader[0]));
classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
if (DEBUG) {
@@ -902,14 +807,6 @@
} else {
ArrayUtils.unstableRemoveIf(mResourceReferences, sEmptyReferencePredicate);
}
-
- for (int index = mResourcesWithLoaders.size() - 1; index >= 0; index--) {
- ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(index);
- Resources resources = resourcesWithLoaders.resources();
- if (resources == null) {
- mResourcesWithLoaders.remove(index);
- }
- }
}
}
@@ -983,7 +880,8 @@
int displayId,
@Nullable Configuration overrideConfig,
@NonNull CompatibilityInfo compatInfo,
- @Nullable ClassLoader classLoader) {
+ @Nullable ClassLoader classLoader,
+ @Nullable List<ResourcesLoader> loaders) {
try {
Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, "ResourcesManager#getResources");
final ResourcesKey key = new ResourcesKey(
@@ -993,7 +891,8 @@
libDirs,
displayId,
overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
- compatInfo);
+ compatInfo,
+ loaders == null ? null : loaders.toArray(new ResourcesLoader[0]));
classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
cleanupReferences(activityToken);
@@ -1010,10 +909,10 @@
/**
* Updates an Activity's Resources object with overrideConfig. The Resources object
- * that was previously returned by
- * {@link #getResources(IBinder, String, String[], String[], String[], int, Configuration,
- * CompatibilityInfo, ClassLoader)} is
- * still valid and will have the updated configuration.
+ * that was previously returned by {@link #getResources(IBinder, String, String[], String[],
+ * String[], int, Configuration, CompatibilityInfo, ClassLoader, List)} is still valid and will
+ * have the updated configuration.
+ *
* @param activityToken The Activity token.
* @param overrideConfig The configuration override to update.
* @param displayId Id of the display where activity currently resides.
@@ -1074,25 +973,6 @@
overrideConfig, displayId);
updateActivityResources(resources, newKey, false);
}
-
- // Also find loaders that are associated with an Activity
- final int loaderCount = mResourcesWithLoaders.size();
- for (int index = loaderCount - 1; index >= 0; index--) {
- ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(
- index);
- Resources resources = resourcesWithLoaders.resources();
- if (resources == null
- || resourcesWithLoaders.activityToken() != activityToken) {
- continue;
- }
-
- ResourcesKey newKey = rebaseActivityOverrideConfig(resources, oldConfig,
- overrideConfig, displayId);
-
- updateActivityResources(resources, newKey, true);
-
- resourcesWithLoaders.updateKey(newKey);
- }
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
@@ -1133,9 +1013,8 @@
// Create the new ResourcesKey with the rebased override config.
final ResourcesKey newKey = new ResourcesKey(oldKey.mResDir,
- oldKey.mSplitResDirs,
- oldKey.mOverlayDirs, oldKey.mLibDirs, displayId,
- rebasedOverrideConfig, oldKey.mCompatInfo);
+ oldKey.mSplitResDirs, oldKey.mOverlayDirs, oldKey.mLibDirs,
+ displayId, rebasedOverrideConfig, oldKey.mCompatInfo, oldKey.mLoaders);
if (DEBUG) {
Slog.d(TAG, "rebasing ref=" + resources + " from oldKey=" + oldKey
@@ -1214,18 +1093,6 @@
}
}
- for (int index = mResourcesWithLoaders.size() - 1; index >= 0; index--) {
- ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(index);
- Resources resources = resourcesWithLoaders.resources();
- if (resources == null) {
- mResourcesWithLoaders.remove(index);
- continue;
- }
-
- applyConfigurationToResourcesLocked(config, compat, tmpConfig,
- resourcesWithLoaders.resourcesKey(), resources.getImpl());
- }
-
return changes != 0;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
@@ -1306,37 +1173,8 @@
newLibAssets,
key.mDisplayId,
key.mOverrideConfiguration,
- key.mCompatInfo));
- }
- }
- }
-
- final int count = mResourcesWithLoaders.size();
- for (int index = 0; index < count; index++) {
- ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(index);
- Resources resources = resourcesWithLoaders.resources();
- if (resources == null) {
- continue;
- }
-
- ResourcesKey key = resourcesWithLoaders.resourcesKey();
- if (Objects.equals(key.mResDir, assetPath)) {
- String[] newLibAssets = key.mLibDirs;
- for (String libAsset : libAssets) {
- newLibAssets =
- ArrayUtils.appendElement(String.class, newLibAssets, libAsset);
- }
-
- if (!Arrays.equals(newLibAssets, key.mLibDirs)) {
- updatedResourceKeys.put(resources.getImpl(),
- new ResourcesKey(
- key.mResDir,
- key.mSplitResDirs,
- key.mOverlayDirs,
- newLibAssets,
- key.mDisplayId,
- key.mOverrideConfiguration,
- key.mCompatInfo));
+ key.mCompatInfo,
+ key.mLoaders));
}
}
}
@@ -1345,72 +1183,6 @@
}
}
- /**
- * Mark a {@link Resources} as containing a {@link ResourceLoader}.
- *
- * This removes its {@link ResourcesImpl} from the shared pool and creates it a new one. It is
- * then tracked separately, kept unique, and restored properly across {@link Activity}
- * recreations.
- */
- public void registerForLoaders(Resources resources) {
- synchronized (this) {
- boolean found = false;
- IBinder activityToken = null;
-
- // Remove the Resources from the reference list as it's now tracked separately
- int size = mResourceReferences.size();
- for (int index = 0; index < size; index++) {
- WeakReference<Resources> reference = mResourceReferences.get(index);
- if (reference.get() == resources) {
- mResourceReferences.remove(index);
- found = true;
- break;
- }
- }
-
- if (!found) {
- // Do the same removal for any Activity Resources
- for (Map.Entry<IBinder, ActivityResources> entry :
- mActivityResourceReferences.entrySet()) {
- ArrayList<WeakReference<Resources>> activityResourcesList =
- entry.getValue().activityResources;
- final int resCount = activityResourcesList.size();
- for (int index = 0; index < resCount; index++) {
- WeakReference<Resources> reference = activityResourcesList.get(index);
- if (reference.get() == resources) {
- activityToken = entry.getKey();
- activityResourcesList.remove(index);
- found = true;
- break;
- }
- }
-
- if (found) {
- break;
- }
- }
- }
-
- if (!found) {
- throw new IllegalArgumentException("Resources " + resources
- + " registered for loaders but was not previously tracked by"
- + " ResourcesManager");
- }
-
- ResourcesKey key = findKeyForResourceImplLocked(resources.getImpl());
- ResourcesImpl impl = createResourcesImpl(key);
-
- if (mResourcesWithLoaders == Collections.EMPTY_LIST) {
- mResourcesWithLoaders = Collections.synchronizedList(new ArrayList<>());
- }
-
- mResourcesWithLoaders.add(new ResourcesWithLoaders(resources, key, activityToken));
-
- // Set the new Impl, which is now guaranteed to be unique per Resources object
- resources.setImpl(impl);
- }
- }
-
// TODO(adamlesinski): Make this accept more than just overlay directories.
final void applyNewResourceDirsLocked(@NonNull final ApplicationInfo appInfo,
@Nullable final String[] oldPaths) {
@@ -1450,37 +1222,12 @@
key.mLibDirs,
key.mDisplayId,
key.mOverrideConfiguration,
- key.mCompatInfo
+ key.mCompatInfo,
+ key.mLoaders
));
}
}
- final int count = mResourcesWithLoaders.size();
- for (int index = 0; index < count; index++) {
- ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(index);
- Resources resources = resourcesWithLoaders.resources();
- if (resources == null) {
- continue;
- }
-
- ResourcesKey key = resourcesWithLoaders.resourcesKey();
-
- if (key.mResDir == null
- || key.mResDir.equals(baseCodePath)
- || ArrayUtils.contains(oldPaths, key.mResDir)) {
- updatedResourceKeys.put(resources.getImpl(),
- new ResourcesKey(
- baseCodePath,
- copiedSplitDirs,
- copiedResourceDirs,
- key.mLibDirs,
- key.mDisplayId,
- key.mOverrideConfiguration,
- key.mCompatInfo
- ));
- }
- }
-
redirectResourcesToNewImplLocked(updatedResourceKeys);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
@@ -1530,25 +1277,62 @@
}
}
}
+ }
- // Update any references that need to be re-built with loaders. These are intentionally not
- // part of either of the above lists.
- final int loaderCount = mResourcesWithLoaders.size();
- for (int index = loaderCount - 1; index >= 0; index--) {
- ResourcesWithLoaders resourcesWithLoaders = mResourcesWithLoaders.get(index);
- Resources resources = resourcesWithLoaders.resources();
- if (resources == null) {
- continue;
+ private class UpdateHandler implements Resources.UpdateCallbacks {
+
+ /**
+ * Updates the list of {@link ResourcesLoader ResourcesLoader(s)} that the {@code resources}
+ * instance uses.
+ */
+ @Override
+ public void onLoadersChanged(Resources resources, List<ResourcesLoader> newLoader) {
+ synchronized (ResourcesManager.this) {
+ final ResourcesKey oldKey = findKeyForResourceImplLocked(resources.getImpl());
+ if (oldKey == null) {
+ throw new IllegalArgumentException("Cannot modify resource loaders of"
+ + " ResourcesImpl not registered with ResourcesManager");
+ }
+
+ final ResourcesKey newKey = new ResourcesKey(
+ oldKey.mResDir,
+ oldKey.mSplitResDirs,
+ oldKey.mOverlayDirs,
+ oldKey.mLibDirs,
+ oldKey.mDisplayId,
+ oldKey.mOverrideConfiguration,
+ oldKey.mCompatInfo,
+ newLoader.toArray(new ResourcesLoader[0]));
+
+ final ResourcesImpl impl = findOrCreateResourcesImplForKeyLocked(newKey);
+ resources.setImpl(impl);
}
+ }
- ResourcesKey newKey = updatedResourceKeys.get(resources.getImpl());
- if (newKey == null) {
- continue;
+ /**
+ * Refreshes the {@link AssetManager} of all {@link ResourcesImpl} that contain the
+ * {@code loader} to apply any changes of the set of {@link ApkAssets}.
+ **/
+ @Override
+ public void onLoaderUpdated(ResourcesLoader loader) {
+ synchronized (ResourcesManager.this) {
+ final ArrayMap<ResourcesImpl, ResourcesKey> updatedResourceImplKeys =
+ new ArrayMap<>();
+
+ for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
+ final ResourcesKey key = mResourceImpls.keyAt(i);
+ final WeakReference<ResourcesImpl> impl = mResourceImpls.valueAt(i);
+ if (impl == null || impl.get() == null
+ || !ArrayUtils.contains(key.mLoaders, loader)) {
+ continue;
+ }
+
+ mResourceImpls.remove(key);
+ updatedResourceImplKeys.put(impl.get(), key);
+ }
+
+ redirectResourcesToNewImplLocked(updatedResourceImplKeys);
}
-
- resourcesWithLoaders.updateKey(newKey);
- resourcesWithLoaders.resources()
- .setImpl(createResourcesImpl(newKey));
}
}
}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index dbfc650..430241a 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -8521,7 +8521,8 @@
Display.DEFAULT_DISPLAY,
null,
systemResources.getCompatibilityInfo(),
- systemResources.getClassLoader());
+ systemResources.getClassLoader(),
+ null);
sUseRoundIcon = overlayableRes.getBoolean(com.android.internal.R.bool.config_useRoundIcon);
}
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java
index 96fbe91..1b01758 100644
--- a/core/java/android/content/res/AssetManager.java
+++ b/core/java/android/content/res/AssetManager.java
@@ -27,13 +27,12 @@
import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration.NativeConfig;
-import android.content.res.loader.ResourceLoader;
-import android.content.res.loader.ResourceLoaderManager;
+import android.content.res.loader.AssetsProvider;
+import android.content.res.loader.ResourcesLoader;
import android.content.res.loader.ResourcesProvider;
import android.os.ParcelFileDescriptor;
import android.util.ArraySet;
import android.util.Log;
-import android.util.Pair;
import android.util.SparseArray;
import android.util.TypedValue;
@@ -47,6 +46,7 @@
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
@@ -113,12 +113,7 @@
@GuardedBy("this") private int mNumRefs = 1;
@GuardedBy("this") private HashMap<Long, RuntimeException> mRefStacks;
- private ResourceLoaderManager mResourceLoaderManager;
-
- /** @hide */
- public void setResourceLoaderManager(ResourceLoaderManager resourceLoaderManager) {
- mResourceLoaderManager = resourceLoaderManager;
- }
+ private ResourcesLoader[] mLoaders;
/**
* A Builder class that helps create an AssetManager with only a single invocation of
@@ -130,32 +125,66 @@
*/
public static class Builder {
private ArrayList<ApkAssets> mUserApkAssets = new ArrayList<>();
+ private ArrayList<ResourcesLoader> mLoaders = new ArrayList<>();
public Builder addApkAssets(ApkAssets apkAssets) {
mUserApkAssets.add(apkAssets);
return this;
}
+ public Builder addLoader(ResourcesLoader loader) {
+ mLoaders.add(loader);
+ return this;
+ }
+
public AssetManager build() {
// Retrieving the system ApkAssets forces their creation as well.
final ApkAssets[] systemApkAssets = getSystem().getApkAssets();
- final int totalApkAssetCount = systemApkAssets.length + mUserApkAssets.size();
+ // Filter ApkAssets so that assets provided by multiple loaders are only included once
+ // in the AssetManager assets. The last appearance of the ApkAssets dictates its load
+ // order.
+ final ArrayList<ApkAssets> loaderApkAssets = new ArrayList<>();
+ final ArraySet<ApkAssets> uniqueLoaderApkAssets = new ArraySet<>();
+ for (int i = mLoaders.size() - 1; i >= 0; i--) {
+ final List<ApkAssets> currentLoaderApkAssets = mLoaders.get(i).getApkAssets();
+ for (int j = currentLoaderApkAssets.size() - 1; j >= 0; j--) {
+ final ApkAssets apkAssets = currentLoaderApkAssets.get(j);
+ if (uniqueLoaderApkAssets.contains(apkAssets)) {
+ continue;
+ }
+
+ uniqueLoaderApkAssets.add(apkAssets);
+ loaderApkAssets.add(0, apkAssets);
+ }
+ }
+
+ final int totalApkAssetCount = systemApkAssets.length + mUserApkAssets.size()
+ + loaderApkAssets.size();
final ApkAssets[] apkAssets = new ApkAssets[totalApkAssetCount];
System.arraycopy(systemApkAssets, 0, apkAssets, 0, systemApkAssets.length);
- final int userApkAssetCount = mUserApkAssets.size();
- for (int i = 0; i < userApkAssetCount; i++) {
+ // Append user ApkAssets after system ApkAssets.
+ for (int i = 0, n = mUserApkAssets.size(); i < n; i++) {
apkAssets[i + systemApkAssets.length] = mUserApkAssets.get(i);
}
+ // Append ApkAssets provided by loaders to the end.
+ for (int i = 0, n = loaderApkAssets.size(); i < n; i++) {
+ apkAssets[i + systemApkAssets.length + mUserApkAssets.size()] =
+ loaderApkAssets.get(i);
+ }
+
// Calling this constructor prevents creation of system ApkAssets, which we took care
// of in this Builder.
final AssetManager assetManager = new AssetManager(false /*sentinel*/);
assetManager.mApkAssets = apkAssets;
AssetManager.nativeSetApkAssets(assetManager.mObject, apkAssets,
false /*invalidateCaches*/);
+ assetManager.mLoaders = mLoaders.isEmpty() ? null
+ : mLoaders.toArray(new ResourcesLoader[0]);
+
return assetManager;
}
}
@@ -432,6 +461,12 @@
}
}
+ /** @hide */
+ @NonNull
+ public List<ResourcesLoader> getLoaders() {
+ return mLoaders == null ? Collections.emptyList() : Arrays.asList(mLoaders);
+ }
+
/**
* Ensures that the native implementation has not been destroyed.
* The AssetManager may have been closed, but references to it still exist
@@ -1056,38 +1091,70 @@
}
}
- private InputStream searchLoaders(int cookie, @NonNull String fileName, int accessMode)
- throws IOException {
- if (mResourceLoaderManager == null) {
+ private ResourcesProvider findResourcesProvider(int assetCookie) {
+ if (mLoaders == null) {
return null;
}
- List<Pair<ResourceLoader, ResourcesProvider>> loaders =
- mResourceLoaderManager.getInternalList();
+ int apkAssetsIndex = assetCookie - 1;
+ if (apkAssetsIndex >= mApkAssets.length || apkAssetsIndex < 0) {
+ return null;
+ }
- // A cookie of 0 means no specific ApkAssets, so search everything
+ final ApkAssets apkAssets = mApkAssets[apkAssetsIndex];
+ if (!apkAssets.isForLoader()) {
+ return null;
+ }
+
+ for (int i = mLoaders.length - 1; i >= 0; i--) {
+ final ResourcesLoader loader = mLoaders[i];
+ for (int j = 0, n = loader.getProviders().size(); j < n; j++) {
+ final ResourcesProvider provider = loader.getProviders().get(j);
+ if (apkAssets == provider.getApkAssets()) {
+ return provider;
+ }
+ }
+ }
+
+ return null;
+ }
+
+ private InputStream searchLoaders(int cookie, @NonNull String fileName, int accessMode)
+ throws IOException {
+ if (mLoaders == null) {
+ return null;
+ }
+
if (cookie == 0) {
- for (int index = loaders.size() - 1; index >= 0; index--) {
- Pair<ResourceLoader, ResourcesProvider> pair = loaders.get(index);
- try {
- InputStream inputStream = pair.first.loadAsset(fileName, accessMode);
- if (inputStream != null) {
- return inputStream;
+ // A cookie of 0 means no specific ApkAssets, so search everything
+ for (int i = mLoaders.length - 1; i >= 0; i--) {
+ final ResourcesLoader loader = mLoaders[i];
+ final List<ResourcesProvider> providers = loader.getProviders();
+ for (int j = providers.size() - 1; j >= 0; j--) {
+ final AssetsProvider assetsProvider = providers.get(j).getAssetsProvider();
+ if (assetsProvider == null) {
+ continue;
}
- } catch (IOException ignored) {
- // When searching, ignore read failures
+
+ try {
+ final InputStream inputStream = assetsProvider.loadAsset(
+ fileName, accessMode);
+ if (inputStream != null) {
+ return inputStream;
+ }
+ } catch (IOException ignored) {
+ // When searching, ignore read failures
+ }
}
}
return null;
}
- ApkAssets apkAssets = mApkAssets[cookie - 1];
- for (int index = loaders.size() - 1; index >= 0; index--) {
- Pair<ResourceLoader, ResourcesProvider> pair = loaders.get(index);
- if (pair.second.getApkAssets() == apkAssets) {
- return pair.first.loadAsset(fileName, accessMode);
- }
+ final ResourcesProvider provider = findResourcesProvider(cookie);
+ if (provider != null && provider.getAssetsProvider() != null) {
+ return provider.getAssetsProvider().loadAsset(
+ fileName, accessMode);
}
return null;
@@ -1095,43 +1162,48 @@
private AssetFileDescriptor searchLoadersFd(int cookie, @NonNull String fileName)
throws IOException {
- if (mResourceLoaderManager == null) {
+ if (mLoaders == null) {
return null;
}
- List<Pair<ResourceLoader, ResourcesProvider>> loaders =
- mResourceLoaderManager.getInternalList();
-
- // A cookie of 0 means no specific ApkAssets, so search everything
if (cookie == 0) {
- for (int index = loaders.size() - 1; index >= 0; index--) {
- Pair<ResourceLoader, ResourcesProvider> pair = loaders.get(index);
- try {
- ParcelFileDescriptor fileDescriptor = pair.first.loadAssetFd(fileName);
- if (fileDescriptor != null) {
- return new AssetFileDescriptor(fileDescriptor, 0,
- AssetFileDescriptor.UNKNOWN_LENGTH);
+ // A cookie of 0 means no specific ApkAssets, so search everything
+ for (int i = mLoaders.length - 1; i >= 0; i--) {
+ final ResourcesLoader loader = mLoaders[i];
+ final List<ResourcesProvider> providers = loader.getProviders();
+ for (int j = providers.size() - 1; j >= 0; j--) {
+ final AssetsProvider assetsProvider = providers.get(j).getAssetsProvider();
+ if (assetsProvider == null) {
+ continue;
}
- } catch (IOException ignored) {
- // When searching, ignore read failures
+
+ try {
+ final ParcelFileDescriptor fileDescriptor = assetsProvider
+ .loadAssetParcelFd(fileName);
+ if (fileDescriptor != null) {
+ return new AssetFileDescriptor(fileDescriptor, 0,
+ AssetFileDescriptor.UNKNOWN_LENGTH);
+ }
+ } catch (IOException ignored) {
+ // When searching, ignore read failures
+ }
}
}
return null;
}
- ApkAssets apkAssets = mApkAssets[cookie - 1];
- for (int index = loaders.size() - 1; index >= 0; index--) {
- Pair<ResourceLoader, ResourcesProvider> pair = loaders.get(index);
- if (pair.second.getApkAssets() == apkAssets) {
- ParcelFileDescriptor fileDescriptor = pair.first.loadAssetFd(fileName);
- if (fileDescriptor != null) {
- return new AssetFileDescriptor(fileDescriptor, 0,
- AssetFileDescriptor.UNKNOWN_LENGTH);
- }
- return null;
+ final ResourcesProvider provider = findResourcesProvider(cookie);
+ if (provider != null && provider.getAssetsProvider() != null) {
+ final ParcelFileDescriptor fileDescriptor = provider.getAssetsProvider()
+ .loadAssetParcelFd(fileName);
+ if (fileDescriptor != null) {
+ return new AssetFileDescriptor(fileDescriptor, 0,
+ AssetFileDescriptor.UNKNOWN_LENGTH);
}
+ return null;
}
+
return null;
}
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 4725e0a..471e83c 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -30,7 +30,6 @@
import android.annotation.DrawableRes;
import android.annotation.FontRes;
import android.annotation.FractionRes;
-import android.annotation.IntRange;
import android.annotation.IntegerRes;
import android.annotation.LayoutRes;
import android.annotation.NonNull;
@@ -41,13 +40,10 @@
import android.annotation.StyleRes;
import android.annotation.StyleableRes;
import android.annotation.XmlRes;
-import android.app.ResourcesManager;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo;
import android.content.pm.ActivityInfo.Config;
-import android.content.res.loader.ResourceLoader;
-import android.content.res.loader.ResourceLoaderManager;
-import android.content.res.loader.ResourcesProvider;
+import android.content.res.loader.ResourcesLoader;
import android.graphics.Movie;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
@@ -55,18 +51,17 @@
import android.graphics.drawable.DrawableInflater;
import android.os.Build;
import android.os.Bundle;
+import android.util.ArraySet;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.LongSparseArray;
-import android.util.Pair;
import android.util.Pools.SynchronizedPool;
import android.util.TypedValue;
import android.view.DisplayAdjustments;
import android.view.ViewDebug;
import android.view.ViewHierarchyEncoder;
-import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.GrowingArrayUtils;
@@ -117,6 +112,7 @@
static final String TAG = "Resources";
private static final Object sSync = new Object();
+ private final Object mLock = new Object();
// Used by BridgeResources in layoutlib
@UnsupportedAppUsage
@@ -143,10 +139,7 @@
@UnsupportedAppUsage
final ClassLoader mClassLoader;
- private final Object mResourceLoaderLock = new Object();
-
- @GuardedBy("mResourceLoaderLock")
- private ResourceLoaderManager mResourceLoaderManager;
+ private UpdateCallbacks mCallbacks = null;
/**
* WeakReferences to Themes that were constructed from this Resources object.
@@ -240,6 +233,18 @@
}
}
+ /** @hide */
+ public interface UpdateCallbacks extends ResourcesLoader.UpdateCallbacks {
+ /**
+ * Invoked when a {@link Resources} instance has a {@link ResourcesLoader} added, removed,
+ * or reordered.
+ *
+ * @param resources the instance being updated
+ * @param newLoaders the new set of loaders for the instance
+ */
+ void onLoadersChanged(Resources resources, List<ResourcesLoader> newLoaders);
+ }
+
/**
* Create a new Resources object on top of an existing set of assets in an
* AssetManager.
@@ -303,12 +308,6 @@
mBaseApkAssetsSize = ArrayUtils.size(impl.getAssets().getApkAssets());
mResourcesImpl = impl;
- synchronized (mResourceLoaderLock) {
- if (mResourceLoaderManager != null) {
- mResourceLoaderManager.onImplUpdate(mResourcesImpl);
- }
- }
-
// Create new ThemeImpls that are identical to the ones we have.
synchronized (mThemeRefs) {
final int count = mThemeRefs.size();
@@ -322,6 +321,15 @@
}
}
+ /** @hide */
+ public void setCallbacks(UpdateCallbacks callbacks) {
+ if (mCallbacks != null) {
+ throw new IllegalStateException("callback already registered");
+ }
+
+ mCallbacks = callbacks;
+ }
+
/**
* @hide
*/
@@ -937,14 +945,6 @@
@UnsupportedAppUsage
Drawable loadDrawable(@NonNull TypedValue value, int id, int density, @Nullable Theme theme)
throws NotFoundException {
- ResourceLoader loader = findLoader(value.assetCookie);
- if (loader != null) {
- Drawable drawable = loader.loadDrawable(value, id, density, theme);
- if (drawable != null) {
- return drawable;
- }
- }
-
return mResourcesImpl.loadDrawable(this, value, id, density, theme);
}
@@ -2337,14 +2337,6 @@
@UnsupportedAppUsage
XmlResourceParser loadXmlResourceParser(String file, int id, int assetCookie,
String type) throws NotFoundException {
- ResourceLoader loader = findLoader(assetCookie);
- if (loader != null) {
- XmlResourceParser xml = loader.loadXmlResourceParser(file, id);
- if (xml != null) {
- return xml;
- }
- }
-
return mResourcesImpl.loadXmlResourceParser(file, id, assetCookie, type);
}
@@ -2371,136 +2363,106 @@
return theme.obtainStyledAttributes(set, attrs, 0, 0);
}
- private ResourceLoader findLoader(int assetCookie) {
- ApkAssets[] apkAssetsArray = mResourcesImpl.getAssets().getApkAssets();
- int apkAssetsIndex = assetCookie - 1;
- if (apkAssetsIndex < apkAssetsArray.length && apkAssetsIndex >= 0) {
- ApkAssets apkAssets = apkAssetsArray[apkAssetsIndex];
- if (apkAssets.isForLoader()) {
- List<Pair<ResourceLoader, ResourcesProvider>> loaders;
- // Since we don't lock the entire resolution path anyways,
- // only lock here instead of entire method. The list is copied
- // and effectively a snapshot is used.
- synchronized (mResourceLoaderLock) {
- loaders = mResourceLoaderManager.getInternalList();
- }
-
- if (!ArrayUtils.isEmpty(loaders)) {
- int size = loaders.size();
- for (int index = 0; index < size; index++) {
- Pair<ResourceLoader, ResourcesProvider> pair = loaders.get(index);
- if (pair.second.getApkAssets() == apkAssets) {
- return pair.first;
- }
- }
- }
- }
+ private void checkCallbacksRegistered() {
+ if (mCallbacks == null) {
+ throw new IllegalArgumentException("Cannot modify resource loaders of Resources"
+ + " instances created outside of ResourcesManager");
}
-
- return null;
}
/**
- * @return copied list of loaders and providers previously added
+ * Retrieves the list of loaders.
+ *
+ * <p>Loaders are listed in increasing precedence order. A loader will override the resources
+ * and assets of loaders listed before itself.
*/
@NonNull
- public List<Pair<ResourceLoader, ResourcesProvider>> getLoaders() {
- synchronized (mResourceLoaderLock) {
- return mResourceLoaderManager == null
- ? Collections.emptyList()
- : mResourceLoaderManager.getLoaders();
- }
+ public List<ResourcesLoader> getLoaders() {
+ return mResourcesImpl.getAssets().getLoaders();
}
/**
- * Add a custom {@link ResourceLoader} which is added to the paths searched by
- * {@link AssetManager} when resolving a resource.
+ * Appends a loader to the end of the loader list. If the loader is already present in the
+ * loader list, the list will not be modified.
*
- * Resources are resolved as if the loader was a resource overlay, meaning the latest
- * in the list, of equal or better config, is returned.
- *
- * {@link ResourcesProvider}s passed in here are not managed and a reference should be held
- * to remove, re-use, or close them when necessary.
- *
- * @param resourceLoader an interface used to resolve file paths for drawables/XML files;
- * a reference should be kept to remove the loader if necessary
- * @param resourcesProvider an .apk or .arsc file representation
- * @param index where to add the loader in the list
- * @throws IllegalArgumentException if the resourceLoader is already added
- * @throws IndexOutOfBoundsException if the index is invalid
+ * @param loader the loader to add
*/
- public void addLoader(@NonNull ResourceLoader resourceLoader,
- @NonNull ResourcesProvider resourcesProvider, @IntRange(from = 0) int index) {
- synchronized (mResourceLoaderLock) {
- if (mResourceLoaderManager == null) {
- ResourcesManager.getInstance().registerForLoaders(this);
- mResourceLoaderManager = new ResourceLoaderManager(mResourcesImpl);
+ public void addLoader(@NonNull ResourcesLoader loader) {
+ synchronized (mLock) {
+ checkCallbacksRegistered();
+
+ final List<ResourcesLoader> loaders = new ArrayList<>(
+ mResourcesImpl.getAssets().getLoaders());
+ if (loaders.contains(loader)) {
+ return;
}
- mResourceLoaderManager.addLoader(resourceLoader, resourcesProvider, index);
+ loaders.add(loader);
+ mCallbacks.onLoadersChanged(this, loaders);
+ loader.registerOnProvidersChangedCallback(this, mCallbacks);
}
}
/**
- * @see #addLoader(ResourceLoader, ResourcesProvider, int).
+ * Removes a loader from the loaders. If the loader is not present in the loader list, the list
+ * will not be modified.
*
- * Adds to the end of the list.
- *
- * @return index the loader was added at
+ * @param loader the loader to remove
*/
- public int addLoader(@NonNull ResourceLoader resourceLoader,
- @NonNull ResourcesProvider resourcesProvider) {
- synchronized (mResourceLoaderLock) {
- int index = getLoaders().size();
- addLoader(resourceLoader, resourcesProvider, index);
- return index;
- }
- }
+ public void removeLoader(@NonNull ResourcesLoader loader) {
+ synchronized (mLock) {
+ checkCallbacksRegistered();
- /**
- * Remove a loader previously added by
- * {@link #addLoader(ResourceLoader, ResourcesProvider, int)}
- *
- * The caller maintains responsibility for holding a reference to the matching
- * {@link ResourcesProvider} and closing it after this method has been called.
- *
- * @param resourceLoader the same reference passed into [addLoader
- * @return the index the loader was at in the list, or -1 if the loader was not found
- */
- public int removeLoader(@NonNull ResourceLoader resourceLoader) {
- synchronized (mResourceLoaderLock) {
- if (mResourceLoaderManager == null) {
- return -1;
+ final List<ResourcesLoader> loaders = new ArrayList<>(
+ mResourcesImpl.getAssets().getLoaders());
+ if (!loaders.remove(loader)) {
+ return;
}
- return mResourceLoaderManager.removeLoader(resourceLoader);
+ mCallbacks.onLoadersChanged(this, loaders);
+ loader.unregisterOnProvidersChangedCallback(this);
}
}
/**
- * Swap the current set of loaders. Preferred to multiple remove/add calls as this doesn't
- * update the resource data structures after each modification.
+ * Sets the list of loaders.
*
- * Set to null or an empty list to clear the set of loaders.
- *
- * The caller maintains responsibility for holding references to the added
- * {@link ResourcesProvider}s and closing them after this method has been called.
- *
- * @param resourceLoadersAndProviders a list of pairs to add
+ * @param loaders the new loaders
*/
- public void setLoaders(
- @Nullable List<Pair<ResourceLoader, ResourcesProvider>> resourceLoadersAndProviders) {
- synchronized (mResourceLoaderLock) {
- if (mResourceLoaderManager == null) {
- if (ArrayUtils.isEmpty(resourceLoadersAndProviders)) {
- return;
+ public void setLoaders(@NonNull List<ResourcesLoader> loaders) {
+ synchronized (mLock) {
+ checkCallbacksRegistered();
+
+ final List<ResourcesLoader> oldLoaders = mResourcesImpl.getAssets().getLoaders();
+ int index = 0;
+ boolean modified = loaders.size() != oldLoaders.size();
+ final ArraySet<ResourcesLoader> seenLoaders = new ArraySet<>();
+ for (final ResourcesLoader loader : loaders) {
+ if (!seenLoaders.add(loader)) {
+ throw new IllegalArgumentException("Loader " + loader + " present twice");
}
- ResourcesManager.getInstance().registerForLoaders(this);
- mResourceLoaderManager = new ResourceLoaderManager(mResourcesImpl);
+ if (!modified && oldLoaders.get(index++) != loader) {
+ modified = true;
+ }
}
- mResourceLoaderManager.setLoaders(resourceLoadersAndProviders);
+ if (!modified) {
+ return;
+ }
+
+ mCallbacks.onLoadersChanged(this, loaders);
+ for (int i = 0, n = oldLoaders.size(); i < n; i++) {
+ oldLoaders.get(i).unregisterOnProvidersChangedCallback(this);
+ }
+ for (ResourcesLoader newLoader : loaders) {
+ newLoader.registerOnProvidersChangedCallback(this, mCallbacks);
+ }
}
}
+
+ /** Removes all {@link ResourcesLoader ResourcesLoader(s)}. */
+ public void clearLoaders() {
+ setLoaders(Collections.emptyList());
+ }
}
diff --git a/core/java/android/content/res/ResourcesKey.java b/core/java/android/content/res/ResourcesKey.java
index a29fea0..9e40f46 100644
--- a/core/java/android/content/res/ResourcesKey.java
+++ b/core/java/android/content/res/ResourcesKey.java
@@ -19,6 +19,7 @@
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.UnsupportedAppUsage;
+import android.content.res.loader.ResourcesLoader;
import android.text.TextUtils;
import java.util.Arrays;
@@ -48,6 +49,9 @@
@NonNull
public final CompatibilityInfo mCompatInfo;
+ @Nullable
+ public final ResourcesLoader[] mLoaders;
+
private final int mHash;
@UnsupportedAppUsage
@@ -57,11 +61,13 @@
@Nullable String[] libDirs,
int displayId,
@Nullable Configuration overrideConfig,
- @Nullable CompatibilityInfo compatInfo) {
+ @Nullable CompatibilityInfo compatInfo,
+ @Nullable ResourcesLoader[] loader) {
mResDir = resDir;
mSplitResDirs = splitResDirs;
mOverlayDirs = overlayDirs;
mLibDirs = libDirs;
+ mLoaders = (loader != null && loader.length == 0) ? null : loader;
mDisplayId = displayId;
mOverrideConfiguration = new Configuration(overrideConfig != null
? overrideConfig : Configuration.EMPTY);
@@ -75,6 +81,7 @@
hash = 31 * hash + mDisplayId;
hash = 31 * hash + Objects.hashCode(mOverrideConfiguration);
hash = 31 * hash + Objects.hashCode(mCompatInfo);
+ hash = 31 * hash + Arrays.hashCode(mLoaders);
mHash = hash;
}
@@ -140,6 +147,9 @@
if (!Objects.equals(mCompatInfo, peer.mCompatInfo)) {
return false;
}
+ if (!Arrays.equals(mLoaders, peer.mLoaders)) {
+ return false;
+ }
return true;
}
@@ -167,7 +177,11 @@
builder.append(" mOverrideConfig=").append(Configuration.resourceQualifierString(
mOverrideConfiguration));
builder.append(" mCompatInfo=").append(mCompatInfo);
- builder.append("}");
+ builder.append(" mLoaders=[");
+ if (mLoaders != null) {
+ builder.append(TextUtils.join(",", mLoaders));
+ }
+ builder.append("]}");
return builder.toString();
}
}
diff --git a/core/java/android/content/res/loader/AssetsProvider.java b/core/java/android/content/res/loader/AssetsProvider.java
new file mode 100644
index 0000000..c315494
--- /dev/null
+++ b/core/java/android/content/res/loader/AssetsProvider.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.res.loader;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.AssetManager;
+import android.os.ParcelFileDescriptor;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Provides callbacks that allow for the value of a file-based resources or assets of a
+ * {@link ResourcesProvider} to be specified or overridden.
+ */
+public interface AssetsProvider {
+
+ /**
+ * Callback that allows the value of a file-based resources or asset to be specified or
+ * overridden.
+ *
+ * <p>There are two situations in which this method will be called:
+ * <ul>
+ * <li>AssetManager is queried for an InputStream of an asset using APIs like
+ * {@link AssetManager#open} and {@link AssetManager#openXmlResourceParser}.
+ * <li>AssetManager is resolving the value of a file-based resource provided by the
+ * {@link ResourcesProvider} this instance is associated with.
+ * </ul>
+ *
+ * <p>If the value retrieved from this callback is null, AssetManager will attempt to find the
+ * file-based resource or asset within the APK provided by the ResourcesProvider this instance
+ * is associated with.
+ *
+ * @param path the asset path being loaded
+ * @param accessMode the {@link AssetManager} access mode
+ *
+ * @see AssetManager#open
+ */
+ @Nullable
+ default InputStream loadAsset(@NonNull String path, int accessMode) throws IOException {
+ return null;
+ }
+
+ /**
+ * {@link ParcelFileDescriptor} variant of {@link #loadAsset(String, int)}.
+ *
+ * @param path the asset path being loaded
+ */
+ @Nullable
+ default ParcelFileDescriptor loadAssetParcelFd(@NonNull String path) throws IOException {
+ return null;
+ }
+}
diff --git a/core/java/android/content/res/loader/DirectoryResourceLoader.java b/core/java/android/content/res/loader/DirectoryAssetsProvider.java
similarity index 70%
rename from core/java/android/content/res/loader/DirectoryResourceLoader.java
rename to core/java/android/content/res/loader/DirectoryAssetsProvider.java
index 7d90e72..81c2a4c 100644
--- a/core/java/android/content/res/loader/DirectoryResourceLoader.java
+++ b/core/java/android/content/res/loader/DirectoryAssetsProvider.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -26,23 +26,27 @@
import java.io.InputStream;
/**
- * A {@link ResourceLoader} that searches a directory for assets.
- *
- * Assumes that resource paths are resolvable child paths of the directory passed in.
+ * A {@link AssetsProvider} that searches a directory for assets.
+ * Assumes that resource paths are resolvable child paths of the root directory passed in.
*/
-public class DirectoryResourceLoader implements ResourceLoader {
+public class DirectoryAssetsProvider implements AssetsProvider {
@NonNull
private final File mDirectory;
- public DirectoryResourceLoader(@NonNull File directory) {
+ /**
+ * Creates a DirectoryAssetsProvider with given root directory.
+ *
+ * @param directory the root directory to resolve files from
+ */
+ public DirectoryAssetsProvider(@NonNull File directory) {
this.mDirectory = directory;
}
@Nullable
@Override
public InputStream loadAsset(@NonNull String path, int accessMode) throws IOException {
- File file = findFile(path);
+ final File file = findFile(path);
if (file == null || !file.exists()) {
return null;
}
@@ -51,8 +55,8 @@
@Nullable
@Override
- public ParcelFileDescriptor loadAssetFd(@NonNull String path) throws IOException {
- File file = findFile(path);
+ public ParcelFileDescriptor loadAssetParcelFd(@NonNull String path) throws IOException {
+ final File file = findFile(path);
if (file == null || !file.exists()) {
return null;
}
@@ -60,7 +64,9 @@
}
/**
- * Find the file for the given path encoded into the resource table.
+ * Finds the file relative to the root directory.
+ *
+ * @param path the relative path of the file
*/
@Nullable
public File findFile(@NonNull String path) {
diff --git a/core/java/android/content/res/loader/ResourceLoader.java b/core/java/android/content/res/loader/ResourceLoader.java
deleted file mode 100644
index af32aa2..0000000
--- a/core/java/android/content/res/loader/ResourceLoader.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.content.res.loader;
-
-import android.annotation.AnyRes;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.content.res.AssetManager;
-import android.content.res.Resources;
-import android.content.res.XmlResourceParser;
-import android.graphics.drawable.Drawable;
-import android.os.ParcelFileDescriptor;
-import android.util.TypedValue;
-
-import java.io.IOException;
-import java.io.InputStream;
-
-/**
- * Exposes methods for overriding file-based resource loading from a {@link Resources}.
- *
- * To be used with {@link Resources#addLoader(ResourceLoader, ResourcesProvider, int)} and related
- * methods to override resource loading.
- *
- * Note that this class doesn't actually contain any resource data. Non-file-based resources are
- * loaded directly from the {@link ResourcesProvider}'s .arsc representation.
- *
- * An instance's methods will only be called if its corresponding {@link ResourcesProvider}'s
- * resources table contains an entry for the resource ID being resolved,
- * with the exception of the non-cookie variants of {@link AssetManager}'s openAsset and
- * openNonAsset.
- *
- * Those methods search backwards through all {@link ResourceLoader}s and then any paths provided
- * by the application or system.
- *
- * Otherwise, an ARSC that defines R.drawable.some_id must be provided if a {@link ResourceLoader}
- * wants to point R.drawable.some_id to a different file on disk.
- */
-public interface ResourceLoader {
-
- /**
- * Given the value resolved from the string pool of the {@link ResourcesProvider} passed to
- * {@link Resources#addLoader(ResourceLoader, ResourcesProvider, int)}, return a
- * {@link Drawable} which should be returned by the parent
- * {@link Resources#getDrawable(int, Resources.Theme)}.
- *
- * @param value the resolved {@link TypedValue} before it has been converted to a Drawable
- * object
- * @param id the R.drawable ID this resolution is for
- * @param density the requested density
- * @param theme the {@link Resources.Theme} resolved under
- * @return null if resolution should try to find an entry inside the {@link ResourcesProvider},
- * including calling through to {@link #loadAsset(String, int)} or {@link #loadAssetFd(String)}
- */
- @Nullable
- default Drawable loadDrawable(@NonNull TypedValue value, int id, int density,
- @Nullable Resources.Theme theme) {
- return null;
- }
-
- /**
- * Given the value resolved from the string pool of the {@link ResourcesProvider} passed to
- * {@link Resources#addLoader(ResourceLoader, ResourcesProvider, int)}, return an
- * {@link XmlResourceParser} which should be returned by the parent
- * {@link Resources#getDrawable(int, Resources.Theme)}.
- *
- * @param path the string that was found in the string pool
- * @param id the XML ID this resolution is for, can be R.anim, R.layout, or R.xml
- * @return null if resolution should try to find an entry inside the {@link ResourcesProvider},
- * including calling through to {@link #loadAssetFd(String)} (String, int)}
- */
- @Nullable
- default XmlResourceParser loadXmlResourceParser(@NonNull String path, @AnyRes int id) {
- return null;
- }
-
- /**
- * Given the value resolved from the string pool of the {@link ResourcesProvider} passed to
- * {@link Resources#addLoader(ResourceLoader, ResourcesProvider, int)}, return an
- * {@link InputStream} which should be returned when an asset is loaded by {@link AssetManager}.
- * Assets will be loaded from a provider's root, with anything in its assets subpath prefixed
- * with "assets/".
- *
- * @param path the asset path to load
- * @param accessMode {@link AssetManager} access mode; does not have to be respected
- * @return null if resolution should try to find an entry inside the {@link ResourcesProvider}
- */
- @Nullable
- default InputStream loadAsset(@NonNull String path, int accessMode) throws IOException {
- return null;
- }
-
- /**
- * {@link ParcelFileDescriptor} variant of {@link #loadAsset(String, int)}.
- *
- * @param path the asset path to load
- * @return null if resolution should try to find an entry inside the {@link ResourcesProvider}
- */
- @Nullable
- default ParcelFileDescriptor loadAssetFd(@NonNull String path) throws IOException {
- return null;
- }
-}
diff --git a/core/java/android/content/res/loader/ResourceLoaderManager.java b/core/java/android/content/res/loader/ResourceLoaderManager.java
deleted file mode 100644
index 592ec09..0000000
--- a/core/java/android/content/res/loader/ResourceLoaderManager.java
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.content.res.loader;
-
-import android.annotation.Nullable;
-import android.content.res.ApkAssets;
-import android.content.res.AssetManager;
-import android.content.res.Resources;
-import android.content.res.ResourcesImpl;
-import android.util.Pair;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.ArrayUtils;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Objects;
-
-/**
- * @hide
- */
-public class ResourceLoaderManager {
-
- private final Object mLock = new Object();
-
- @GuardedBy("mLock")
- private final List<Pair<ResourceLoader, ResourcesProvider>> mResourceLoaders =
- new ArrayList<>();
-
- @GuardedBy("mLock")
- private ResourcesImpl mResourcesImpl;
-
- public ResourceLoaderManager(ResourcesImpl resourcesImpl) {
- this.mResourcesImpl = resourcesImpl;
- this.mResourcesImpl.getAssets().setResourceLoaderManager(this);
- }
-
- /**
- * Copies the list to ensure that ongoing mutations don't affect the list if it's being used
- * as a search set.
- *
- * @see Resources#getLoaders()
- */
- public List<Pair<ResourceLoader, ResourcesProvider>> getLoaders() {
- synchronized (mLock) {
- return new ArrayList<>(mResourceLoaders);
- }
- }
-
- /**
- * Returns a list for searching for a loader. Locks and copies the list to ensure that
- * ongoing mutations don't affect the search set.
- */
- public List<Pair<ResourceLoader, ResourcesProvider>> getInternalList() {
- synchronized (mLock) {
- return new ArrayList<>(mResourceLoaders);
- }
- }
-
- /**
- * TODO(b/136251855): Consider optional boolean ignoreConfigurations to allow ResourceLoader
- * to override every configuration in the target package
- *
- * @see Resources#addLoader(ResourceLoader, ResourcesProvider)
- */
- public void addLoader(ResourceLoader resourceLoader, ResourcesProvider resourcesProvider,
- int index) {
- synchronized (mLock) {
- for (int listIndex = 0; listIndex < mResourceLoaders.size(); listIndex++) {
- if (Objects.equals(mResourceLoaders.get(listIndex).first, resourceLoader)) {
- throw new IllegalArgumentException("Cannot add the same ResourceLoader twice");
- }
- }
-
- mResourceLoaders.add(index, Pair.create(resourceLoader, resourcesProvider));
- updateLoaders();
- }
- }
-
- /**
- * @see Resources#removeLoader(ResourceLoader)
- */
- public int removeLoader(ResourceLoader resourceLoader) {
- synchronized (mLock) {
- int indexOfLoader = -1;
-
- for (int index = 0; index < mResourceLoaders.size(); index++) {
- if (mResourceLoaders.get(index).first == resourceLoader) {
- indexOfLoader = index;
- break;
- }
- }
-
- if (indexOfLoader < 0) {
- return indexOfLoader;
- }
-
- mResourceLoaders.remove(indexOfLoader);
- updateLoaders();
- return indexOfLoader;
- }
- }
-
- /**
- * @see Resources#setLoaders(List)
- */
- public void setLoaders(
- @Nullable List<Pair<ResourceLoader, ResourcesProvider>> newLoadersAndProviders) {
- synchronized (mLock) {
- if (ArrayUtils.isEmpty(newLoadersAndProviders)) {
- mResourceLoaders.clear();
- updateLoaders();
- return;
- }
-
- int size = newLoadersAndProviders.size();
- for (int newIndex = 0; newIndex < size; newIndex++) {
- ResourceLoader resourceLoader = newLoadersAndProviders.get(newIndex).first;
- for (int oldIndex = 0; oldIndex < mResourceLoaders.size(); oldIndex++) {
- if (Objects.equals(mResourceLoaders.get(oldIndex).first, resourceLoader)) {
- throw new IllegalArgumentException(
- "Cannot add the same ResourceLoader twice");
- }
- }
- }
-
- mResourceLoaders.clear();
- mResourceLoaders.addAll(newLoadersAndProviders);
-
- updateLoaders();
- }
- }
-
- /**
- * Swap the tracked {@link ResourcesImpl} and reattach any loaders to it.
- */
- public void onImplUpdate(ResourcesImpl resourcesImpl) {
- synchronized (mLock) {
- this.mResourcesImpl = resourcesImpl;
- this.mResourcesImpl.getAssets().setResourceLoaderManager(this);
- updateLoaders();
- }
- }
-
- private void updateLoaders() {
- synchronized (mLock) {
- AssetManager assetManager = mResourcesImpl.getAssets();
- ApkAssets[] existingApkAssets = assetManager.getApkAssets();
- int baseApkAssetsSize = 0;
- for (int index = existingApkAssets.length - 1; index >= 0; index--) {
- // Loaders are always last, so the first non-loader is the end of the base assets
- if (!existingApkAssets[index].isForLoader()) {
- baseApkAssetsSize = index + 1;
- break;
- }
- }
-
- List<ApkAssets> newAssets = new ArrayList<>();
- for (int index = 0; index < baseApkAssetsSize; index++) {
- newAssets.add(existingApkAssets[index]);
- }
-
- int size = mResourceLoaders.size();
- for (int index = 0; index < size; index++) {
- ApkAssets apkAssets = mResourceLoaders.get(index).second.getApkAssets();
- newAssets.add(apkAssets);
- }
-
- assetManager.setApkAssets(newAssets.toArray(new ApkAssets[0]), true);
-
- // Short of resolving every resource, it's too difficult to determine what has changed
- // when a resource loader is changed, so just clear everything.
- mResourcesImpl.clearAllCaches();
- }
- }
-}
diff --git a/core/java/android/content/res/loader/ResourcesLoader.java b/core/java/android/content/res/loader/ResourcesLoader.java
new file mode 100644
index 0000000..69dacee
--- /dev/null
+++ b/core/java/android/content/res/loader/ResourcesLoader.java
@@ -0,0 +1,253 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.res.loader;
+
+import android.annotation.NonNull;
+import android.content.res.ApkAssets;
+import android.content.res.Resources;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
+
+import java.lang.ref.WeakReference;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A container for supplying {@link ResourcesProvider ResourcesProvider(s)} to {@link Resources}
+ * objects.
+ *
+ * <p>{@link ResourcesLoader ResourcesLoader(s)} are added to Resources objects to supply
+ * additional resources and assets or modify the values of existing resources and assets. Multiple
+ * Resources objects can share the same ResourcesLoaders and ResourcesProviders. Changes to the list
+ * of {@link ResourcesProvider ResourcesProvider(s)} a loader contains propagates to all Resources
+ * objects that use the loader.
+ *
+ * <p>Loaders retrieved with {@link Resources#getLoaders()} are listed in increasing precedence
+ * order. A loader will override the resources and assets of loaders listed before itself.
+ *
+ * <p>Providers retrieved with {@link #getProviders()} are listed in increasing precedence order. A
+ * provider will override the resources and assets of providers listed before itself.
+ */
+public class ResourcesLoader {
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private ApkAssets[] mApkAssets;
+
+ @GuardedBy("mLock")
+ private ResourcesProvider[] mPreviousProviders;
+
+ @GuardedBy("mLock")
+ private ResourcesProvider[] mProviders;
+
+ @GuardedBy("mLock")
+ private ArrayMap<WeakReference<Object>, UpdateCallbacks> mChangeCallbacks = new ArrayMap<>();
+
+ /** @hide */
+ public interface UpdateCallbacks {
+
+ /**
+ * Invoked when a {@link ResourcesLoader} has a {@link ResourcesProvider} added, removed,
+ * or reordered.
+ *
+ * @param loader the loader that was updated
+ */
+ void onLoaderUpdated(@NonNull ResourcesLoader loader);
+ }
+
+ /**
+ * Retrieves the list of providers loaded into this instance. Providers are listed in increasing
+ * precedence order. A provider will override the values of providers listed before itself.
+ */
+ @NonNull
+ public List<ResourcesProvider> getProviders() {
+ synchronized (mLock) {
+ return mProviders == null ? Collections.emptyList() : Arrays.asList(mProviders);
+ }
+ }
+
+ /**
+ * Appends a provider to the end of the provider list. If the provider is already present in the
+ * loader list, the list will not be modified.
+ *
+ * @param resourcesProvider the provider to add
+ */
+ public void addProvider(@NonNull ResourcesProvider resourcesProvider) {
+ synchronized (mLock) {
+ mProviders = ArrayUtils.appendElement(ResourcesProvider.class, mProviders,
+ resourcesProvider);
+ notifyProvidersChangedLocked();
+ }
+ }
+
+ /**
+ * Removes a provider from the provider list. If the provider is not present in the provider
+ * list, the list will not be modified.
+ *
+ * @param resourcesProvider the provider to remove
+ */
+ public void removeProvider(@NonNull ResourcesProvider resourcesProvider) {
+ synchronized (mLock) {
+ mProviders = ArrayUtils.removeElement(ResourcesProvider.class, mProviders,
+ resourcesProvider);
+ notifyProvidersChangedLocked();
+ }
+ }
+
+ /**
+ * Sets the list of providers.
+ *
+ * @param resourcesProviders the new providers
+ */
+ public void setProviders(@NonNull List<ResourcesProvider> resourcesProviders) {
+ synchronized (mLock) {
+ mProviders = resourcesProviders.toArray(new ResourcesProvider[0]);
+ notifyProvidersChangedLocked();
+ }
+ }
+
+ /** Removes all {@link ResourcesProvider ResourcesProvider(s)}. */
+ public void clearProviders() {
+ synchronized (mLock) {
+ mProviders = null;
+ notifyProvidersChangedLocked();
+ }
+ }
+
+ /**
+ * Retrieves the list of {@link ApkAssets} used by the providers.
+ *
+ * @hide
+ */
+ @NonNull
+ public List<ApkAssets> getApkAssets() {
+ synchronized (mLock) {
+ if (mApkAssets == null) {
+ return Collections.emptyList();
+ }
+ return Arrays.asList(mApkAssets);
+ }
+ }
+
+ /**
+ * Registers a callback to be invoked when {@link ResourcesProvider ResourcesProvider(s)}
+ * change.
+ * @param instance the instance tied to the callback
+ * @param callbacks the callback to invoke
+ *
+ * @hide
+ */
+ public void registerOnProvidersChangedCallback(@NonNull Object instance,
+ @NonNull UpdateCallbacks callbacks) {
+ synchronized (mLock) {
+ mChangeCallbacks.put(new WeakReference<>(instance), callbacks);
+ }
+ }
+
+ /**
+ * Removes a previously registered callback.
+ * @param instance the instance tied to the callback
+ *
+ * @hide
+ */
+ public void unregisterOnProvidersChangedCallback(@NonNull Object instance) {
+ synchronized (mLock) {
+ for (int i = 0, n = mChangeCallbacks.size(); i < n; i++) {
+ final WeakReference<Object> key = mChangeCallbacks.keyAt(i);
+ if (instance == key.get()) {
+ mChangeCallbacks.removeAt(i);
+ return;
+ }
+ }
+ }
+ }
+
+ /** Returns whether the arrays contain the same provider instances in the same order. */
+ private static boolean arrayEquals(ResourcesProvider[] a1, ResourcesProvider[] a2) {
+ if (a1 == a2) {
+ return true;
+ }
+
+ if (a1 == null || a2 == null) {
+ return false;
+ }
+
+ if (a1.length != a2.length) {
+ return false;
+ }
+
+ // Check that the arrays contain the exact same instances in the same order. Providers do
+ // not have any form of equivalence checking of whether the contents of two providers have
+ // equivalent apk assets.
+ for (int i = 0, n = a1.length; i < n; i++) {
+ if (a1[i] != a2[i]) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+
+ /**
+ * Invokes registered callbacks when the list of {@link ResourcesProvider} instances this loader
+ * uses changes.
+ */
+ private void notifyProvidersChangedLocked() {
+ final ArraySet<UpdateCallbacks> uniqueCallbacks = new ArraySet<>();
+ if (arrayEquals(mPreviousProviders, mProviders)) {
+ return;
+ }
+
+ if (mProviders == null || mProviders.length == 0) {
+ mApkAssets = null;
+ } else {
+ mApkAssets = new ApkAssets[mProviders.length];
+ for (int i = 0, n = mProviders.length; i < n; i++) {
+ mProviders[i].incrementRefCount();
+ mApkAssets[i] = mProviders[i].getApkAssets();
+ }
+ }
+
+ // Decrement the ref count after incrementing the new provider ref count so providers
+ // present before and after this method do not drop to zero references.
+ if (mPreviousProviders != null) {
+ for (ResourcesProvider provider : mPreviousProviders) {
+ provider.decrementRefCount();
+ }
+ }
+
+ mPreviousProviders = mProviders;
+
+ for (int i = mChangeCallbacks.size() - 1; i >= 0; i--) {
+ final WeakReference<Object> key = mChangeCallbacks.keyAt(i);
+ if (key.get() == null) {
+ mChangeCallbacks.removeAt(i);
+ } else {
+ uniqueCallbacks.add(mChangeCallbacks.valueAt(i));
+ }
+ }
+
+ for (int i = 0, n = uniqueCallbacks.size(); i < n; i++) {
+ uniqueCallbacks.valueAt(i).onLoaderUpdated(this);
+ }
+ }
+}
diff --git a/core/java/android/content/res/loader/ResourcesProvider.java b/core/java/android/content/res/loader/ResourcesProvider.java
index 050aeb7..419ec78 100644
--- a/core/java/android/content/res/loader/ResourcesProvider.java
+++ b/core/java/android/content/res/loader/ResourcesProvider.java
@@ -17,84 +17,144 @@
package android.content.res.loader;
import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.res.ApkAssets;
-import android.content.res.Resources;
import android.os.ParcelFileDescriptor;
import android.os.SharedMemory;
+import android.util.Log;
+import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
import java.io.Closeable;
import java.io.IOException;
/**
- * Provides methods to load resources from an .apk or .arsc file to pass to
- * {@link Resources#addLoader(ResourceLoader, ResourcesProvider, int)}.
- *
- * It is the responsibility of the app to close any instances.
+ * Provides methods to load resources data from APKs ({@code .apk}) and resources tables
+ * {@code .arsc} for use with {@link ResourcesLoader ResourcesLoader(s)}.
*/
-public final class ResourcesProvider implements AutoCloseable, Closeable {
+public class ResourcesProvider implements AutoCloseable, Closeable {
+ private static final String TAG = "ResourcesProvider";
+ private final Object mLock = new Object();
+
+ @GuardedBy("mLock")
+ private boolean mOpen = true;
+
+ @GuardedBy("mLock")
+ private int mOpenCount = 0;
+
+ @GuardedBy("mLock")
+ private final ApkAssets mApkAssets;
+
+ private final AssetsProvider mAssetsProvider;
/**
- * Contains no data, assuming that any resource loading behavior will be handled in the
- * corresponding {@link ResourceLoader}.
+ * Creates an empty ResourcesProvider with no resource data. This is useful for loading assets
+ * that are not associated with resource identifiers.
+ *
+ * @param assetsProvider the assets provider that overrides the loading of file-based resources
*/
@NonNull
- public static ResourcesProvider empty() {
- return new ResourcesProvider(ApkAssets.loadEmptyForLoader());
+ public static ResourcesProvider empty(@NonNull AssetsProvider assetsProvider) {
+ return new ResourcesProvider(ApkAssets.loadEmptyForLoader(), assetsProvider);
}
/**
- * Read from an .apk file descriptor.
+ * Creates a ResourcesProvider from an APK ({@code .apk}) file descriptor.
*
- * The file descriptor is duplicated and the one passed in may be closed by the application
- * at any time.
+ * The file descriptor is duplicated and the original may be closed by the application at any
+ * time without affecting the ResourcesProvider.
+ *
+ * @param fileDescriptor the file descriptor of the APK to load
*/
@NonNull
public static ResourcesProvider loadFromApk(@NonNull ParcelFileDescriptor fileDescriptor)
throws IOException {
- return new ResourcesProvider(
- ApkAssets.loadApkForLoader(fileDescriptor.getFileDescriptor()));
+ return loadFromApk(fileDescriptor, null);
}
/**
- * Read from an .apk file representation in memory.
+ * Creates a ResourcesProvider from an APK ({@code .apk}) file descriptor.
+ *
+ * The file descriptor is duplicated and the original may be closed by the application at any
+ * time without affecting the ResourcesProvider.
+ *
+ * @param fileDescriptor the file descriptor of the APK to load
+ * @param assetsProvider the assets provider that overrides the loading of file-based resources
+ */
+ @NonNull
+ public static ResourcesProvider loadFromApk(@NonNull ParcelFileDescriptor fileDescriptor,
+ @Nullable AssetsProvider assetsProvider)
+ throws IOException {
+ return new ResourcesProvider(
+ ApkAssets.loadApkForLoader(fileDescriptor.getFileDescriptor()), assetsProvider);
+ }
+
+ /**
+ * Creates a ResourcesProvider from an {@code .apk} file representation in memory.
+ *
+ * @param sharedMemory the shared memory containing the data of the APK to load
*/
@NonNull
public static ResourcesProvider loadFromApk(@NonNull SharedMemory sharedMemory)
throws IOException {
- return new ResourcesProvider(
- ApkAssets.loadApkForLoader(sharedMemory.getFileDescriptor()));
+ return loadFromApk(sharedMemory, null);
}
/**
- * Read from an .arsc file descriptor.
+ * Creates a ResourcesProvider from an {@code .apk} file representation in memory.
*
- * The file descriptor is duplicated and the one passed in may be closed by the application
- * at any time.
+ * @param sharedMemory the shared memory containing the data of the APK to load
+ * @param assetsProvider the assets provider that implements the loading of file-based resources
*/
@NonNull
- public static ResourcesProvider loadFromArsc(@NonNull ParcelFileDescriptor fileDescriptor)
+ public static ResourcesProvider loadFromApk(@NonNull SharedMemory sharedMemory,
+ @Nullable AssetsProvider assetsProvider)
throws IOException {
return new ResourcesProvider(
- ApkAssets.loadArscForLoader(fileDescriptor.getFileDescriptor()));
+ ApkAssets.loadApkForLoader(sharedMemory.getFileDescriptor()), assetsProvider);
}
/**
- * Read from an .arsc file representation in memory.
+ * Creates a ResourcesProvider from a resources table ({@code .arsc}) file descriptor.
+ *
+ * The file descriptor is duplicated and the original may be closed by the application at any
+ * time without affecting the ResourcesProvider.
+ *
+ * @param fileDescriptor the file descriptor of the resources table to load
+ * @param assetsProvider the assets provider that implements the loading of file-based resources
*/
@NonNull
- public static ResourcesProvider loadFromArsc(@NonNull SharedMemory sharedMemory)
+ public static ResourcesProvider loadFromTable(@NonNull ParcelFileDescriptor fileDescriptor,
+ @Nullable AssetsProvider assetsProvider)
throws IOException {
return new ResourcesProvider(
- ApkAssets.loadArscForLoader(sharedMemory.getFileDescriptor()));
+ ApkAssets.loadArscForLoader(fileDescriptor.getFileDescriptor()), assetsProvider);
+ }
+
+ /**
+ * Creates a ResourcesProvider from a resources table ({@code .arsc}) file representation in
+ * memory.
+ *
+ * @param sharedMemory the shared memory containing the data of the resources table to load
+ * @param assetsProvider the assets provider that overrides the loading of file-based resources
+ */
+ @NonNull
+ public static ResourcesProvider loadFromTable(@NonNull SharedMemory sharedMemory,
+ @Nullable AssetsProvider assetsProvider)
+ throws IOException {
+ return new ResourcesProvider(
+ ApkAssets.loadArscForLoader(sharedMemory.getFileDescriptor()), assetsProvider);
}
/**
* Read from a split installed alongside the application, which may not have been
* loaded initially because the application requested isolated split loading.
+ *
+ * @param context a context of the package that contains the split
+ * @param splitName the name of the split to load
*/
@NonNull
public static ResourcesProvider loadFromSplit(@NonNull Context context,
@@ -106,15 +166,18 @@
}
String splitPath = appInfo.getSplitCodePaths()[splitIndex];
- return new ResourcesProvider(ApkAssets.loadApkForLoader(splitPath));
+ return new ResourcesProvider(ApkAssets.loadApkForLoader(splitPath), null);
}
-
- @NonNull
- private final ApkAssets mApkAssets;
-
- private ResourcesProvider(@NonNull ApkAssets apkAssets) {
+ private ResourcesProvider(@NonNull ApkAssets apkAssets,
+ @Nullable AssetsProvider assetsProvider) {
this.mApkAssets = apkAssets;
+ this.mAssetsProvider = assetsProvider;
+ }
+
+ @Nullable
+ public AssetsProvider getAssetsProvider() {
+ return mAssetsProvider;
}
/** @hide */
@@ -123,8 +186,41 @@
return mApkAssets;
}
+ final void incrementRefCount() {
+ synchronized (mLock) {
+ if (!mOpen) {
+ throw new IllegalStateException("Operation failed: resources provider is closed");
+ }
+ mOpenCount++;
+ }
+ }
+
+ final void decrementRefCount() {
+ synchronized (mLock) {
+ mOpenCount--;
+ }
+ }
+
+ /**
+ * Frees internal data structures. Closed providers can no longer be added to
+ * {@link ResourcesLoader ResourcesLoader(s)}.
+ *
+ * @throws IllegalStateException if provider is currently used by a ResourcesLoader
+ */
@Override
public void close() {
+ synchronized (mLock) {
+ if (!mOpen) {
+ return;
+ }
+
+ if (mOpenCount != 0) {
+ throw new IllegalStateException("Failed to close provider used by " + mOpenCount
+ + " ResourcesLoader instances");
+ }
+ mOpen = false;
+ }
+
try {
mApkAssets.close();
} catch (Throwable ignored) {
@@ -133,7 +229,16 @@
@Override
protected void finalize() throws Throwable {
- close();
- super.finalize();
+ synchronized (mLock) {
+ if (mOpenCount != 0) {
+ Log.w(TAG, "ResourcesProvider " + this + " finalized with non-zero refs: "
+ + mOpenCount);
+ }
+
+ if (mOpen) {
+ mOpen = false;
+ mApkAssets.close();
+ }
+ }
}
}
diff --git a/core/tests/ResourceLoaderTests/Android.bp b/core/tests/ResourceLoaderTests/Android.bp
index 53db832..fec4628 100644
--- a/core/tests/ResourceLoaderTests/Android.bp
+++ b/core/tests/ResourceLoaderTests/Android.bp
@@ -32,15 +32,16 @@
"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 702151d..d732132 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
index efd71ee..8102d15 100644
--- a/core/tests/ResourceLoaderTests/res/drawable-nodpi/non_asset_bitmap.png
+++ b/core/tests/ResourceLoaderTests/res/drawable-nodpi/non_asset_bitmap.png
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/res/layout/layout.xml b/core/tests/ResourceLoaderTests/res/layout/layout.xml
index d59059b..05499ed 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 885f681..8e05aef 100755
--- a/core/tests/ResourceLoaderTests/resources/compileAndLink.sh
+++ b/core/tests/ResourceLoaderTests/resources/compileAndLink.sh
@@ -68,9 +68,13 @@
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 @@
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 @@
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 @@
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
new file mode 100644
index 0000000..4eb8ca3
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapRed.png
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapWhite.png b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapWhite.png
new file mode 100644
index 0000000..e9a4cfc
--- /dev/null
+++ b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetBitmapWhite.png
Binary files differ
diff --git a/core/tests/ResourceLoaderTests/overlay/res/values/strings.xml b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableFour.xml
similarity index 78%
copy from core/tests/ResourceLoaderTests/overlay/res/values/strings.xml
copy to core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableFour.xml
index 348bb35..0623245 100644
--- a/core/tests/ResourceLoaderTests/overlay/res/values/strings.xml
+++ b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableFour.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.
@@ -15,8 +15,7 @@
~ limitations under the License.
-->
-<resources>
-
- <string name="loader_path_change_test">Overlaid</string>
-
-</resources>
+<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 f1a93d2..57a8cf1 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/overlay/res/values/strings.xml b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableThree.xml
similarity index 78%
copy from core/tests/ResourceLoaderTests/overlay/res/values/strings.xml
copy to core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableThree.xml
index 348bb35..41095d4 100644
--- a/core/tests/ResourceLoaderTests/overlay/res/values/strings.xml
+++ b/core/tests/ResourceLoaderTests/resources/res/drawable-nodpi/nonAssetDrawableThree.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.
@@ -15,8 +15,7 @@
~ limitations under the License.
-->
-<resources>
-
- <string name="loader_path_change_test">Overlaid</string>
-
-</resources>
+<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 7c455a5..333fe34 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/overlay/res/values/strings.xml b/core/tests/ResourceLoaderTests/resources/res/layout/layout_four.xml
similarity index 73%
copy from core/tests/ResourceLoaderTests/overlay/res/values/strings.xml
copy to core/tests/ResourceLoaderTests/resources/res/layout/layout_four.xml
index 348bb35..ab9e265 100644
--- a/core/tests/ResourceLoaderTests/overlay/res/values/strings.xml
+++ b/core/tests/ResourceLoaderTests/resources/res/layout/layout_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.
@@ -15,8 +15,9 @@
~ limitations under the License.
-->
-<resources>
+<TableLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ />
- <string name="loader_path_change_test">Overlaid</string>
-
-</resources>
diff --git a/core/tests/ResourceLoaderTests/overlay/res/values/strings.xml b/core/tests/ResourceLoaderTests/resources/res/layout/layout_three.xml
similarity index 73%
copy from core/tests/ResourceLoaderTests/overlay/res/values/strings.xml
copy to core/tests/ResourceLoaderTests/resources/res/layout/layout_three.xml
index 348bb35..d58d3db 100644
--- a/core/tests/ResourceLoaderTests/overlay/res/values/strings.xml
+++ b/core/tests/ResourceLoaderTests/resources/res/layout/layout_three.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.
@@ -15,8 +15,9 @@
~ limitations under the License.
-->
-<resources>
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ />
- <string name="loader_path_change_test">Overlaid</string>
-
-</resources>
diff --git a/core/tests/ResourceLoaderTests/overlay/res/values/strings.xml b/core/tests/ResourceLoaderTests/resources/res/values/dimen_four.xml
similarity index 79%
copy from core/tests/ResourceLoaderTests/overlay/res/values/strings.xml
copy to core/tests/ResourceLoaderTests/resources/res/values/dimen_four.xml
index 348bb35..5b30eba 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 69ecf23..b17ec1c 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/overlay/res/values/strings.xml b/core/tests/ResourceLoaderTests/resources/res/values/dimen_three.xml
similarity index 79%
rename from core/tests/ResourceLoaderTests/overlay/res/values/strings.xml
rename to core/tests/ResourceLoaderTests/resources/res/values/dimen_three.xml
index 348bb35..07a35ce 100644
--- a/core/tests/ResourceLoaderTests/overlay/res/values/strings.xml
+++ b/core/tests/ResourceLoaderTests/resources/res/values/dimen_three.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">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 4d55def..570b40a 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/overlay/res/values/strings.xml b/core/tests/ResourceLoaderTests/resources/res/values/string_four.xml
similarity index 77%
copy from core/tests/ResourceLoaderTests/overlay/res/values/strings.xml
copy to core/tests/ResourceLoaderTests/resources/res/values/string_four.xml
index 348bb35..8789bcd 100644
--- a/core/tests/ResourceLoaderTests/overlay/res/values/strings.xml
+++ b/core/tests/ResourceLoaderTests/resources/res/values/string_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="string" name="cancel" id="0x01040000" />
+ <string name="cancel">SomeRidiculouslyUnlikelyStringFour</string>
</resources>
diff --git a/core/tests/ResourceLoaderTests/overlay/res/values/strings.xml b/core/tests/ResourceLoaderTests/resources/res/values/string_three.xml
similarity index 77%
copy from core/tests/ResourceLoaderTests/overlay/res/values/strings.xml
copy to core/tests/ResourceLoaderTests/resources/res/values/string_three.xml
index 348bb35..82cd6ec 100644
--- a/core/tests/ResourceLoaderTests/overlay/res/values/strings.xml
+++ b/core/tests/ResourceLoaderTests/resources/res/values/string_three.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="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
similarity index 74%
rename from core/tests/ResourceLoaderTests/overlay/Android.bp
rename to core/tests/ResourceLoaderTests/splits/SplitFour/Android.bp
index 63e7e61..eb4d8e1 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
similarity index 79%
rename from core/tests/ResourceLoaderTests/overlay/AndroidManifest.xml
rename to core/tests/ResourceLoaderTests/splits/SplitFour/AndroidManifest.xml
index 942f7da..24a0a2a 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/overlay/res/values/strings.xml b/core/tests/ResourceLoaderTests/splits/SplitFour/res/values/string_split.xml
similarity index 77%
copy from core/tests/ResourceLoaderTests/overlay/res/values/strings.xml
copy to core/tests/ResourceLoaderTests/splits/SplitFour/res/values/string_split.xml
index 348bb35..4759db9 100644
--- a/core/tests/ResourceLoaderTests/overlay/res/values/strings.xml
+++ b/core/tests/ResourceLoaderTests/splits/SplitFour/res/values/string_split.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="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
similarity index 100%
rename from core/tests/ResourceLoaderTests/SplitOne/Android.bp
rename to core/tests/ResourceLoaderTests/splits/SplitOne/Android.bp
diff --git a/core/tests/ResourceLoaderTests/SplitOne/AndroidManifest.xml b/core/tests/ResourceLoaderTests/splits/SplitOne/AndroidManifest.xml
similarity index 100%
rename from core/tests/ResourceLoaderTests/SplitOne/AndroidManifest.xml
rename to 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
similarity index 100%
rename from core/tests/ResourceLoaderTests/SplitOne/res/values/string_split_one.xml
rename to core/tests/ResourceLoaderTests/splits/SplitOne/res/values/string_split.xml
diff --git a/core/tests/ResourceLoaderTests/overlay/Android.bp b/core/tests/ResourceLoaderTests/splits/SplitThree/Android.bp
similarity index 74%
copy from core/tests/ResourceLoaderTests/overlay/Android.bp
copy to core/tests/ResourceLoaderTests/splits/SplitThree/Android.bp
index 63e7e61..bf98a74 100644
--- a/core/tests/ResourceLoaderTests/overlay/Android.bp
+++ b/core/tests/ResourceLoaderTests/splits/SplitThree/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: "FrameworksResourceLoaderTestsSplitThree"
}
diff --git a/core/tests/ResourceLoaderTests/overlay/AndroidManifest.xml b/core/tests/ResourceLoaderTests/splits/SplitThree/AndroidManifest.xml
similarity index 79%
copy from core/tests/ResourceLoaderTests/overlay/AndroidManifest.xml
copy to core/tests/ResourceLoaderTests/splits/SplitThree/AndroidManifest.xml
index 942f7da..ae1579b 100644
--- a/core/tests/ResourceLoaderTests/overlay/AndroidManifest.xml
+++ b/core/tests/ResourceLoaderTests/splits/SplitThree/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_three"
>
+ <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/overlay/res/values/strings.xml b/core/tests/ResourceLoaderTests/splits/SplitThree/res/values/string_spli.xml
similarity index 77%
copy from core/tests/ResourceLoaderTests/overlay/res/values/strings.xml
copy to core/tests/ResourceLoaderTests/splits/SplitThree/res/values/string_spli.xml
index 348bb35..97682aa 100644
--- a/core/tests/ResourceLoaderTests/overlay/res/values/strings.xml
+++ b/core/tests/ResourceLoaderTests/splits/SplitThree/res/values/string_spli.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="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
similarity index 100%
rename from core/tests/ResourceLoaderTests/splits/Android.bp
rename to core/tests/ResourceLoaderTests/splits/SplitTwo/Android.bp
diff --git a/core/tests/ResourceLoaderTests/splits/AndroidManifest.xml b/core/tests/ResourceLoaderTests/splits/SplitTwo/AndroidManifest.xml
similarity index 100%
rename from core/tests/ResourceLoaderTests/splits/AndroidManifest.xml
rename to 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
similarity index 100%
rename from core/tests/ResourceLoaderTests/splits/res/values/string_split_two.xml
rename to 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
similarity index 68%
rename from core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryResourceLoaderTest.kt
rename to core/tests/ResourceLoaderTests/src/android/content/res/loader/test/DirectoryAssetsProviderTest.kt
index b1bdc96..9e94bdc 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.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 @@
@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 @@
@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 a6a8378..0000000
--- 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 0000000..e3ba93d
--- /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 0c3d34e..0000000
--- 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 09fd27e..0000000
--- 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 1ec2094..0000000
--- 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 5af453d..4c62955 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 @@
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 @@
@After
fun removeAllLoaders() {
- resources.setLoaders(null)
+ resources.clearLoaders()
+ context.applicationContext.resources.clearLoaders()
openedObjects.forEach {
try {
it.close()
@@ -69,149 +61,73 @@
}
}
- 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 017552a..0cc56d7 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 @@
@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 @@
{ 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 @@
{ assets.open("Asset.txt").reader().readText() },
"assetOne", { "assetOne" },
"assetTwo", { "assetTwo" },
+ "assetFour", { "assetFour" },
+ "assetThree", { "assetThree" },
listOf(DataType.ASSET)
)
@@ -71,6 +89,8 @@
{ assets.openFd("Asset.txt").readText() },
"assetOne", { "assetOne" },
"assetTwo", { "assetTwo" },
+ "assetFour", { "assetFour" },
+ "assetThree", { "assetThree" },
listOf(DataType.ASSET_FD)
)
@@ -79,14 +99,18 @@
{ 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 @@
(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 @@
{ getLayout(R.layout.layout).advanceToRoot().name },
"layoutOne", { "RelativeLayout" },
"layoutTwo", { "LinearLayout" },
+ "layoutThree", { "FrameLayout" },
+ "layoutFour", { "TableLayout" },
listOf(DataType.NON_ASSET_LAYOUT)
)
@@ -114,6 +142,8 @@
{ 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,19 +164,52 @@
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 addMultipleProviders() {
+ val originalValue = getValue()
+ val testOne = openOne()
+ val testTwo = openTwo()
+ val loader = ResourcesLoader()
+
+ resources.addLoader(loader)
+ loader.addProvider(testOne)
+ assertEquals(valueOne, getValue())
+
+ loader.addProvider(testTwo)
+ assertEquals(valueTwo, getValue())
+
+ loader.removeProvider(testOne)
+ assertEquals(valueTwo, getValue())
+
+ loader.removeProvider(testTwo)
+ assertEquals(originalValue, getValue())
}
@Test
@@ -154,201 +217,429 @@
val originalValue = getValue()
val testOne = openOne()
val testTwo = openTwo()
+ val loader1 = ResourcesLoader()
+ val loader2 = ResourcesLoader()
- addLoader(testOne, testTwo)
-
- assertEquals(valueTwo, getValue())
-
- removeLoader(testTwo)
-
+ resources.addLoader(loader1)
+ loader1.addProvider(testOne)
assertEquals(valueOne, getValue())
- removeLoader(testOne)
+ resources.addLoader(loader2)
+ loader2.addProvider(testTwo)
+ assertEquals(valueTwo, getValue())
+ resources.removeLoader(loader1)
+ assertEquals(valueTwo, getValue())
+
+ resources.removeLoader(loader2)
+ assertEquals(originalValue, getValue())
+ }
+
+ @Test
+ fun setMultipleProviders() {
+ val originalValue = getValue()
+ val testOne = openOne()
+ val testTwo = openTwo()
+ val loader = ResourcesLoader()
+
+ resources.addLoader(loader)
+ loader.providers = listOf(testOne, testTwo)
+ assertEquals(valueTwo, getValue())
+
+ loader.removeProvider(testTwo)
+ assertEquals(valueOne, getValue())
+
+ loader.providers = Collections.emptyList()
assertEquals(originalValue, getValue())
}
@Test
fun setMultipleLoaders() {
val originalValue = getValue()
- val testOne = openOne()
- val testTwo = openTwo()
+ val loader1 = ResourcesLoader()
+ loader1.addProvider(openOne())
+ val loader2 = ResourcesLoader()
+ loader2.addProvider(openTwo())
- setLoaders(testOne, testTwo)
-
+ resources.loaders = listOf(loader1, loader2)
assertEquals(valueTwo, getValue())
- removeLoader(testTwo)
-
+ resources.removeLoader(loader2)
assertEquals(valueOne, getValue())
- setLoaders()
-
+ resources.loaders = Collections.emptyList()
assertEquals(originalValue, getValue())
}
- @Test
- fun getLoadersContainsAll() {
+ @Test(expected = UnsupportedOperationException::class)
+ fun getProvidersDoesNotLeakMutability() {
val testOne = openOne()
- val testTwo = openTwo()
-
- addLoader(testOne, testTwo)
-
- assertThat(getLoaders()).containsAllOf(testOne, testTwo)
+ val loader = ResourcesLoader()
+ val providers = loader.providers
+ providers += testOne
}
- @Test
+ @Test(expected = UnsupportedOperationException::class)
fun getLoadersDoesNotLeakMutability() {
+ val loaders = resources.loaders
+ loaders += ResourcesLoader()
+ }
+
+ @Test
+ fun alreadyAddedProviderNoOps() {
+ val testOne = openOne()
+ val testTwo = openTwo()
+ val loader = ResourcesLoader()
+
+ resources.addLoader(loader)
+ loader.addProvider(testOne)
+ loader.addProvider(testTwo)
+ loader.addProvider(testOne)
+
+ assertEquals(2, loader.providers.size)
+ assertEquals(loader.providers[0], testOne)
+ assertEquals(loader.providers[1], testTwo)
+ }
+
+ @Test
+ 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)
+
+ loader.removeProvider(testOne)
+ loader.removeProvider(testOne)
+
+ assertEquals(1, loader.providers.size)
+ assertEquals(loader.providers[0], testTwo)
+ }
+
+ @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)
+ }
+
+ @Test
+ fun repeatedSetProvider() {
+ val testOne = openOne()
+ val testTwo = openTwo()
+ val loader = ResourcesLoader()
+
+ resources.addLoader(loader)
+ loader.providers = listOf(testOne, testTwo)
+ loader.providers = listOf(testOne, testTwo)
+
+ 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 reorderProviders() {
val originalValue = getValue()
val testOne = openOne()
val testTwo = openTwo()
+ val loader = ResourcesLoader()
- addLoader(testOne)
-
- assertEquals(valueOne, getValue())
-
- val loaders = getLoaders()
- loaders += testTwo
-
- assertEquals(valueOne, getValue())
-
- removeLoader(testOne)
-
- assertEquals(originalValue, getValue())
- }
-
- @Test(expected = IllegalArgumentException::class)
- fun alreadyAddedThrows() {
- val testOne = openOne()
- val testTwo = openTwo()
-
- addLoader(testOne)
- addLoader(testTwo)
- addLoader(testOne)
- }
-
- @Test(expected = IllegalArgumentException::class)
- fun alreadyAddedAndSetThrows() {
- val testOne = openOne()
- val testTwo = openTwo()
-
- addLoader(testOne)
- addLoader(testTwo)
- setLoaders(testTwo)
- }
-
- @Test
- fun repeatedRemoveSucceeds() {
- val originalValue = getValue()
- val testOne = openOne()
-
- addLoader(testOne)
-
- assertNotEquals(originalValue, getValue())
-
- removeLoader(testOne)
-
- assertEquals(originalValue, getValue())
-
- removeLoader(testOne)
-
- assertEquals(originalValue, getValue())
- }
-
- @Test
- fun addToFront() {
- val testOne = openOne()
- val testTwo = openTwo()
-
- addLoader(testOne)
-
- assertEquals(valueOne, getValue())
-
- addLoader(testTwo, 0)
-
- assertEquals(valueOne, getValue())
-
- // Remove top loader, so previously added to front should now resolve
- removeLoader(testOne)
+ resources.addLoader(loader)
+ loader.addProvider(testOne)
+ loader.addProvider(testTwo)
assertEquals(valueTwo, getValue())
- }
- @Test
- fun addToEnd() {
- val testOne = openOne()
- val testTwo = openTwo()
-
- addLoader(testOne)
-
- assertEquals(valueOne, getValue())
-
- addLoader(testTwo, 1)
-
+ loader.removeProvider(testOne)
assertEquals(valueTwo, getValue())
- }
- @Test(expected = IndexOutOfBoundsException::class)
- fun addPastEnd() {
- val testOne = openOne()
- val testTwo = openTwo()
-
- addLoader(testOne)
-
+ loader.addProvider(testOne)
assertEquals(valueOne, getValue())
- addLoader(testTwo, 2)
- }
-
- @Test(expected = IndexOutOfBoundsException::class)
- fun addBeforeFront() {
- val testOne = openOne()
- val testTwo = openTwo()
-
- addLoader(testOne)
-
+ loader.removeProvider(testTwo)
assertEquals(valueOne, getValue())
- addLoader(testTwo, -1)
+ loader.removeProvider(testOne)
+ assertEquals(originalValue, getValue())
}
@Test
- fun reorder() {
+ 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, testTwo)
-
+ resources.addLoader(loader1)
+ resources.addLoader(loader2)
assertEquals(valueTwo, getValue())
- removeLoader(testOne)
-
+ resources.removeLoader(loader1)
assertEquals(valueTwo, getValue())
- addLoader(testOne)
-
+ resources.addLoader(loader1)
assertEquals(valueOne, getValue())
- removeLoader(testTwo)
-
+ resources.removeLoader(loader2)
assertEquals(valueOne, getValue())
- removeLoader(testOne)
-
+ resources.removeLoader(loader1)
assertEquals(originalValue, getValue())
}
+ @Test
+ fun reorderMultipleLoadersAndProviders() {
+ val testOne = openOne()
+ val testTwo = openTwo()
+ val testThree = openThree()
+ val testFour = openFour()
+
+ 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())
+
+ loader1.removeProvider(testOne)
+ assertEquals(valueFour, getValue())
+ }
+
+ private fun createContext(context: Context, id: Int): Context {
+ val overrideConfig = Configuration()
+ overrideConfig.orientation = Int.MAX_VALUE - id
+ return context.createConfigurationContext(overrideConfig)
+ }
+
+ @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))
+
+ // 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 loaderUpdatesAffectContexts() {
+ val originalValue = getValue()
+ val testOne = openOne()
+ val testTwo = openTwo()
+ val loader = ResourcesLoader()
+
+ resources.addLoader(loader)
+ loader.addProvider(testOne)
+ assertEquals(valueOne, getValue())
+
+ 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))
+
+ // Changes to the loaders for a context do not affect providers.
+ resources.clearLoaders()
+ assertEquals(originalValue, getValue())
+ assertEquals(valueTwo, getValue(childContext))
+
+ val childContext2 = createContext(context, 1)
+ assertEquals(originalValue, getValue())
+ assertEquals(originalValue, getValue(childContext2))
+
+ childContext2.resources.addLoader(loader)
+ assertEquals(originalValue, getValue())
+ assertEquals(valueTwo, getValue(childContext))
+ assertEquals(valueTwo, getValue(childContext2))
+ }
+
+ @Test
+ fun appLoadersIncludedInActivityContexts() {
+ val loader = ResourcesLoader()
+ loader.addProvider(openOne())
+
+ val applicationContext = context.applicationContext
+ applicationContext.resources.addLoader(loader)
+ assertEquals(valueOne, getValue(applicationContext))
+
+ 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())
+
+ assertEquals(2, resources.assets.apkAssets.count { apkAssets -> apkAssets.isForLoader })
+ }
+
+ @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 df2d09a..4e8ee5c 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.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 a2dab99..df5c9d2 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 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 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 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 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 @@
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 @@
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 @@
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 @@
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 @@
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 d211cfd..c6ccd4a 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 @@
mDisplayContent.getDisplayId(),
null /* overrideConfig */,
uiContext.getResources().getCompatibilityInfo(),
- null /* classLoader */);
+ null /* classLoader */,
+ null /* loaders */);
}
@VisibleForTesting