Cache per-Activity Resources objects

Each Activity has a Resources object whose underlying state changes
when a configuration change occurs, both global and for that activity only (multi-window).
Views and other clients of the Resources object can safely cache it knowing that
they always have a reference to the most up-to-date resources.

This applies to Resources.Theme objects as well.

Bug:26854894
Change-Id: Ifd3da5a1086e17f747dfc265a447fe92a3a5c692
This commit is contained in:
Adam Lesinski
2016-03-04 14:33:47 -08:00
parent 0dd17c8406
commit 082614c6a5
12 changed files with 1064 additions and 265 deletions

View File

@@ -1793,24 +1793,9 @@ public final class ActivityThread {
* Resources if one has already been created. * Resources if one has already been created.
*/ */
Resources getTopLevelResources(String resDir, String[] splitResDirs, String[] overlayDirs, Resources getTopLevelResources(String resDir, String[] splitResDirs, String[] overlayDirs,
String[] libDirs, int displayId, Configuration overrideConfiguration, String[] libDirs, int displayId, LoadedApk pkgInfo) {
LoadedApk pkgInfo) { return mResourcesManager.getResources(null, resDir, splitResDirs, overlayDirs, libDirs,
return mResourcesManager.getTopLevelResources(resDir, splitResDirs, overlayDirs, libDirs, displayId, null, pkgInfo.getCompatibilityInfo(), pkgInfo.getClassLoader());
displayId, overrideConfiguration, pkgInfo.getCompatibilityInfo(),
pkgInfo.getClassLoader());
}
/**
* Creates a new top level resources for the given package. Will always create a new
* Resources, regardless if one has already been created.
*/
Resources getNewTopLevelResources(String resDir, String[] splitResDirs, String[] overlayDirs,
String[] libDirs, int displayId, Configuration overrideConfiguration,
LoadedApk pkgInfo) {
mResourcesManager.removeTopLevelResources(
resDir, displayId, overrideConfiguration, pkgInfo.getCompatibilityInfo());
return getTopLevelResources(resDir, splitResDirs, overlayDirs, libDirs,
displayId, overrideConfiguration, pkgInfo);
} }
final Handler getHandler() { final Handler getHandler() {
@@ -2624,7 +2609,7 @@ public final class ActivityThread {
} }
ContextImpl appContext = ContextImpl.createActivityContext( ContextImpl appContext = ContextImpl.createActivityContext(
this, r.packageInfo, displayId, r.overrideConfig); this, r.packageInfo, r.token, displayId, r.overrideConfig);
appContext.setOuterContext(activity); appContext.setOuterContext(activity);
Context baseContext = appContext; Context baseContext = appContext;
@@ -3481,14 +3466,9 @@ public final class ActivityThread {
if (!r.activity.mFinished && willBeVisible if (!r.activity.mFinished && willBeVisible
&& r.activity.mDecor != null && !r.hideForNow) { && r.activity.mDecor != null && !r.hideForNow) {
if (r.newConfig != null) { if (r.newConfig != null) {
r.tmpConfig.setTo(r.newConfig); performConfigurationChangedForActivity(r, r.newConfig, REPORT_TO_ACTIVITY);
if (r.overrideConfig != null) {
r.tmpConfig.updateFrom(r.overrideConfig);
}
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Resuming activity " if (DEBUG_CONFIGURATION) Slog.v(TAG, "Resuming activity "
+ r.activityInfo.name + " with newConfig " + r.tmpConfig); + r.activityInfo.name + " with newConfig " + r.activity.mCurrentConfig);
performConfigurationChanged(r.activity, r.tmpConfig, REPORT_TO_ACTIVITY);
freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.tmpConfig));
r.newConfig = null; r.newConfig = null;
} }
if (localLOGV) Slog.v(TAG, "Resuming " + r + " with isForward=" if (localLOGV) Slog.v(TAG, "Resuming " + r + " with isForward="
@@ -3833,14 +3813,10 @@ public final class ActivityThread {
} }
} }
if (r.newConfig != null) { if (r.newConfig != null) {
r.tmpConfig.setTo(r.newConfig); performConfigurationChangedForActivity(r, r.newConfig, REPORT_TO_ACTIVITY);
if (r.overrideConfig != null) {
r.tmpConfig.updateFrom(r.overrideConfig);
}
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Updating activity vis " if (DEBUG_CONFIGURATION) Slog.v(TAG, "Updating activity vis "
+ r.activityInfo.name + " with new config " + r.tmpConfig); + r.activityInfo.name + " with new config "
performConfigurationChanged(r.activity, r.tmpConfig, REPORT_TO_ACTIVITY); + r.activity.mCurrentConfig);
freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.tmpConfig));
r.newConfig = null; r.newConfig = null;
} }
} else { } else {
@@ -4545,8 +4521,44 @@ public final class ActivityThread {
return callbacks; return callbacks;
} }
private static void performConfigurationChanged(ComponentCallbacks2 cb, Configuration config, /**
boolean reportToActivity) { * Updates the configuration for an Activity. The ActivityClientRecord's
* {@link ActivityClientRecord#overrideConfig} is used to compute the final Configuration for
* that Activity. {@link ActivityClientRecord#tmpConfig} is used as a temporary for delivering
* the updated Configuration.
* @param r ActivityClientRecord representing the Activity.
* @param newBaseConfig The new configuration to use. This may be augmented with
* {@link ActivityClientRecord#overrideConfig}.
* @param reportToActivity true if the change should be reported to the Activity's callback.
*/
private void performConfigurationChangedForActivity(ActivityClientRecord r,
Configuration newBaseConfig,
boolean reportToActivity) {
r.tmpConfig.setTo(newBaseConfig);
if (r.overrideConfig != null) {
r.tmpConfig.updateFrom(r.overrideConfig);
}
performConfigurationChanged(r.activity, r.token, r.tmpConfig, r.overrideConfig,
reportToActivity);
freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.tmpConfig));
}
/**
* Decides whether to update an Activity's configuration and whether to tell the
* Activity/Component about it.
* @param cb The component callback to notify of configuration change.
* @param activityToken The Activity binder token for which this configuration change happened.
* If the change is global, this is null.
* @param newConfig The new configuration.
* @param overrideConfig The override config that differentiates the Activity's configuration
* from the base global configuration.
* @param reportToActivity Notify the Activity of the change.
*/
private void performConfigurationChanged(ComponentCallbacks2 cb,
IBinder activityToken,
Configuration newConfig,
Configuration overrideConfig,
boolean reportToActivity) {
// Only for Activity objects, check that they actually call up to their // Only for Activity objects, check that they actually call up to their
// superclass implementation. ComponentCallbacks2 is an interface, so // superclass implementation. ComponentCallbacks2 is an interface, so
// we check the runtime type and act accordingly. // we check the runtime type and act accordingly.
@@ -4563,7 +4575,7 @@ public final class ActivityThread {
// If the new config is the same as the config this Activity // If the new config is the same as the config this Activity
// is already running with then don't bother calling // is already running with then don't bother calling
// onConfigurationChanged // onConfigurationChanged
int diff = activity.mCurrentConfig.diff(config); int diff = activity.mCurrentConfig.diff(newConfig);
if (diff != 0) { if (diff != 0) {
// If this activity doesn't handle any of the config changes then don't bother // If this activity doesn't handle any of the config changes then don't bother
// calling onConfigurationChanged as we're going to destroy it. // calling onConfigurationChanged as we're going to destroy it.
@@ -4578,21 +4590,31 @@ public final class ActivityThread {
} }
} }
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Config callback " + cb if (DEBUG_CONFIGURATION) {
+ ": shouldChangeConfig=" + shouldChangeConfig); Slog.v(TAG, "Config callback " + cb + ": shouldChangeConfig=" + shouldChangeConfig);
}
if (shouldChangeConfig) { if (shouldChangeConfig) {
if (activityToken != null) {
// We only update an Activity's configuration if this is not a global
// configuration change. This must also be done before the callback,
// or else we violate the contract that the new resources are available
// in {@link ComponentCallbacks2#onConfigurationChanged(Configuration)}.
mResourcesManager.updateResourcesForActivity(activityToken, overrideConfig);
}
if (reportToActivity) { if (reportToActivity) {
cb.onConfigurationChanged(config); cb.onConfigurationChanged(newConfig);
} }
if (activity != null) { if (activity != null) {
if (reportToActivity && !activity.mCalled) { if (reportToActivity && !activity.mCalled) {
throw new SuperNotCalledException( throw new SuperNotCalledException(
"Activity " + activity.getLocalClassName() + "Activity " + activity.getLocalClassName() +
" did not call through to super.onConfigurationChanged()"); " did not call through to super.onConfigurationChanged()");
} }
activity.mConfigChangeFlags = 0; activity.mConfigChangeFlags = 0;
activity.mCurrentConfig = new Configuration(config); activity.mCurrentConfig = new Configuration(newConfig);
} }
} }
} }
@@ -4609,7 +4631,8 @@ public final class ActivityThread {
mCompatConfiguration = new Configuration(); mCompatConfiguration = new Configuration();
} }
mCompatConfiguration.setTo(mConfiguration); mCompatConfiguration.setTo(mConfiguration);
if (mResourcesManager.applyCompatConfiguration(displayDensity, mCompatConfiguration)) { if (mResourcesManager.applyCompatConfigurationLocked(displayDensity,
mCompatConfiguration)) {
config = mCompatConfiguration; config = mCompatConfiguration;
} }
return config; return config;
@@ -4661,7 +4684,8 @@ public final class ActivityThread {
if (callbacks != null) { if (callbacks != null) {
final int N = callbacks.size(); final int N = callbacks.size();
for (int i=0; i<N; i++) { for (int i=0; i<N; i++) {
performConfigurationChanged(callbacks.get(i), config, REPORT_TO_ACTIVITY); performConfigurationChanged(callbacks.get(i), null, config, null,
REPORT_TO_ACTIVITY);
} }
} }
} }
@@ -4687,15 +4711,8 @@ public final class ActivityThread {
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle activity config changed: " if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle activity config changed: "
+ r.activityInfo.name + ", with callback=" + reportToActivity); + r.activityInfo.name + ", with callback=" + reportToActivity);
r.tmpConfig.setTo(mCompatConfiguration); r.overrideConfig = data.overrideConfig;
if (data.overrideConfig != null) { performConfigurationChangedForActivity(r, mCompatConfiguration, reportToActivity);
r.overrideConfig = data.overrideConfig;
r.tmpConfig.updateFrom(data.overrideConfig);
}
performConfigurationChanged(r.activity, r.tmpConfig, reportToActivity);
freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(mCompatConfiguration));
mSomeActivitiesChanged = true; mSomeActivitiesChanged = true;
} }
@@ -5032,21 +5049,25 @@ public final class ActivityThread {
*/ */
TimeZone.setDefault(null); TimeZone.setDefault(null);
/* synchronized (mResourcesManager) {
* Initialize the default locales in this process for the reasons we set the time zone. /*
* * Initialize the default locales in this process for the reasons we set the time zone.
* We do this through ResourcesManager, since we need to do locale negotiation. *
*/ * We do this through ResourcesManager, since we need to do locale negotiation.
mResourcesManager.setDefaultLocalesLocked(data.config.getLocales()); */
mResourcesManager.setDefaultLocalesLocked(data.config.getLocales());
/* /*
* Update the system configuration since its preloaded and might not * Update the system configuration since its preloaded and might not
* reflect configuration changes. The configuration object passed * reflect configuration changes. The configuration object passed
* in AppBindData can be safely assumed to be up to date * in AppBindData can be safely assumed to be up to date
*/ */
mResourcesManager.applyConfigurationToResourcesLocked(data.config, data.compatInfo); mResourcesManager.applyConfigurationToResourcesLocked(data.config, data.compatInfo);
mCurDefaultDisplayDpi = data.config.densityDpi; mCurDefaultDisplayDpi = data.config.densityDpi;
applyCompatConfiguration(mCurDefaultDisplayDpi);
// This calls mResourcesManager so keep it within the synchronized block.
applyCompatConfiguration(mCurDefaultDisplayDpi);
}
data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo); data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);

