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:
@@ -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);
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
@@ -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.
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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());
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user