private final ArrayList<WeakReference<Resources>> mResourceReferences = new ArrayList<>();
/**
- * Each Activity may have only one Resources object.
+ * Resources and base configuration override associated with an Activity.
*/
- private final WeakHashMap<IBinder, WeakReference<Resources>> mActivityResourceReferences =
+ private static class ActivityResources {
+ public final Configuration overrideConfig = new Configuration();
+ public final ArrayList<WeakReference<Resources>> activityResources = new ArrayList<>();
+ }
+
+ /**
+ * Each Activity may has a base override configuration that is applied to each Resources object,
+ * which in turn may have their own override configuration specified.
+ */
+ private final WeakHashMap<IBinder, ActivityResources> mActivityResourceReferences =
new WeakHashMap<>();
/**
}
public Configuration getConfiguration() {
- return mResConfiguration;
+ synchronized (this) {
+ return mResConfiguration;
+ }
}
- DisplayMetrics getDisplayMetricsLocked() {
- return getDisplayMetricsLocked(Display.DEFAULT_DISPLAY);
+ DisplayMetrics getDisplayMetrics() {
+ return getDisplayMetrics(Display.DEFAULT_DISPLAY);
}
/**
* Protected so that tests can override and returns something a fixed value.
*/
@VisibleForTesting
- protected DisplayMetrics getDisplayMetricsLocked(int displayId) {
+ protected DisplayMetrics getDisplayMetrics(int displayId) {
DisplayMetrics dm = new DisplayMetrics();
final Display display =
getAdjustedDisplay(displayId, DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
return config;
}
-
private ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) {
AssetManager assets = createAssetManager(key);
- DisplayMetrics dm = getDisplayMetricsLocked(key.mDisplayId);
+ DisplayMetrics dm = getDisplayMetrics(key.mDisplayId);
Configuration config = generateConfig(key, dm);
ResourcesImpl impl = new ResourcesImpl(assets, dm, config, key.mCompatInfo);
if (DEBUG) {
* @param key The key to match.
* @return a ResourcesImpl if the key matches a cache entry, null otherwise.
*/
- private ResourcesImpl findResourcesImplForKey(@NonNull ResourcesKey key) {
+ private ResourcesImpl findResourcesImplForKeyLocked(@NonNull ResourcesKey key) {
WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.get(key);
ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
if (impl != null && impl.getAssets().isUpToDate()) {
* Find the ResourcesKey that this ResourcesImpl object is associated with.
* @return the ResourcesKey or null if none was found.
*/
- private ResourcesKey findKeyForResourceImpl(@NonNull ResourcesImpl resourceImpl) {
+ private ResourcesKey findKeyForResourceImplLocked(@NonNull ResourcesImpl resourceImpl) {
final int refCount = mResourceImpls.size();
for (int i = 0; i < refCount; i++) {
WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
return null;
}
+ private ActivityResources getOrCreateActivityResourcesStructLocked(
+ @NonNull IBinder activityToken) {
+ ActivityResources activityResources = mActivityResourceReferences.get(activityToken);
+ if (activityResources == null) {
+ activityResources = new ActivityResources();
+ mActivityResourceReferences.put(activityToken, activityResources);
+ }
+ return activityResources;
+ }
+
/**
* Gets an existing Resources object tied to this Activity, or creates one if it doesn't exist
* or the class loader is different.
*/
private Resources getOrCreateResourcesForActivityLocked(@NonNull IBinder activityToken,
@NonNull ClassLoader classLoader, @NonNull ResourcesImpl impl) {
- // This is a request tied to an Activity, meaning we will need to update all
- // Activity related Resources to match this configuration.
- WeakReference<Resources> weakResourceRef = mActivityResourceReferences.get(activityToken);
- Resources resources = weakResourceRef != null ? weakResourceRef.get() : null;
- if (resources == null || !Objects.equals(resources.getClassLoader(), classLoader)) {
- resources = new Resources(classLoader);
- mActivityResourceReferences.put(activityToken, new WeakReference<>(resources));
- if (DEBUG) {
- Slog.d(TAG, "- creating new ref=" + resources);
- }
- } else {
- if (DEBUG) {
- Slog.d(TAG, "- using existing ref=" + resources);
- }
- }
+ final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
+ activityToken);
- if (resources.getImpl() != impl) {
- if (DEBUG) {
- Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
+ final int refCount = activityResources.activityResources.size();
+ for (int i = 0; i < refCount; i++) {
+ WeakReference<Resources> weakResourceRef = activityResources.activityResources.get(i);
+ Resources resources = weakResourceRef.get();
+
+ if (resources != null
+ && Objects.equals(resources.getClassLoader(), classLoader)
+ && resources.getImpl() == impl) {
+ if (DEBUG) {
+ Slog.d(TAG, "- using existing ref=" + resources);
+ }
+ return resources;
}
+ }
- // Setting an impl is expensive because we update all ThemeImpl references.
- // too.
- resources.setImpl(impl);
+ Resources resources = new Resources(classLoader);
+ resources.setImpl(impl);
+ activityResources.activityResources.add(new WeakReference<>(resources));
+ if (DEBUG) {
+ Slog.d(TAG, "- creating new ref=" + resources);
+ Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
}
return resources;
}
final int refCount = mResourceReferences.size();
for (int i = 0; i < refCount; i++) {
WeakReference<Resources> weakResourceRef = mResourceReferences.get(i);
- Resources resources = weakResourceRef != null ? weakResourceRef.get() : null;
+ Resources resources = weakResourceRef.get();
if (resources != null &&
Objects.equals(resources.getClassLoader(), classLoader) &&
resources.getImpl() == impl) {
}
/**
- * Gets or creates a new Resources object associated with the IBinder token. References returned
- * by this method live as long as the Activity, meaning they can be cached and used by the
- * Activity even after a configuration change. If any other parameter is changed
- * (resDir, splitResDirs, overrideConfig) for a given Activity, the same Resources object
- * is updated and handed back to the caller. However, changing the class loader will result in a
- * new Resources object.
- * <p/>
- * If activityToken is null, a cached Resources object will be returned if it matches the
- * input parameters. Otherwise a new Resources object that satisfies these parameters is
- * returned.
+ * 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
+ * configurations merged with the one specified here.
*
- * @param activityToken Represents an Activity. If null, global resources are assumed.
+ * @param activityToken Represents an Activity.
* @param resDir The base resource path. Can be null (only framework resources will be loaded).
* @param splitResDirs An array of split resource paths. Can be null.
* @param overlayDirs An array of overlay paths. Can be null.
* @param libDirs An array of resource library paths. Can be null.
* @param displayId The ID of the display for which to create the resources.
* @param overrideConfig The configuration to apply on top of the base configuration. Can be
- * null. Mostly used with Activities that are in multi-window which may override width and
- * height properties from the base config.
+ * null. This provides the base override for this Activity.
* @param compatInfo The compatibility settings to use. Cannot be null. A default to use is
- * {@link CompatibilityInfo#DEFAULT_COMPATIBILITY_INFO}.
+ * {@link CompatibilityInfo#DEFAULT_COMPATIBILITY_INFO}.
* @param classLoader The class loader to use when inflating Resources. If null, the
- * {@link ClassLoader#getSystemClassLoader()} is used.
+ * {@link ClassLoader#getSystemClassLoader()} is used.
* @return a Resources object from which to access resources.
*/
- public Resources getResources(@Nullable IBinder activityToken,
+ public Resources createBaseActivityResources(@NonNull IBinder activityToken,
@Nullable String resDir,
@Nullable String[] splitResDirs,
@Nullable String[] overlayDirs,
displayId,
overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
compatInfo);
-
classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
+ synchronized (this) {
+ final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
+ activityToken);
+
+ if (overrideConfig != null) {
+ activityResources.overrideConfig.setTo(overrideConfig);
+ } else {
+ activityResources.overrideConfig.setToDefaults();
+ }
+ }
+
+ // Update any existing Activity Resources references.
+ updateResourcesForActivity(activityToken, overrideConfig);
+
+ // Now request an actual Resources object.
+ return getOrCreateResources(activityToken, key, classLoader);
+ }
+
+ /**
+ * Gets an existing Resources object set with a ResourcesImpl object matching the given key,
+ * or creates one if it doesn't exist.
+ *
+ * @param activityToken The Activity this Resources object should be associated with.
+ * @param key The key describing the parameters of the ResourcesImpl object.
+ * @param classLoader The classloader to use for the Resources object.
+ * If null, {@link ClassLoader#getSystemClassLoader()} is used.
+ * @return A Resources object that gets updated when
+ * {@link #applyConfigurationToResourcesLocked(Configuration, CompatibilityInfo)}
+ * is called.
+ */
+ private Resources getOrCreateResources(@Nullable IBinder activityToken,
+ @NonNull ResourcesKey key, @NonNull ClassLoader classLoader) {
final boolean findSystemLocales;
final boolean hasNonSystemLocales;
synchronized (this) {
}
if (activityToken != null) {
- ResourcesImpl resourcesImpl = findResourcesImplForKey(key);
+ final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
+ activityToken);
+
+ // Clean up any dead references so they don't pile up.
+ ArrayUtils.unstableRemoveIf(activityResources.activityResources,
+ sEmptyReferencePredicate);
+
+ // Rebase the key's override config on top of the Activity's base override.
+ if (key.hasOverrideConfiguration()
+ && !activityResources.overrideConfig.equals(Configuration.EMPTY)) {
+ final Configuration temp = new Configuration(activityResources.overrideConfig);
+ temp.updateFrom(key.mOverrideConfiguration);
+ key.mOverrideConfiguration.setTo(temp);
+ }
+
+ ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
if (resourcesImpl != null) {
if (DEBUG) {
Slog.d(TAG, "- using existing impl=" + resourcesImpl);
ArrayUtils.unstableRemoveIf(mResourceReferences, sEmptyReferencePredicate);
// Not tied to an Activity, find a shared Resources that has the right ResourcesImpl
- ResourcesImpl resourcesImpl = findResourcesImplForKey(key);
+ ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(key);
if (resourcesImpl != null) {
if (DEBUG) {
Slog.d(TAG, "- using existing impl=" + resourcesImpl);
mNonSystemLocales.addAll(Arrays.asList(nonSystemLocales));
mHasNonSystemLocales = mHasNonSystemLocales || !isPseudoLocalesOnly;
- ResourcesImpl existingResourcesImpl = findResourcesImplForKey(key);
+ ResourcesImpl existingResourcesImpl = findResourcesImplForKeyLocked(key);
if (existingResourcesImpl != null) {
if (DEBUG) {
Slog.d(TAG, "- got beat! existing impl=" + existingResourcesImpl
}
/**
+ * Gets or creates a new Resources object associated with the IBinder token. References returned
+ * by this method live as long as the Activity, meaning they can be cached and used by the
+ * Activity even after a configuration change. If any other parameter is changed
+ * (resDir, splitResDirs, overrideConfig) for a given Activity, the same Resources object
+ * is updated and handed back to the caller. However, changing the class loader will result in a
+ * new Resources object.
+ * <p/>
+ * If activityToken is null, a cached Resources object will be returned if it matches the
+ * input parameters. Otherwise a new Resources object that satisfies these parameters is
+ * returned.
+ *
+ * @param activityToken Represents an Activity. If null, global resources are assumed.
+ * @param resDir The base resource path. Can be null (only framework resources will be loaded).
+ * @param splitResDirs An array of split resource paths. Can be null.
+ * @param overlayDirs An array of overlay paths. Can be null.
+ * @param libDirs An array of resource library paths. Can be null.
+ * @param displayId The ID of the display for which to create the resources.
+ * @param overrideConfig The configuration to apply on top of the base configuration. Can be
+ * null. Mostly used with Activities that are in multi-window which may override width and
+ * height properties from the base config.
+ * @param compatInfo The compatibility settings to use. Cannot be null. A default to use is
+ * {@link CompatibilityInfo#DEFAULT_COMPATIBILITY_INFO}.
+ * @param classLoader The class loader to use when inflating Resources. If null, the
+ * {@link ClassLoader#getSystemClassLoader()} is used.
+ * @return a Resources object from which to access resources.
+ */
+ public Resources getResources(@Nullable IBinder activityToken,
+ @Nullable String resDir,
+ @Nullable String[] splitResDirs,
+ @Nullable String[] overlayDirs,
+ @Nullable String[] libDirs,
+ int displayId,
+ @Nullable Configuration overrideConfig,
+ @NonNull CompatibilityInfo compatInfo,
+ @Nullable ClassLoader classLoader) {
+ final ResourcesKey key = new ResourcesKey(
+ resDir,
+ splitResDirs,
+ overlayDirs,
+ libDirs,
+ displayId,
+ overrideConfig != null ? new Configuration(overrideConfig) : null, // Copy
+ compatInfo);
+ classLoader = classLoader != null ? classLoader : ClassLoader.getSystemClassLoader();
+ return getOrCreateResources(activityToken, key, classLoader);
+ }
+
+ /**
* 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,
*/
public void updateResourcesForActivity(@NonNull IBinder activityToken,
@Nullable Configuration overrideConfig) {
- final ClassLoader classLoader;
- final ResourcesKey oldKey;
synchronized (this) {
- // Extract the ResourcesKey that was last used to create the Resources for this
- // activity.
- WeakReference<Resources> weakResRef = mActivityResourceReferences.get(activityToken);
- final Resources resources = weakResRef != null ? weakResRef.get() : null;
- if (resources == null) {
- Slog.e(TAG, "can't update resources for uncached activity " + activityToken);
+ final ActivityResources activityResources = getOrCreateActivityResourcesStructLocked(
+ activityToken);
+
+ if (Objects.equals(activityResources.overrideConfig, overrideConfig)) {
+ // They are the same, no work to do.
return;
}
- classLoader = resources.getClassLoader();
- oldKey = findKeyForResourceImpl(resources.getImpl());
- if (oldKey == null) {
- Slog.e(TAG, "can't find ResourcesKey for resources impl=" + resources.getImpl());
- return;
+ // Grab a copy of the old configuration so we can create the delta's of each
+ // Resources object associated with this Activity.
+ final Configuration oldConfig = new Configuration(activityResources.overrideConfig);
+
+ // Update the Activity's base override.
+ if (overrideConfig != null) {
+ activityResources.overrideConfig.setTo(overrideConfig);
+ } else {
+ activityResources.overrideConfig.setToDefaults();
}
- }
- // Update the Resources object with the new override config and all of the existing
- // settings.
- getResources(activityToken, oldKey.mResDir, oldKey.mSplitResDirs, oldKey.mOverlayDirs,
- oldKey.mLibDirs, oldKey.mDisplayId, overrideConfig, oldKey.mCompatInfo,
- classLoader);
+ final boolean activityHasOverrideConfig =
+ !activityResources.overrideConfig.equals(Configuration.EMPTY);
+
+ // Rebase each Resources associated with this Activity.
+ final int refCount = activityResources.activityResources.size();
+ for (int i = 0; i < refCount; i++) {
+ WeakReference<Resources> weakResRef = activityResources.activityResources.get(i);
+ Resources resources = weakResRef.get();
+ if (resources == null) {
+ continue;
+ }
+
+ // Extract the ResourcesKey that was last used to create the Resources for this
+ // activity.
+ final ResourcesKey oldKey = findKeyForResourceImplLocked(resources.getImpl());
+ if (oldKey == null) {
+ Slog.e(TAG, "can't find ResourcesKey for resources impl="
+ + resources.getImpl());
+ continue;
+ }
+
+ // Build the new override configuration for this ResourcesKey.
+ final Configuration rebasedOverrideConfig = new Configuration();
+ if (overrideConfig != null) {
+ rebasedOverrideConfig.setTo(overrideConfig);
+ }
+
+ if (activityHasOverrideConfig && oldKey.hasOverrideConfiguration()) {
+ // Generate a delta between the old base Activity override configuration and
+ // the actual final override configuration that was used to figure out the real
+ // delta this Resources object wanted.
+ Configuration overrideOverrideConfig = Configuration.generateDelta(
+ oldConfig, oldKey.mOverrideConfiguration);
+ rebasedOverrideConfig.updateFrom(overrideOverrideConfig);
+ }
+
+ // Create the new ResourcesKey with the rebased override config.
+ final ResourcesKey newKey = new ResourcesKey(oldKey.mResDir, oldKey.mSplitResDirs,
+ oldKey.mOverlayDirs, oldKey.mLibDirs, oldKey.mDisplayId,
+ rebasedOverrideConfig, oldKey.mCompatInfo);
+
+ ResourcesImpl resourcesImpl = findResourcesImplForKeyLocked(newKey);
+ if (resourcesImpl == null) {
+ resourcesImpl = createResourcesImpl(newKey);
+ }
+
+ if (resourcesImpl != resources.getImpl()) {
+ // Set the ResourcesImpl, updating it for all users of this Resources object.
+ resources.setImpl(resourcesImpl);
+ }
+ }
+ }
}
/* package */ void setDefaultLocalesLocked(@NonNull LocaleList locales) {
int changes = mResConfiguration.updateFrom(config);
// Things might have changed in display manager, so clear the cached displays.
mDisplays.clear();
- DisplayMetrics defaultDisplayMetrics = getDisplayMetricsLocked();
+ DisplayMetrics defaultDisplayMetrics = getDisplayMetrics();
if (compat != null && (mResCompatibilityInfo == null ||
!mResCompatibilityInfo.equals(compat))) {
}
tmpConfig.setTo(localeAdjustedConfig);
if (!isDefaultDisplay) {
- dm = getDisplayMetricsLocked(displayId);
+ dm = getDisplayMetrics(displayId);
applyNonDefaultDisplayMetricsToConfiguration(dm, tmpConfig);
}
if (hasOverrideConfiguration) {
import android.annotation.NonNull;
import android.app.ResourcesManager;
import android.os.Binder;
-import android.test.suitebuilder.annotation.SmallTest;
+import android.support.test.filters.SmallTest;
import android.util.DisplayMetrics;
import android.util.LocaleList;
import android.util.TypedValue;
}
@Override
- protected DisplayMetrics getDisplayMetricsLocked(int displayId) {
+ protected DisplayMetrics getDisplayMetrics(int displayId) {
return mDisplayMetrics;
}
};
// The implementations should be the same.
assertSame(resources1.getImpl(), resources2.getImpl());
-
- final Configuration overrideConfig = new Configuration();
- overrideConfig.orientation = Configuration.ORIENTATION_LANDSCAPE;
- Resources resources3 = mResourcesManager.getResources(
- activity2, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY,
- overrideConfig, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
-
- // Since we requested new resources for activity2, the resource should be the same
- // as the one returned before for activity2.
- assertSame(resources2, resources3);
-
- // But the implementation has changed.
- assertNotSame(resources1.getImpl(), resources2.getImpl());
}
@SmallTest
public void testThemesGetUpdatedWithNewImpl() {
Binder activity1 = new Binder();
- Resources resources1 = mResourcesManager.getResources(
+ Resources resources1 = mResourcesManager.createBaseActivityResources(
activity1, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null,
CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
assertNotNull(resources1);
final Configuration overrideConfig = new Configuration();
overrideConfig.orientation = Configuration.ORIENTATION_LANDSCAPE;
- Resources resources2 = mResourcesManager.getResources(
- activity1, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY,
- overrideConfig, CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
- assertNotNull(resources2);
- assertSame(resources1, resources2);
- assertSame(resources2, theme.getResources());
+ mResourcesManager.updateResourcesForActivity(activity1, overrideConfig);
+ assertSame(resources1, theme.getResources());
// Make sure we can still access the data.
assertTrue(theme.resolveAttribute(android.R.attr.windowNoTitle, value, true));
assertEquals(TypedValue.TYPE_INT_BOOLEAN, value.type);
assertTrue(value.data != 0);
}
+
+ @SmallTest
+ public void testMultipleResourcesForOneActivityGetUpdatedWhenActivityBaseUpdates() {
+ Binder activity1 = new Binder();
+
+ // Create a Resources for the Activity.
+ Configuration config1 = new Configuration();
+ config1.densityDpi = 280;
+ Resources resources1 = mResourcesManager.createBaseActivityResources(
+ activity1, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, config1,
+ CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
+ assertNotNull(resources1);
+
+ // Create a Resources based on the Activity.
+ Configuration config2 = new Configuration();
+ 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);
+ assertNotNull(resources2);
+
+ assertNotSame(resources1, resources2);
+ assertNotSame(resources1.getImpl(), resources2.getImpl());
+
+ final Configuration expectedConfig1 = new Configuration();
+ expectedConfig1.setLocales(LocaleList.getAdjustedDefault());
+ expectedConfig1.densityDpi = 280;
+ assertEquals(expectedConfig1, resources1.getConfiguration());
+
+ // resources2 should be based on the Activity's override config, so the density should
+ // be the same as resources1.
+ final Configuration expectedConfig2 = new Configuration();
+ expectedConfig2.setLocales(LocaleList.getAdjustedDefault());
+ expectedConfig2.densityDpi = 280;
+ expectedConfig2.screenLayout |= Configuration.SCREENLAYOUT_ROUND_YES;
+ assertEquals(expectedConfig2, resources2.getConfiguration());
+
+ // Now update the Activity base override, and both resources should update.
+ config1.orientation = Configuration.ORIENTATION_LANDSCAPE;
+ mResourcesManager.updateResourcesForActivity(activity1, config1);
+
+ expectedConfig1.orientation = Configuration.ORIENTATION_LANDSCAPE;
+ assertEquals(expectedConfig1, resources1.getConfiguration());
+
+ expectedConfig2.orientation = Configuration.ORIENTATION_LANDSCAPE;
+ assertEquals(expectedConfig2, resources2.getConfiguration());
+ }
}