View File

@@ -1182,7 +1182,7 @@ public class ApplicationPackageManager extends PackageManager {
sameUid ? app.sourceDir : app.publicSourceDir, sameUid ? app.sourceDir : app.publicSourceDir,
sameUid ? app.splitSourceDirs : app.splitPublicSourceDirs, sameUid ? app.splitSourceDirs : app.splitPublicSourceDirs,
app.resourceDirs, app.sharedLibraryFiles, Display.DEFAULT_DISPLAY, app.resourceDirs, app.sharedLibraryFiles, Display.DEFAULT_DISPLAY,
null, mContext.mPackageInfo); mContext.mPackageInfo);
if (r != null) { if (r != null) {
return r; return r;
} }

View File

@@ -1994,9 +1994,10 @@ class ContextImpl extends Context {
} }
static ContextImpl createActivityContext(ActivityThread mainThread, static ContextImpl createActivityContext(ActivityThread mainThread,
LoadedApk packageInfo, int displayId, Configuration overrideConfiguration) { LoadedApk packageInfo, IBinder activityToken, int displayId,
Configuration overrideConfiguration) {
if (packageInfo == null) throw new IllegalArgumentException("packageInfo"); if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
return new ContextImpl(null, mainThread, packageInfo, null, null, 0, return new ContextImpl(null, mainThread, packageInfo, activityToken, null, 0,
null, overrideConfiguration, displayId); null, overrideConfiguration, displayId);
} }
@@ -2054,10 +2055,16 @@ class ContextImpl extends Context {
|| overrideConfiguration != null || overrideConfiguration != null
|| (compatInfo != null && compatInfo.applicationScale || (compatInfo != null && compatInfo.applicationScale
!= resources.getCompatibilityInfo().applicationScale)) { != resources.getCompatibilityInfo().applicationScale)) {
resources = mResourcesManager.getTopLevelResources(packageInfo.getResDir(), resources = mResourcesManager.getResources(
packageInfo.getSplitResDirs(), packageInfo.getOverlayDirs(), activityToken,
packageInfo.getApplicationInfo().sharedLibraryFiles, displayId, packageInfo.getResDir(),
overrideConfiguration, compatInfo, packageInfo.getClassLoader()); packageInfo.getSplitResDirs(),
packageInfo.getOverlayDirs(),
packageInfo.getApplicationInfo().sharedLibraryFiles,
displayId,
overrideConfiguration,
compatInfo,
packageInfo.getClassLoader());
} }
} }
mResources = resources; mResources = resources;

View File

@@ -283,9 +283,9 @@ public final class LoadedApk {
synchronized (this) { synchronized (this) {
mClassLoader = createOrUpdateClassLoaderLocked(addedPaths); mClassLoader = createOrUpdateClassLoaderLocked(addedPaths);
if (mResources != null) { if (mResources != null) {
mResources = mActivityThread.getNewTopLevelResources(mResDir, mSplitResDirs, mResources = mActivityThread.getTopLevelResources(mResDir, mSplitResDirs,
mOverlayDirs, mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY, mOverlayDirs, mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY,
null /*overrideConfiguration*/, this); this);
} }
} }
} }
@@ -668,7 +668,7 @@ public final class LoadedApk {
public Resources getResources(ActivityThread mainThread) { public Resources getResources(ActivityThread mainThread) {
if (mResources == null) { if (mResources == null) {
mResources = mainThread.getTopLevelResources(mResDir, mSplitResDirs, mOverlayDirs, mResources = mainThread.getTopLevelResources(mResDir, mSplitResDirs, mOverlayDirs,
mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY, null, this); mApplicationInfo.sharedLibraryFiles, Display.DEFAULT_DISPLAY, this);
} }
return mResources; return mResources;
} }

View File

@@ -18,6 +18,8 @@ package android.app;
import static android.app.ActivityThread.DEBUG_CONFIGURATION; import static android.app.ActivityThread.DEBUG_CONFIGURATION;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo;
import android.content.res.AssetManager; import android.content.res.AssetManager;
import android.content.res.CompatibilityInfo; import android.content.res.CompatibilityInfo;
@@ -26,6 +28,7 @@ import android.content.res.Resources;
import android.content.res.ResourcesImpl; import android.content.res.ResourcesImpl;
import android.content.res.ResourcesKey; import android.content.res.ResourcesKey;
import android.hardware.display.DisplayManagerGlobal; import android.hardware.display.DisplayManagerGlobal;
import android.os.IBinder;
import android.util.ArrayMap; import android.util.ArrayMap;
import android.util.DisplayMetrics; import android.util.DisplayMetrics;
import android.util.LocaleList; import android.util.LocaleList;
@@ -34,11 +37,16 @@ import android.util.Pair;
import android.util.Slog; import android.util.Slog;
import android.view.Display; import android.view.Display;
import android.view.DisplayAdjustments; import android.view.DisplayAdjustments;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.ArrayUtils;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.Objects;
import java.util.WeakHashMap;
import java.util.function.Predicate;
/** @hide */ /** @hide */
public class ResourcesManager { public class ResourcesManager {
@@ -46,18 +54,56 @@ public class ResourcesManager {
private static final boolean DEBUG = false; private static final boolean DEBUG = false;
private static ResourcesManager sResourcesManager; private static ResourcesManager sResourcesManager;
private final ArrayMap<ResourcesKey, WeakReference<Resources>> mActiveResources =
new ArrayMap<>(); /**
private final ArrayMap<Pair<Integer, DisplayAdjustments>, WeakReference<Display>> mDisplays = * Predicate that returns true if a WeakReference is gc'ed.
new ArrayMap<>(); */
private static final Predicate<WeakReference<Resources>> sEmptyReferencePredicate =
new Predicate<WeakReference<Resources>>() {
@Override
public boolean test(WeakReference<Resources> weakRef) {
return weakRef == null || weakRef.get() == null;
}
};
private String[] mSystemLocales = {}; private String[] mSystemLocales = {};
private final HashSet<String> mNonSystemLocales = new HashSet<String>(); private final HashSet<String> mNonSystemLocales = new HashSet<>();
private boolean mHasNonSystemLocales = false; private boolean mHasNonSystemLocales = false;
CompatibilityInfo mResCompatibilityInfo; /**
* The global compatibility settings.
*/
private CompatibilityInfo mResCompatibilityInfo;
Configuration mResConfiguration; /**
* The global configuration upon which all Resources are based. Multi-window Resources
* apply their overrides to this configuration.
*/
private final Configuration mResConfiguration = new Configuration();
/**
* A mapping of ResourceImpls and their configurations. These are heavy weight objects
* which should be reused as much as possible.
*/
private final ArrayMap<ResourcesKey, WeakReference<ResourcesImpl>> mResourceImpls =
new ArrayMap<>();
/**
* A list of Resource references that can be reused.
*/
private final ArrayList<WeakReference<Resources>> mResourceReferences = new ArrayList<>();
/**
* Each Activity may have only one Resources object.
*/
private final WeakHashMap<IBinder, WeakReference<Resources>> mActivityResourceReferences =
new WeakHashMap<>();
/**
* A cache of DisplayId to DisplayAdjustments.
*/
private final ArrayMap<Pair<Integer, DisplayAdjustments>, WeakReference<Display>> mDisplays =
new ArrayMap<>();
public static ResourcesManager getInstance() { public static ResourcesManager getInstance() {
synchronized (ResourcesManager.class) { synchronized (ResourcesManager.class) {
@@ -76,7 +122,11 @@ public class ResourcesManager {
return getDisplayMetricsLocked(Display.DEFAULT_DISPLAY); return getDisplayMetricsLocked(Display.DEFAULT_DISPLAY);
} }
DisplayMetrics getDisplayMetricsLocked(int displayId) { /**
* Protected so that tests can override and returns something a fixed value.
*/
@VisibleForTesting
protected DisplayMetrics getDisplayMetricsLocked(int displayId) {
DisplayMetrics dm = new DisplayMetrics(); DisplayMetrics dm = new DisplayMetrics();
final Display display = final Display display =
getAdjustedDisplay(displayId, DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS); getAdjustedDisplay(displayId, DisplayAdjustments.DEFAULT_DISPLAY_ADJUSTMENTS);
@@ -88,12 +138,12 @@ public class ResourcesManager {
return dm; return dm;
} }
final void applyNonDefaultDisplayMetricsToConfigurationLocked( private static void applyNonDefaultDisplayMetricsToConfiguration(
DisplayMetrics dm, Configuration config) { @NonNull DisplayMetrics dm, @NonNull Configuration config) {
config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH; config.touchscreen = Configuration.TOUCHSCREEN_NOTOUCH;
config.densityDpi = dm.densityDpi; config.densityDpi = dm.densityDpi;
config.screenWidthDp = (int)(dm.widthPixels / dm.density); config.screenWidthDp = (int) (dm.widthPixels / dm.density);
config.screenHeightDp = (int)(dm.heightPixels / dm.density); config.screenHeightDp = (int) (dm.heightPixels / dm.density);
int sl = Configuration.resetScreenLayout(config.screenLayout); int sl = Configuration.resetScreenLayout(config.screenLayout);
if (dm.widthPixels > dm.heightPixels) { if (dm.widthPixels > dm.heightPixels) {
config.orientation = Configuration.ORIENTATION_LANDSCAPE; config.orientation = Configuration.ORIENTATION_LANDSCAPE;
@@ -110,8 +160,8 @@ public class ResourcesManager {
config.compatSmallestScreenWidthDp = config.smallestScreenWidthDp; config.compatSmallestScreenWidthDp = config.smallestScreenWidthDp;
} }
public boolean applyCompatConfiguration(int displayDensity, public boolean applyCompatConfigurationLocked(int displayDensity,
Configuration compatConfiguration) { @NonNull Configuration compatConfiguration) {
if (mResCompatibilityInfo != null && !mResCompatibilityInfo.supportsScreen()) { if (mResCompatibilityInfo != null && !mResCompatibilityInfo.supportsScreen()) {
mResCompatibilityInfo.applyToConfiguration(displayDensity, compatConfiguration); mResCompatibilityInfo.applyToConfiguration(displayDensity, compatConfiguration);
return true; return true;
@@ -126,7 +176,8 @@ public class ResourcesManager {
* @param displayId display Id. * @param displayId display Id.
* @param displayAdjustments display adjustments. * @param displayAdjustments display adjustments.
*/ */
public Display getAdjustedDisplay(final int displayId, DisplayAdjustments displayAdjustments) { public Display getAdjustedDisplay(final int displayId,
@Nullable DisplayAdjustments displayAdjustments) {
final DisplayAdjustments displayAdjustmentsCopy = (displayAdjustments != null) final DisplayAdjustments displayAdjustmentsCopy = (displayAdjustments != null)
? new DisplayAdjustments(displayAdjustments) : new DisplayAdjustments(); ? new DisplayAdjustments(displayAdjustments) : new DisplayAdjustments();
final Pair<Integer, DisplayAdjustments> key = final Pair<Integer, DisplayAdjustments> key =
@@ -153,76 +204,42 @@ public class ResourcesManager {
} }
/** /**
* Creates the top level Resources for applications with the given compatibility info. * Creates an AssetManager from the paths within the ResourcesKey.
* *
* @param resDir the resource directory. * This can be overridden in tests so as to avoid creating a real AssetManager with
* @param splitResDirs split resource directories. * real APK paths.
* @param overlayDirs the resource overlay directories. * @param key The key containing the resource paths to add to the AssetManager.
* @param libDirs the shared library resource dirs this app references. * @return a new AssetManager.
* @param displayId display Id. */
* @param overrideConfiguration override configurations. @VisibleForTesting
* @param compatInfo the compatibility info. Must not be null. protected AssetManager createAssetManager(@NonNull final ResourcesKey key) {
* @param classLoader the class loader for the resource package
*/
Resources getTopLevelResources(String resDir, String[] splitResDirs,
String[] overlayDirs, String[] libDirs, int displayId,
Configuration overrideConfiguration, CompatibilityInfo compatInfo,
ClassLoader classLoader) {
final float scale = compatInfo.applicationScale;
Configuration overrideConfigCopy = (overrideConfiguration != null)
? new Configuration(overrideConfiguration) : null;
ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfigCopy, scale);
Resources r;
final boolean findSystemLocales;
final boolean hasNonSystemLocales;
synchronized (this) {
// Resources is app scale dependent.
if (DEBUG) Slog.w(TAG, "getTopLevelResources: " + resDir + " / " + scale);
WeakReference<Resources> wr = mActiveResources.get(key);
r = wr != null ? wr.get() : null;
//if (r != null) Log.i(TAG, "isUpToDate " + resDir + ": " + r.getAssets().isUpToDate());
if (r != null && r.getAssets().isUpToDate()) {
if (DEBUG) Slog.w(TAG, "Returning cached resources " + r + " " + resDir
+ ": appScale=" + r.getCompatibilityInfo().applicationScale
+ " key=" + key + " overrideConfig=" + overrideConfiguration);
return r;
}
findSystemLocales = (mSystemLocales.length == 0);
hasNonSystemLocales = mHasNonSystemLocales;
}
//if (r != null) {
// Log.w(TAG, "Throwing away out-of-date resources!!!! "
// + r + " " + resDir);
//}
AssetManager assets = new AssetManager(); AssetManager assets = new AssetManager();
// resDir can be null if the 'android' package is creating a new Resources object. // resDir can be null if the 'android' package is creating a new Resources object.
// This is fine, since each AssetManager automatically loads the 'android' package // This is fine, since each AssetManager automatically loads the 'android' package
// already. // already.
if (resDir != null) { if (key.mResDir != null) {
if (assets.addAssetPath(resDir) == 0) { if (assets.addAssetPath(key.mResDir) == 0) {
return null; return null;
} }
} }
if (splitResDirs != null) { if (key.mSplitResDirs != null) {
for (String splitResDir : splitResDirs) { for (final String splitResDir : key.mSplitResDirs) {
if (assets.addAssetPath(splitResDir) == 0) { if (assets.addAssetPath(splitResDir) == 0) {
return null; return null;
} }
} }
} }
if (overlayDirs != null) { if (key.mOverlayDirs != null) {
for (String idmapPath : overlayDirs) { for (final String idmapPath : key.mOverlayDirs) {
assets.addOverlayPath(idmapPath); assets.addOverlayPath(idmapPath);
} }
} }
if (libDirs != null) { if (key.mLibDirs != null) {
for (String libDir : libDirs) { for (final String libDir : key.mLibDirs) {
if (libDir.endsWith(".apk")) { if (libDir.endsWith(".apk")) {
// Avoid opening files we know do not have resources, // Avoid opening files we know do not have resources,
// like code-only .jar files. // like code-only .jar files.
@@ -233,16 +250,17 @@ public class ResourcesManager {
} }
} }
} }
return assets;
}
//Log.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics); private Configuration generateConfig(@NonNull ResourcesKey key, @NonNull DisplayMetrics dm) {
DisplayMetrics dm = getDisplayMetricsLocked(displayId);
Configuration config; Configuration config;
final boolean isDefaultDisplay = (displayId == Display.DEFAULT_DISPLAY); final boolean isDefaultDisplay = (key.mDisplayId == Display.DEFAULT_DISPLAY);
final boolean hasOverrideConfig = key.hasOverrideConfiguration(); final boolean hasOverrideConfig = key.hasOverrideConfiguration();
if (!isDefaultDisplay || hasOverrideConfig) { if (!isDefaultDisplay || hasOverrideConfig) {
config = new Configuration(getConfiguration()); config = new Configuration(getConfiguration());
if (!isDefaultDisplay) { if (!isDefaultDisplay) {
applyNonDefaultDisplayMetricsToConfigurationLocked(dm, config); applyNonDefaultDisplayMetricsToConfiguration(dm, config);
} }
if (hasOverrideConfig) { if (hasOverrideConfig) {
config.updateFrom(key.mOverrideConfiguration); config.updateFrom(key.mOverrideConfiguration);
@@ -251,16 +269,212 @@ public class ResourcesManager {
} else { } else {
config = getConfiguration(); config = getConfiguration();
} }
r = new Resources(classLoader); return config;
r.setImpl(new ResourcesImpl(assets, dm, config, compatInfo)); }
if (DEBUG) Slog.i(TAG, "Created app resources " + resDir + " " + r + ": "
+ r.getConfiguration() + " appScale=" + r.getCompatibilityInfo().applicationScale);
final String[] systemLocales = (
findSystemLocales ? private ResourcesImpl createResourcesImpl(@NonNull ResourcesKey key) {
AssetManager.getSystem().getLocales() : AssetManager assets = createAssetManager(key);
null); DisplayMetrics dm = getDisplayMetricsLocked(key.mDisplayId);
final String[] nonSystemLocales = assets.getNonSystemLocales(); Configuration config = generateConfig(key, dm);
ResourcesImpl impl = new ResourcesImpl(assets, dm, config, key.mCompatInfo);
if (DEBUG) {
Slog.d(TAG, "- creating impl=" + impl + " with key: " + key);
}
return impl;
}
/**
* Finds a cached ResourcesImpl object that matches the given ResourcesKey.
*
* @param key The key to match.
* @return a ResourcesImpl if the key matches a cache entry, null otherwise.
*/
private ResourcesImpl findResourcesImplForKey(@NonNull ResourcesKey key) {
WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.get(key);
ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
if (impl != null && impl.getAssets().isUpToDate()) {
return impl;
}
return null;
}
/**
* 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) {
final int refCount = mResourceImpls.size();
for (int i = 0; i < refCount; i++) {
WeakReference<ResourcesImpl> weakImplRef = mResourceImpls.valueAt(i);
ResourcesImpl impl = weakImplRef != null ? weakImplRef.get() : null;
if (impl != null && resourceImpl == impl) {
return mResourceImpls.keyAt(i);
}
}
return null;
}
/**
* 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);
}
}
if (resources.getImpl() != impl) {
if (DEBUG) {
Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
}
// Setting an impl is expensive because we update all ThemeImpl references.
// too.
resources.setImpl(impl);
}
return resources;
}
/**
* Gets an existing Resources object if the class loader and ResourcesImpl are the same,
* otherwise creates a new Resources object.
*/
private Resources getOrCreateResourcesLocked(@NonNull ClassLoader classLoader,
@NonNull ResourcesImpl impl) {
// Find an existing Resources that has this ResourcesImpl set.
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;
if (resources != null &&
Objects.equals(resources.getClassLoader(), classLoader) &&
resources.getImpl() == impl) {
if (DEBUG) {
Slog.d(TAG, "- using existing ref=" + resources);
}
return resources;
}
}
// Create a new Resources reference and use the existing ResourcesImpl object.
Resources resources = new Resources(classLoader);
resources.setImpl(impl);
mResourceReferences.add(new WeakReference<>(resources));
if (DEBUG) {
Slog.d(TAG, "- creating new ref=" + resources);
Slog.d(TAG, "- setting ref=" + resources + " with impl=" + impl);
}
return resources;
}
/**
* 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();
final boolean findSystemLocales;
final boolean hasNonSystemLocales;
synchronized (this) {
findSystemLocales = (mSystemLocales.length == 0);
hasNonSystemLocales = mHasNonSystemLocales;
if (DEBUG) {
Throwable here = new Throwable();
here.fillInStackTrace();
Slog.w(TAG, "!! Get resources for activity=" + activityToken + " key=" + key, here);
}
if (activityToken != null) {
ResourcesImpl resourcesImpl = findResourcesImplForKey(key);
if (resourcesImpl != null) {
if (DEBUG) {
Slog.d(TAG, "- using existing impl=" + resourcesImpl);
}
return getOrCreateResourcesForActivityLocked(activityToken, classLoader,
resourcesImpl);
}
// We will create the ResourcesImpl object outside of holding this lock.
} else {
// Clean up any dead references so they don't pile up.
ArrayUtils.unstableRemoveIf(mResourceReferences, sEmptyReferencePredicate);
// Not tied to an Activity, find a shared Resources that has the right ResourcesImpl
ResourcesImpl resourcesImpl = findResourcesImplForKey(key);
if (resourcesImpl != null) {
if (DEBUG) {
Slog.d(TAG, "- using existing impl=" + resourcesImpl);
}
return getOrCreateResourcesLocked(classLoader, resourcesImpl);
}
// We will create the ResourcesImpl object outside of holding this lock.
}
}
// If we're here, we didn't find a suitable ResourcesImpl to use, so create one now.
ResourcesImpl resourcesImpl = createResourcesImpl(key);
final String[] systemLocales = findSystemLocales
? AssetManager.getSystem().getLocales() : null;
final String[] nonSystemLocales = resourcesImpl.getAssets().getNonSystemLocales();
// Avoid checking for non-pseudo-locales if we already know there were some from a previous // Avoid checking for non-pseudo-locales if we already know there were some from a previous
// Resources. The default value (for when hasNonSystemLocales is true) doesn't matter, // Resources. The default value (for when hasNonSystemLocales is true) doesn't matter,
// since mHasNonSystemLocales will also be true, and thus isPseudoLocalesOnly would not be // since mHasNonSystemLocales will also be true, and thus isPseudoLocalesOnly would not be
@@ -269,41 +483,75 @@ public class ResourcesManager {
LocaleList.isPseudoLocalesOnly(nonSystemLocales); LocaleList.isPseudoLocalesOnly(nonSystemLocales);
synchronized (this) { synchronized (this) {
WeakReference<Resources> wr = mActiveResources.get(key);
Resources existing = wr != null ? wr.get() : null;
if (existing != null && existing.getAssets().isUpToDate()) {
// Someone else already created the resources while we were
// unlocked; go ahead and use theirs.
r.getAssets().close();
return existing;
}
// XXX need to remove entries when weak references go away
mActiveResources.put(key, new WeakReference<>(r));
if (mSystemLocales.length == 0) { if (mSystemLocales.length == 0) {
mSystemLocales = systemLocales; mSystemLocales = systemLocales;
} }
mNonSystemLocales.addAll(Arrays.asList(nonSystemLocales)); mNonSystemLocales.addAll(Arrays.asList(nonSystemLocales));
mHasNonSystemLocales = mHasNonSystemLocales || !isPseudoLocalesOnly; mHasNonSystemLocales = mHasNonSystemLocales || !isPseudoLocalesOnly;
if (DEBUG) Slog.v(TAG, "mActiveResources.size()=" + mActiveResources.size());
return r; ResourcesImpl existingResourcesImpl = findResourcesImplForKey(key);
if (existingResourcesImpl != null) {
if (DEBUG) {
Slog.d(TAG, "- got beat! existing impl=" + existingResourcesImpl
+ " new impl=" + resourcesImpl);
}
resourcesImpl.getAssets().close();
resourcesImpl = existingResourcesImpl;
} else {
// Add this ResourcesImpl to the cache.
mResourceImpls.put(key, new WeakReference<>(resourcesImpl));
}
final Resources resources;
if (activityToken != null) {
resources = getOrCreateResourcesForActivityLocked(activityToken, classLoader,
resourcesImpl);
} else {
resources = getOrCreateResourcesLocked(classLoader, resourcesImpl);
}
return resources;
} }
} }
/** /**
* Removes the top level Resources for applications with the given compatibility info. * Updates an Activity's Resources object with overrideConfig. The Resources object
* @see #getTopLevelResources(String, String[], String[], String[], int, Configuration, CompatibilityInfo, ClassLoader) * 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.
* @param activityToken The Activity token.
* @param overrideConfig The configuration override to update.
*/ */
void removeTopLevelResources(String resDir, int displayId, Configuration overrideConfiguration, public void updateResourcesForActivity(@NonNull IBinder activityToken,
CompatibilityInfo compatInfo) { @Nullable Configuration overrideConfig) {
final float scale = compatInfo.applicationScale; final ClassLoader classLoader;
final Configuration overrideConfigCopy = (overrideConfiguration != null) final ResourcesKey oldKey;
? new Configuration(overrideConfiguration) : null; synchronized (this) {
final ResourcesKey key = new ResourcesKey(resDir, displayId, overrideConfigCopy, scale); // Extract the ResourcesKey that was last used to create the Resources for this
mActiveResources.remove(key); // 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);
return;
}
classLoader = resources.getClassLoader();
oldKey = findKeyForResourceImpl(resources.getImpl());
if (oldKey == null) {
Slog.e(TAG, "can't find ResourcesKey for resources impl=" + resources.getImpl());
return;
}
}
// 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);
} }
/* package */ void setDefaultLocalesLocked(LocaleList locales) { /* package */ void setDefaultLocalesLocked(@NonNull LocaleList locales) {
final int bestLocale; final int bestLocale;
if (mHasNonSystemLocales) { if (mHasNonSystemLocales) {
bestLocale = locales.getFirstMatchIndexWithEnglishSupported(mNonSystemLocales); bestLocale = locales.getFirstMatchIndexWithEnglishSupported(mNonSystemLocales);
@@ -317,11 +565,8 @@ public class ResourcesManager {
LocaleList.setDefault(locales, bestLocale); LocaleList.setDefault(locales, bestLocale);
} }
final boolean applyConfigurationToResourcesLocked(Configuration config, public final boolean applyConfigurationToResourcesLocked(@NonNull Configuration config,
CompatibilityInfo compat) { @Nullable CompatibilityInfo compat) {
if (mResConfiguration == null) {
mResConfiguration = new Configuration();
}
if (!mResConfiguration.isOtherSeqNewer(config) && compat == null) { if (!mResConfiguration.isOtherSeqNewer(config) && compat == null) {
if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Skipping new config: curSeq=" if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Skipping new config: curSeq="
+ mResConfiguration.seq + ", newSeq=" + config.seq); + mResConfiguration.seq + ", newSeq=" + config.seq);
@@ -368,9 +613,9 @@ public class ResourcesManager {
Configuration tmpConfig = null; Configuration tmpConfig = null;
for (int i = mActiveResources.size() - 1; i >= 0; i--) { for (int i = mResourceImpls.size() - 1; i >= 0; i--) {
ResourcesKey key = mActiveResources.keyAt(i); ResourcesKey key = mResourceImpls.keyAt(i);
Resources r = mActiveResources.valueAt(i).get(); ResourcesImpl r = mResourceImpls.valueAt(i).get();
if (r != null) { if (r != null) {
if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources " if (DEBUG || DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources "
+ r + " config to: " + localeAdjustedConfig); + r + " config to: " + localeAdjustedConfig);
@@ -385,7 +630,7 @@ public class ResourcesManager {
tmpConfig.setTo(localeAdjustedConfig); tmpConfig.setTo(localeAdjustedConfig);
if (!isDefaultDisplay) { if (!isDefaultDisplay) {
dm = getDisplayMetricsLocked(displayId); dm = getDisplayMetricsLocked(displayId);
applyNonDefaultDisplayMetricsToConfigurationLocked(dm, tmpConfig); applyNonDefaultDisplayMetricsToConfiguration(dm, tmpConfig);
} }
if (hasOverrideConfiguration) { if (hasOverrideConfiguration) {
tmpConfig.updateFrom(key.mOverrideConfiguration); tmpConfig.updateFrom(key.mOverrideConfiguration);
@@ -398,11 +643,10 @@ public class ResourcesManager {
// + " " + r + ": " + r.getConfiguration()); // + " " + r + ": " + r.getConfiguration());
} else { } else {
//Slog.i(TAG, "Removing old resources " + v.getKey()); //Slog.i(TAG, "Removing old resources " + v.getKey());
mActiveResources.removeAt(i); mResourceImpls.removeAt(i);
} }
} }
return changes != 0; return changes != 0;
} }
}
}

View File

@@ -54,6 +54,7 @@ import android.util.TypedValue;
import android.view.ViewDebug; import android.view.ViewDebug;
import android.view.ViewHierarchyEncoder; import android.view.ViewHierarchyEncoder;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.GrowingArrayUtils; import com.android.internal.util.GrowingArrayUtils;
import com.android.internal.util.XmlUtils; import com.android.internal.util.XmlUtils;
@@ -62,6 +63,8 @@ import org.xmlpull.v1.XmlPullParserException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
/** /**
* Class for accessing an application's resources. This sits on top of the * Class for accessing an application's resources. This sits on top of the
@@ -112,6 +115,13 @@ public class Resources {
final ClassLoader mClassLoader; final ClassLoader mClassLoader;
/**
* WeakReferences to Themes that were constructed from this Resources object.
* We keep track of these in case our underlying implementation is changed, in which case
* the Themes must also get updated ThemeImpls.
*/
private final ArrayList<WeakReference<Theme>> mThemeRefs = new ArrayList<>();
/** /**
* Returns the most appropriate default theme for the specified target SDK version. * Returns the most appropriate default theme for the specified target SDK version.
* <ul> * <ul>
@@ -231,10 +241,42 @@ public class Resources {
} }
/** /**
* Set the underlying implementation (containing all the resources and caches)
* and updates all Theme references to new implementations as well.
* @hide * @hide
*/ */
public void setImpl(ResourcesImpl impl) { public void setImpl(ResourcesImpl impl) {
if (impl == mResourcesImpl) {
return;
}
mResourcesImpl = impl; mResourcesImpl = impl;
// Create new ThemeImpls that are identical to the ones we have.
synchronized (mThemeRefs) {
final int count = mThemeRefs.size();
for (int i = 0; i < count; i++) {
WeakReference<Theme> weakThemeRef = mThemeRefs.get(i);
Theme theme = weakThemeRef != null ? weakThemeRef.get() : null;
if (theme != null) {
theme.setImpl(mResourcesImpl.newThemeImpl(theme.getKey()));
}
}
}
}
/**
* @hide
*/
public ResourcesImpl getImpl() {
return mResourcesImpl;
}
/**
* @hide
*/
public ClassLoader getClassLoader() {
return mClassLoader;
} }
/** /**
@@ -1683,6 +1725,7 @@ public class Resources {
public final Theme newTheme() { public final Theme newTheme() {
Theme theme = new Theme(); Theme theme = new Theme();
theme.setImpl(mResourcesImpl.newThemeImpl()); theme.setImpl(mResourcesImpl.newThemeImpl());
mThemeRefs.add(new WeakReference<>(theme));
return theme; return theme;
} }
@@ -1785,6 +1828,7 @@ public class Resources {
* This is just for testing. * This is just for testing.
* @hide * @hide
*/ */
@VisibleForTesting
public void setCompatibilityInfo(CompatibilityInfo ci) { public void setCompatibilityInfo(CompatibilityInfo ci) {
if (ci != null) { if (ci != null) {
mResourcesImpl.updateConfiguration(null, null, ci); mResourcesImpl.updateConfiguration(null, null, ci);
@@ -2068,6 +2112,15 @@ public class Resources {
return mResourcesImpl.loadXmlResourceParser(file, id, assetCookie, type); return mResourcesImpl.loadXmlResourceParser(file, id, assetCookie, type);
} }
/**
* Called by ConfigurationBoundResourceCacheTest.
* @hide
*/
@VisibleForTesting
public int calcConfigChanges(Configuration config) {
return mResourcesImpl.calcConfigChanges(config);
}
/** /**
* Obtains styled attributes from the theme, if available, or unstyled * Obtains styled attributes from the theme, if available, or unstyled
* resources if the theme is null. * resources if the theme is null.

View File

@@ -48,6 +48,13 @@ import java.util.Arrays;
import java.util.Locale; import java.util.Locale;
/** /**
* The implementation of Resource access. This class contains the AssetManager and all caches
* associated with it.
*
* {@link Resources} is just a thing wrapper around this class. When a configuration change
* occurs, clients can retain the same {@link Resources} reference because the underlying
* {@link ResourcesImpl} object will be updated or re-created.
*
* @hide * @hide
*/ */
public class ResourcesImpl { public class ResourcesImpl {
@@ -126,14 +133,14 @@ public class ResourcesImpl {
* @param compatInfo this resource's compatibility info. Must not be null. * @param compatInfo this resource's compatibility info. Must not be null.
*/ */
public ResourcesImpl(AssetManager assets, DisplayMetrics metrics, Configuration config, public ResourcesImpl(AssetManager assets, DisplayMetrics metrics, Configuration config,
CompatibilityInfo compatInfo) { CompatibilityInfo compatInfo) {
mAssets = assets; mAssets = assets;
mMetrics.setToDefaults(); mMetrics.setToDefaults();
updateConfiguration(config, metrics, compatInfo); updateConfiguration(config, metrics, compatInfo);
mAssets.ensureStringBlocks(); mAssets.ensureStringBlocks();
} }
AssetManager getAssets() { public AssetManager getAssets() {
return mAssets; return mAssets;
} }
@@ -174,7 +181,7 @@ public class ResourcesImpl {
} }
void getValueForDensity(@AnyRes int id, int density, TypedValue outValue, void getValueForDensity(@AnyRes int id, int density, TypedValue outValue,
boolean resolveRefs) throws NotFoundException { boolean resolveRefs) throws NotFoundException {
boolean found = mAssets.getResourceValue(id, density, outValue, resolveRefs); boolean found = mAssets.getResourceValue(id, density, outValue, resolveRefs);
if (found) { if (found) {
return; return;
@@ -298,8 +305,8 @@ public class ResourcesImpl {
return mStateListAnimatorCache; return mStateListAnimatorCache;
} }
void updateConfiguration(Configuration config, DisplayMetrics metrics, public void updateConfiguration(Configuration config, DisplayMetrics metrics,
CompatibilityInfo compat) { CompatibilityInfo compat) {
synchronized (mAccessLock) { synchronized (mAccessLock) {
if (false) { if (false) {
Slog.i(TAG, "**** Updating config of " + this + ": old config is " Slog.i(TAG, "**** Updating config of " + this + ": old config is "
@@ -388,9 +395,9 @@ public class ResourcesImpl {
} }
/** /**
* Called by ConfigurationBoundResourceCacheTest via reflection. * Called by ConfigurationBoundResourceCacheTest.
*/ */
private int calcConfigChanges(Configuration config) { public int calcConfigChanges(Configuration config) {
int configChanges = 0xfffffff; int configChanges = 0xfffffff;
if (config != null) { if (config != null) {
mTmpConfig.setTo(config); mTmpConfig.setTo(config);
@@ -460,7 +467,7 @@ public class ResourcesImpl {
@Nullable @Nullable
Drawable loadDrawable(Resources wrapper, TypedValue value, int id, Resources.Theme theme, Drawable loadDrawable(Resources wrapper, TypedValue value, int id, Resources.Theme theme,
boolean useCache) throws NotFoundException { boolean useCache) throws NotFoundException {
try { try {
if (TRACE_FOR_PRELOAD) { if (TRACE_FOR_PRELOAD) {
// Log only framework resources // Log only framework resources
@@ -553,7 +560,7 @@ public class ResourcesImpl {
} }
private void cacheDrawable(TypedValue value, boolean isColorDrawable, DrawableCache caches, private void cacheDrawable(TypedValue value, boolean isColorDrawable, DrawableCache caches,
Resources.Theme theme, boolean usesTheme, long key, Drawable dr) { Resources.Theme theme, boolean usesTheme, long key, Drawable dr) {
final Drawable.ConstantState cs = dr.getConstantState(); final Drawable.ConstantState cs = dr.getConstantState();
if (cs == null) { if (cs == null) {
return; return;
@@ -587,7 +594,7 @@ public class ResourcesImpl {
} }
private boolean verifyPreloadConfig(int changingConfigurations, int allowVarying, private boolean verifyPreloadConfig(int changingConfigurations, int allowVarying,
int resourceId, String name) { int resourceId, String name) {
// We allow preloading of resources even if they vary by font scale (which // We allow preloading of resources even if they vary by font scale (which
// doesn't impact resource selection) or density (which we handle specially by // doesn't impact resource selection) or density (which we handle specially by
// simply turning off all preloading), as well as any other configs specified // simply turning off all preloading), as well as any other configs specified
@@ -625,7 +632,7 @@ public class ResourcesImpl {
* Loads a drawable from XML or resources stream. * Loads a drawable from XML or resources stream.
*/ */
private Drawable loadDrawableForCookie(Resources wrapper, TypedValue value, int id, private Drawable loadDrawableForCookie(Resources wrapper, TypedValue value, int id,
Resources.Theme theme) { Resources.Theme theme) {
if (value.string == null) { if (value.string == null) {
throw new NotFoundException("Resource \"" + getResourceName(id) + "\" (" throw new NotFoundException("Resource \"" + getResourceName(id) + "\" ("
+ Integer.toHexString(id) + ") is not a Drawable (color or path): " + value); + Integer.toHexString(id) + ") is not a Drawable (color or path): " + value);
@@ -681,7 +688,7 @@ public class ResourcesImpl {
* Last, parse the XML and generate the CSL. * Last, parse the XML and generate the CSL.
*/ */
private ComplexColor loadComplexColorFromName(Resources wrapper, Resources.Theme theme, private ComplexColor loadComplexColorFromName(Resources wrapper, Resources.Theme theme,
TypedValue value, int id) { TypedValue value, int id) {
final long key = (((long) value.assetCookie) << 32) | value.data; final long key = (((long) value.assetCookie) << 32) | value.data;
final ConfigurationBoundResourceCache<ComplexColor> cache = mComplexColorCache; final ConfigurationBoundResourceCache<ComplexColor> cache = mComplexColorCache;
ComplexColor complexColor = cache.getInstance(key, wrapper, theme); ComplexColor complexColor = cache.getInstance(key, wrapper, theme);
@@ -714,7 +721,7 @@ public class ResourcesImpl {
@Nullable @Nullable
ComplexColor loadComplexColor(Resources wrapper, @NonNull TypedValue value, int id, ComplexColor loadComplexColor(Resources wrapper, @NonNull TypedValue value, int id,
Resources.Theme theme) { Resources.Theme theme) {
if (TRACE_FOR_PRELOAD) { if (TRACE_FOR_PRELOAD) {
// Log only framework resources // Log only framework resources
if ((id >>> 24) == 0x1) { if ((id >>> 24) == 0x1) {
@@ -755,7 +762,7 @@ public class ResourcesImpl {
@Nullable @Nullable
ColorStateList loadColorStateList(Resources wrapper, TypedValue value, int id, ColorStateList loadColorStateList(Resources wrapper, TypedValue value, int id,
Resources.Theme theme) Resources.Theme theme)
throws NotFoundException { throws NotFoundException {
if (TRACE_FOR_PRELOAD) { if (TRACE_FOR_PRELOAD) {
// Log only framework resources // Log only framework resources
@@ -815,7 +822,7 @@ public class ResourcesImpl {
*/ */
@Nullable @Nullable
private ComplexColor loadComplexColorForCookie(Resources wrapper, TypedValue value, int id, private ComplexColor loadComplexColorForCookie(Resources wrapper, TypedValue value, int id,
Resources.Theme theme) { Resources.Theme theme) {
if (value.string == null) { if (value.string == null) {
throw new UnsupportedOperationException( throw new UnsupportedOperationException(
"Can't convert to ComplexColor: type=0x" + value.type); "Can't convert to ComplexColor: type=0x" + value.type);
@@ -893,8 +900,8 @@ public class ResourcesImpl {
* @throws NotFoundException if the file could not be loaded * @throws NotFoundException if the file could not be loaded
*/ */
@NonNull @NonNull
XmlResourceParser loadXmlResourceParser(@NonNull String file, @AnyRes int id, XmlResourceParser loadXmlResourceParser(@NonNull String file, @AnyRes int id, int assetCookie,
int assetCookie, @NonNull String type) @NonNull String type)
throws NotFoundException { throws NotFoundException {
if (id != 0) { if (id != 0) {
try { try {
@@ -975,6 +982,16 @@ public class ResourcesImpl {
return new ThemeImpl(); return new ThemeImpl();
} }
/**
* Creates a new ThemeImpl which is already set to the given Resources.ThemeKey.
*/
ThemeImpl newThemeImpl(Resources.ThemeKey key) {
ThemeImpl impl = new ThemeImpl();
impl.mKey.setTo(key);
impl.rebase();
return impl;
}
public class ThemeImpl { public class ThemeImpl {
/** /**
* Unique key for the series of styles applied to this theme. * Unique key for the series of styles applied to this theme.
@@ -1035,10 +1052,10 @@ public class ResourcesImpl {
@NonNull @NonNull
TypedArray obtainStyledAttributes(@NonNull Resources.Theme wrapper, TypedArray obtainStyledAttributes(@NonNull Resources.Theme wrapper,
AttributeSet set, AttributeSet set,
@StyleableRes int[] attrs, @StyleableRes int[] attrs,
@AttrRes int defStyleAttr, @AttrRes int defStyleAttr,
@StyleRes int defStyleRes) { @StyleRes int defStyleRes) {
synchronized (mKey) { synchronized (mKey) {
final int len = attrs.length; final int len = attrs.length;
final TypedArray array = TypedArray.obtain(wrapper.getResources(), len); final TypedArray array = TypedArray.obtain(wrapper.getResources(), len);
@@ -1060,8 +1077,8 @@ public class ResourcesImpl {
@NonNull @NonNull
TypedArray resolveAttributes(@NonNull Resources.Theme wrapper, TypedArray resolveAttributes(@NonNull Resources.Theme wrapper,
@NonNull int[] values, @NonNull int[] values,
@NonNull int[] attrs) { @NonNull int[] attrs) {
synchronized (mKey) { synchronized (mKey) {
final int len = attrs.length; final int len = attrs.length;
if (values == null || len != values.length) { if (values == null || len != values.length) {

View File

@@ -17,32 +17,59 @@
package android.content.res; package android.content.res;
import android.annotation.NonNull; import android.annotation.NonNull;
import android.annotation.Nullable;
import android.text.TextUtils;
import java.util.Arrays;
import java.util.Objects; import java.util.Objects;
/** @hide */ /** @hide */
public final class ResourcesKey { public final class ResourcesKey {
private final String mResDir; @Nullable
private final float mScale; public final String mResDir;
private final int mHash;
@Nullable
public final String[] mSplitResDirs;
@Nullable
public final String[] mOverlayDirs;
@Nullable
public final String[] mLibDirs;
public final int mDisplayId; public final int mDisplayId;
@NonNull @NonNull
public final Configuration mOverrideConfiguration; public final Configuration mOverrideConfiguration;
public ResourcesKey(String resDir, int displayId, Configuration overrideConfiguration, @NonNull
float scale) { public final CompatibilityInfo mCompatInfo;
private final int mHash;
public ResourcesKey(@Nullable String resDir,
@Nullable String[] splitResDirs,
@Nullable String[] overlayDirs,
@Nullable String[] libDirs,
int displayId,
@Nullable Configuration overrideConfig,
@Nullable CompatibilityInfo compatInfo) {
mResDir = resDir; mResDir = resDir;
mSplitResDirs = splitResDirs;
mOverlayDirs = overlayDirs;
mLibDirs = libDirs;
mDisplayId = displayId; mDisplayId = displayId;
mOverrideConfiguration = overrideConfiguration != null mOverrideConfiguration = overrideConfig != null ? overrideConfig : Configuration.EMPTY;
? overrideConfiguration : Configuration.EMPTY; mCompatInfo = compatInfo != null ? compatInfo : CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO;
mScale = scale;
int hash = 17; int hash = 17;
hash = 31 * hash + (mResDir == null ? 0 : mResDir.hashCode()); hash = 31 * hash + Objects.hashCode(mResDir);
hash = 31 * hash + Arrays.hashCode(mSplitResDirs);
hash = 31 * hash + Arrays.hashCode(mOverlayDirs);
hash = 31 * hash + Arrays.hashCode(mLibDirs);
hash = 31 * hash + mDisplayId; hash = 31 * hash + mDisplayId;
hash = 31 * hash + mOverrideConfiguration.hashCode(); hash = 31 * hash + Objects.hashCode(mOverrideConfiguration);
hash = 31 * hash + Float.floatToIntBits(mScale); hash = 31 * hash + Objects.hashCode(mCompatInfo);
mHash = hash; mHash = hash;
} }
@@ -60,18 +87,32 @@ public final class ResourcesKey {
if (!(obj instanceof ResourcesKey)) { if (!(obj instanceof ResourcesKey)) {
return false; return false;
} }
ResourcesKey peer = (ResourcesKey) obj; ResourcesKey peer = (ResourcesKey) obj;
if (mHash != peer.mHash) {
// If the hashes don't match, the objects can't match.
return false;
}
if (!Objects.equals(mResDir, peer.mResDir)) { if (!Objects.equals(mResDir, peer.mResDir)) {
return false; return false;
} }
if (!Arrays.equals(mSplitResDirs, peer.mSplitResDirs)) {
return false;
}
if (!Arrays.equals(mOverlayDirs, peer.mOverlayDirs)) {
return false;
}
if (!Arrays.equals(mLibDirs, peer.mLibDirs)) {
return false;
}
if (mDisplayId != peer.mDisplayId) { if (mDisplayId != peer.mDisplayId) {
return false; return false;
} }
if (!mOverrideConfiguration.equals(peer.mOverrideConfiguration)) { if (!Objects.equals(mOverrideConfiguration, peer.mOverrideConfiguration)) {
return false; return false;
} }
if (mScale != peer.mScale) { if (!Objects.equals(mCompatInfo, peer.mCompatInfo)) {
return false; return false;
} }
return true; return true;
@@ -79,6 +120,29 @@ public final class ResourcesKey {
@Override @Override
public String toString() { public String toString() {
return Integer.toHexString(mHash); StringBuilder builder = new StringBuilder().append("ResourcesKey{");
builder.append(" mHash=").append(Integer.toHexString(mHash));
builder.append(" mResDir=").append(mResDir);
builder.append(" mSplitDirs=[");
if (mSplitResDirs != null) {
builder.append(TextUtils.join(",", mSplitResDirs));
}
builder.append("]");
builder.append(" mOverlayDirs=[");
if (mOverlayDirs != null) {
builder.append(TextUtils.join(",", mOverlayDirs));
}
builder.append("]");
builder.append(" mLibDirs=[");
if (mLibDirs != null) {
builder.append(TextUtils.join(",", mLibDirs));
}
builder.append("]");
builder.append(" mDisplayId=").append(mDisplayId);
builder.append(" mOverrideConfig=").append(Configuration.resourceQualifierString(
mOverrideConfiguration));
builder.append(" mCompatInfo=").append(mCompatInfo);
builder.append("}");
return builder.toString();
} }
} }

View File

@@ -27,6 +27,7 @@ import libcore.util.EmptyArray;
import java.lang.reflect.Array; import java.lang.reflect.Array;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
@@ -477,4 +478,48 @@ public class ArrayUtils {
} }
return !diff; return !diff;
} }
/**
* Removes elements that match the predicate in an efficient way that alters the order of
* elements in the collection. This should only be used if order is not important.
* @param collection The ArrayList from which to remove elements.
* @param predicate The predicate that each element is tested against.
* @return the number of elements removed.
*/
public static <T> int unstableRemoveIf(@Nullable ArrayList<T> collection,
@NonNull java.util.function.Predicate<T> predicate) {
if (collection == null) {
return 0;
}
final int size = collection.size();
int leftIdx = 0;
int rightIdx = size - 1;
while (leftIdx <= rightIdx) {
// Find the next element to remove moving left to right.
while (leftIdx < size && !predicate.test(collection.get(leftIdx))) {
leftIdx++;
}
// Find the next element to keep moving right to left.
while (rightIdx > leftIdx && predicate.test(collection.get(rightIdx))) {
rightIdx--;
}
if (leftIdx >= rightIdx) {
// Done.
break;
}
Collections.swap(collection, leftIdx, rightIdx);
leftIdx++;
rightIdx--;
}
// leftIdx is now at the end.
for (int i = size - 1; i >= leftIdx; i--) {
collection.remove(i);
}
return size - leftIdx;
}
} }

View File

@@ -17,20 +17,18 @@
package android.content.res; package android.content.res;
import android.test.ActivityInstrumentationTestCase2; import android.test.ActivityInstrumentationTestCase2;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.TypedValue; import android.util.TypedValue;
import com.android.frameworks.coretests.R; import com.android.frameworks.coretests.R;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class ConfigurationBoundResourceCacheTest public class ConfigurationBoundResourceCacheTest
extends ActivityInstrumentationTestCase2<ResourceCacheActivity> { extends ActivityInstrumentationTestCase2<ResourceCacheActivity> {
ConfigurationBoundResourceCache<Float> mCache; ConfigurationBoundResourceCache<Float> mCache;
Method mCalcConfigChanges;
public ConfigurationBoundResourceCacheTest() { public ConfigurationBoundResourceCacheTest() {
super(ResourceCacheActivity.class); super(ResourceCacheActivity.class);
} }
@@ -41,33 +39,42 @@ public class ConfigurationBoundResourceCacheTest
mCache = new ConfigurationBoundResourceCache<>(); mCache = new ConfigurationBoundResourceCache<>();
} }
@SmallTest
public void testGetEmpty() { public void testGetEmpty() {
assertNull(mCache.get(-1, null)); final Resources res = getActivity().getResources();
assertNull(mCache.getInstance(-1, res, null));
} }
@SmallTest
public void testSetGet() { public void testSetGet() {
mCache.put(1, null, new DummyFloatConstantState(5f)); mCache.put(1, null, new DummyFloatConstantState(5f));
assertEquals(5f, mCache.get(1, null)); final Resources res = getActivity().getResources();
assertNotSame(5f, mCache.get(1, null)); assertEquals(5f, mCache.getInstance(1, res, null));
assertEquals(null, mCache.get(1, getActivity().getTheme())); assertNotSame(5f, mCache.getInstance(1, res, null));
assertEquals(null, mCache.getInstance(1, res, getActivity().getTheme()));
} }
@SmallTest
public void testSetGetThemed() { public void testSetGetThemed() {
mCache.put(1, getActivity().getTheme(), new DummyFloatConstantState(5f)); mCache.put(1, getActivity().getTheme(), new DummyFloatConstantState(5f));
assertEquals(null, mCache.get(1, null)); final Resources res = getActivity().getResources();
assertEquals(5f, mCache.get(1, getActivity().getTheme())); assertEquals(null, mCache.getInstance(1, res, null));
assertNotSame(5f, mCache.get(1, getActivity().getTheme())); assertEquals(5f, mCache.getInstance(1, res, getActivity().getTheme()));
assertNotSame(5f, mCache.getInstance(1, res, getActivity().getTheme()));
} }
@SmallTest
public void testMultiThreadPutGet() { public void testMultiThreadPutGet() {
mCache.put(1, getActivity().getTheme(), new DummyFloatConstantState(5f)); mCache.put(1, getActivity().getTheme(), new DummyFloatConstantState(5f));
mCache.put(1, null, new DummyFloatConstantState(10f)); mCache.put(1, null, new DummyFloatConstantState(10f));
assertEquals(10f, mCache.get(1, null)); final Resources res = getActivity().getResources();
assertNotSame(10f, mCache.get(1, null)); assertEquals(10f, mCache.getInstance(1, res, null));
assertEquals(5f, mCache.get(1, getActivity().getTheme())); assertNotSame(10f, mCache.getInstance(1, res, null));
assertNotSame(5f, mCache.get(1, getActivity().getTheme())); assertEquals(5f, mCache.getInstance(1, res, getActivity().getTheme()));
assertNotSame(5f, mCache.getInstance(1, res, getActivity().getTheme()));
} }
@SmallTest
public void testVoidConfigChange() public void testVoidConfigChange()
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
TypedValue staticValue = new TypedValue(); TypedValue staticValue = new TypedValue();
@@ -83,11 +90,12 @@ public class ConfigurationBoundResourceCacheTest
Configuration.ORIENTATION_PORTRAIT Configuration.ORIENTATION_PORTRAIT
: Configuration.ORIENTATION_LANDSCAPE; : Configuration.ORIENTATION_LANDSCAPE;
int changes = calcConfigChanges(res, newCnf); int changes = calcConfigChanges(res, newCnf);
assertEquals(staticDim, mCache.get(key, getActivity().getTheme())); assertEquals(staticDim, mCache.getInstance(key, res, getActivity().getTheme()));
mCache.onConfigurationChange(changes); mCache.onConfigurationChange(changes);
assertEquals(staticDim, mCache.get(key, getActivity().getTheme())); assertEquals(staticDim, mCache.getInstance(key, res, getActivity().getTheme()));
} }
@SmallTest
public void testEffectiveConfigChange() public void testEffectiveConfigChange()
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
TypedValue changingValue = new TypedValue(); TypedValue changingValue = new TypedValue();
@@ -105,11 +113,12 @@ public class ConfigurationBoundResourceCacheTest
Configuration.ORIENTATION_PORTRAIT Configuration.ORIENTATION_PORTRAIT
: Configuration.ORIENTATION_LANDSCAPE; : Configuration.ORIENTATION_LANDSCAPE;
int changes = calcConfigChanges(res, newCnf); int changes = calcConfigChanges(res, newCnf);
assertEquals(changingDim, mCache.get(key, getActivity().getTheme())); assertEquals(changingDim, mCache.getInstance(key, res, getActivity().getTheme()));
mCache.onConfigurationChange(changes); mCache.onConfigurationChange(changes);
assertNull(mCache.get(key, getActivity().getTheme())); assertNull(mCache.get(key, getActivity().getTheme()));
} }
@SmallTest
public void testConfigChangeMultipleResources() public void testConfigChangeMultipleResources()
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
TypedValue staticValue = new TypedValue(); TypedValue staticValue = new TypedValue();
@@ -130,17 +139,19 @@ public class ConfigurationBoundResourceCacheTest
Configuration.ORIENTATION_PORTRAIT Configuration.ORIENTATION_PORTRAIT
: Configuration.ORIENTATION_LANDSCAPE; : Configuration.ORIENTATION_LANDSCAPE;
int changes = calcConfigChanges(res, newCnf); int changes = calcConfigChanges(res, newCnf);
assertEquals(staticDim, mCache.get(R.dimen.resource_cache_test_generic, assertEquals(staticDim, mCache.getInstance(R.dimen.resource_cache_test_generic, res,
getActivity().getTheme()));
assertEquals(changingDim, mCache.get(R.dimen.resource_cache_test_orientation_dependent,
getActivity().getTheme())); getActivity().getTheme()));
assertEquals(changingDim,
mCache.getInstance(R.dimen.resource_cache_test_orientation_dependent, res,
getActivity().getTheme()));
mCache.onConfigurationChange(changes); mCache.onConfigurationChange(changes);
assertEquals(staticDim, mCache.get(R.dimen.resource_cache_test_generic, assertEquals(staticDim, mCache.getInstance(R.dimen.resource_cache_test_generic, res,
getActivity().getTheme())); getActivity().getTheme()));
assertNull(mCache.get(R.dimen.resource_cache_test_orientation_dependent, assertNull(mCache.getInstance(R.dimen.resource_cache_test_orientation_dependent, res,
getActivity().getTheme())); getActivity().getTheme()));
} }
@SmallTest
public void testConfigChangeMultipleThemes() public void testConfigChangeMultipleThemes()
throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
TypedValue[] staticValues = new TypedValue[]{new TypedValue(), new TypedValue()}; TypedValue[] staticValues = new TypedValue[]{new TypedValue(), new TypedValue()};
@@ -172,31 +183,27 @@ public class ConfigurationBoundResourceCacheTest
int changes = calcConfigChanges(res, newCnf); int changes = calcConfigChanges(res, newCnf);
for (int i = 0; i < 2; i++) { for (int i = 0; i < 2; i++) {
final Resources.Theme theme = i == 0 ? getActivity().getTheme() : null; final Resources.Theme theme = i == 0 ? getActivity().getTheme() : null;
assertEquals(staticDim, mCache.get(R.dimen.resource_cache_test_generic, theme)); assertEquals(staticDim,
mCache.getInstance(R.dimen.resource_cache_test_generic, res, theme));
assertEquals(changingDim, assertEquals(changingDim,
mCache.get(R.dimen.resource_cache_test_orientation_dependent, theme)); mCache.getInstance(R.dimen.resource_cache_test_orientation_dependent, res,
theme));
} }
mCache.onConfigurationChange(changes); mCache.onConfigurationChange(changes);
for (int i = 0; i < 2; i++) { for (int i = 0; i < 2; i++) {
final Resources.Theme theme = i == 0 ? getActivity().getTheme() : null; final Resources.Theme theme = i == 0 ? getActivity().getTheme() : null;
assertEquals(staticDim, mCache.get(R.dimen.resource_cache_test_generic, theme)); assertEquals(staticDim,
assertNull(mCache.get(R.dimen.resource_cache_test_orientation_dependent, theme)); mCache.getInstance(R.dimen.resource_cache_test_generic, res, theme));
assertNull(mCache.getInstance(R.dimen.resource_cache_test_orientation_dependent, res,
theme));
} }
} }
private int calcConfigChanges(Resources resources, Configuration configuration) private static int calcConfigChanges(Resources resources, Configuration configuration) {
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { return resources.calcConfigChanges(configuration);
if (mCalcConfigChanges == null) {
mCalcConfigChanges = Resources.class.getDeclaredMethod("calcConfigChanges",
Configuration.class);
mCalcConfigChanges.setAccessible(true);
}
return (Integer) mCalcConfigChanges.invoke(resources, configuration);
} }
static class DummyFloatConstantState extends static class DummyFloatConstantState extends ConstantState<Float> {
ConstantState<Float> {
final Float mObj; final Float mObj;

View File

@@ -0,0 +1,222 @@
/*
* Copyright (C) 2016 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;
import android.annotation.NonNull;
import android.app.ResourcesManager;
import android.os.Binder;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.DisplayMetrics;
import android.util.LocaleList;
import android.util.TypedValue;
import android.view.Display;
import junit.framework.TestCase;
public class ResourcesManagerTest extends TestCase {
private static final String APP_ONE_RES_DIR = "app_one.apk";
private static final String APP_ONE_RES_SPLIT_DIR = "app_one_split.apk";
private static final String APP_TWO_RES_DIR = "app_two.apk";
private static final String LIB_RES_DIR = "lib.apk";
private ResourcesManager mResourcesManager;
private DisplayMetrics mDisplayMetrics;
@Override
protected void setUp() throws Exception {
super.setUp();
mDisplayMetrics = new DisplayMetrics();
mDisplayMetrics.setToDefaults();
// Override defaults (which take device specific properties).
mDisplayMetrics.density = 1.0f;
mDisplayMetrics.densityDpi = DisplayMetrics.DENSITY_DEFAULT;
mDisplayMetrics.xdpi = DisplayMetrics.DENSITY_DEFAULT;
mDisplayMetrics.ydpi = DisplayMetrics.DENSITY_DEFAULT;
mDisplayMetrics.noncompatDensity = mDisplayMetrics.density;
mDisplayMetrics.noncompatDensityDpi = mDisplayMetrics.densityDpi;
mDisplayMetrics.noncompatXdpi = DisplayMetrics.DENSITY_DEFAULT;
mDisplayMetrics.noncompatYdpi = DisplayMetrics.DENSITY_DEFAULT;
mResourcesManager = new ResourcesManager() {
@Override
protected AssetManager createAssetManager(@NonNull ResourcesKey key) {
return new AssetManager();
}
@Override
protected DisplayMetrics getDisplayMetricsLocked(int displayId) {
return mDisplayMetrics;
}
};
}
@SmallTest
public void testMultipleCallsWithIdenticalParametersCacheReference() {
Resources resources = mResourcesManager.getResources(
null, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null,
CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
assertNotNull(resources);
Resources newResources = mResourcesManager.getResources(
null, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null,
CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
assertNotNull(newResources);
assertSame(resources, newResources);
}
@SmallTest
public void testMultipleCallsWithDifferentParametersReturnDifferentReferences() {
Resources resources = mResourcesManager.getResources(
null, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null,
CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, 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);
assertNotNull(newResources);
assertNotSame(resources, newResources);
}
@SmallTest
public void testAddingASplitCreatesANewImpl() {
Resources resources1 = mResourcesManager.getResources(
null, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null,
CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, 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);
assertNotNull(resources2);
assertNotSame(resources1, resources2);
assertNotSame(resources1.getImpl(), resources2.getImpl());
}
@SmallTest
public void testUpdateConfigurationUpdatesAllAssetManagers() {
Resources resources1 = mResourcesManager.getResources(
null, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null,
CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
assertNotNull(resources1);
Resources resources2 = mResourcesManager.getResources(
null, APP_TWO_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null,
CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
assertNotNull(resources2);
Binder activity = new Binder();
final Configuration overrideConfig = new Configuration();
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);
assertNotNull(resources3);
// No Resources object should be the same.
assertNotSame(resources1, resources2);
assertNotSame(resources1, resources3);
assertNotSame(resources2, resources3);
// Each ResourcesImpl should be different.
assertNotSame(resources1.getImpl(), resources2.getImpl());
assertNotSame(resources1.getImpl(), resources3.getImpl());
assertNotSame(resources2.getImpl(), resources3.getImpl());
Configuration newConfig = new Configuration();
newConfig.orientation = Configuration.ORIENTATION_LANDSCAPE;
mResourcesManager.applyConfigurationToResourcesLocked(newConfig, null);
final Configuration expectedConfig = new Configuration();
expectedConfig.setLocales(LocaleList.getAdjustedDefault());
expectedConfig.densityDpi = mDisplayMetrics.densityDpi;
expectedConfig.orientation = Configuration.ORIENTATION_LANDSCAPE;
assertEquals(expectedConfig, resources1.getConfiguration());
assertEquals(expectedConfig, resources2.getConfiguration());
assertEquals(expectedConfig, resources3.getConfiguration());
}
@SmallTest
public void testTwoActivitiesWithIdenticalParametersShareImpl() {
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);
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);
assertNotNull(resources1);
// The references themselves should be unique.
assertNotSame(resources1, resources2);
// 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(
activity1, APP_ONE_RES_DIR, null, null, null, Display.DEFAULT_DISPLAY, null,
CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null);
assertNotNull(resources1);
Resources.Theme theme = resources1.newTheme();
assertSame(resources1, theme.getResources());
theme.applyStyle(android.R.style.Theme_NoTitleBar, false);
TypedValue value = new TypedValue();
assertTrue(theme.resolveAttribute(android.R.attr.windowNoTitle, value, true));
assertEquals(TypedValue.TYPE_INT_BOOLEAN, value.type);
assertTrue(value.data != 0);
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());
// 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);
}
}

View File

@@ -0,0 +1,119 @@
/*
* Copyright (C) 2016 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 com.android.internal.util;
import android.test.suitebuilder.annotation.SmallTest;
import junit.framework.TestCase;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
public class ArrayUtilsTest extends TestCase {
@SmallTest
public void testUnstableRemoveIf() throws Exception {
java.util.function.Predicate<Object> isNull = new java.util.function.Predicate<Object>() {
@Override
public boolean test(Object o) {
return o == null;
}
};
final Object a = new Object();
final Object b = new Object();
final Object c = new Object();
ArrayList<Object> collection = null;
assertEquals(0, ArrayUtils.unstableRemoveIf(collection, isNull));
collection = new ArrayList<>();
assertEquals(0, ArrayUtils.unstableRemoveIf(collection, isNull));
collection = new ArrayList<>(Collections.singletonList(a));
assertEquals(0, ArrayUtils.unstableRemoveIf(collection, isNull));
assertEquals(1, collection.size());
assertTrue(collection.contains(a));
collection = new ArrayList<>(Collections.singletonList(null));
assertEquals(1, ArrayUtils.unstableRemoveIf(collection, isNull));
assertEquals(0, collection.size());
collection = new ArrayList<>(Arrays.asList(a, b));
assertEquals(0, ArrayUtils.unstableRemoveIf(collection, isNull));
assertEquals(2, collection.size());
assertTrue(collection.contains(a));
assertTrue(collection.contains(b));
collection = new ArrayList<>(Arrays.asList(a, null));
assertEquals(1, ArrayUtils.unstableRemoveIf(collection, isNull));
assertEquals(1, collection.size());
assertTrue(collection.contains(a));
collection = new ArrayList<>(Arrays.asList(null, a));
assertEquals(1, ArrayUtils.unstableRemoveIf(collection, isNull));
assertEquals(1, collection.size());
assertTrue(collection.contains(a));
collection = new ArrayList<>(Arrays.asList(null, null));
assertEquals(2, ArrayUtils.unstableRemoveIf(collection, isNull));
assertEquals(0, collection.size());
collection = new ArrayList<>(Arrays.asList(a, b, c));
assertEquals(0, ArrayUtils.unstableRemoveIf(collection, isNull));
assertEquals(3, collection.size());
assertTrue(collection.contains(a));
assertTrue(collection.contains(b));
assertTrue(collection.contains(c));
collection = new ArrayList<>(Arrays.asList(a, b, null));
assertEquals(1, ArrayUtils.unstableRemoveIf(collection, isNull));
assertEquals(2, collection.size());
assertTrue(collection.contains(a));
assertTrue(collection.contains(b));
collection = new ArrayList<>(Arrays.asList(a, null, b));
assertEquals(1, ArrayUtils.unstableRemoveIf(collection, isNull));
assertEquals(2, collection.size());
assertTrue(collection.contains(a));
assertTrue(collection.contains(b));
collection = new ArrayList<>(Arrays.asList(null, a, b));
assertEquals(1, ArrayUtils.unstableRemoveIf(collection, isNull));
assertEquals(2, collection.size());
assertTrue(collection.contains(a));
assertTrue(collection.contains(b));
collection = new ArrayList<>(Arrays.asList(a, null, null));
assertEquals(2, ArrayUtils.unstableRemoveIf(collection, isNull));
assertEquals(1, collection.size());
assertTrue(collection.contains(a));
collection = new ArrayList<>(Arrays.asList(null, null, a));
assertEquals(2, ArrayUtils.unstableRemoveIf(collection, isNull));
assertEquals(1, collection.size());
assertTrue(collection.contains(a));
collection = new ArrayList<>(Arrays.asList(null, a, null));
assertEquals(2, ArrayUtils.unstableRemoveIf(collection, isNull));
assertEquals(1, collection.size());
assertTrue(collection.contains(a));
collection = new ArrayList<>(Arrays.asList(null, null, null));
assertEquals(3, ArrayUtils.unstableRemoveIf(collection, isNull));
assertEquals(0, collection.size());
}
}