Skip to content

Commit 756220b

Browse files
author
Dianne Hackborn
committed
Add API to create new contexts with custom configurations.
This allows you to, say, make a Context whose configuration is set to a different density than the actual density of the device. The main API is Context.createConfigurationContext(). There is also a new API on ContextThemeWrapper that allows you to apply an override context before its resources are retrieved, which addresses some feature requests from developers to be able to customize the context their app is running in. Change-Id: I88364986660088521e24b567e2fda22fb7042819
1 parent 863b19b commit 756220b

File tree

13 files changed

+203
-29
lines changed

13 files changed

+203
-29
lines changed

api/current.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5262,6 +5262,7 @@ package android.content {
52625262
method public abstract int checkUriPermission(android.net.Uri, int, int, int);
52635263
method public abstract int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int);
52645264
method public abstract deprecated void clearWallpaper() throws java.io.IOException;
5265+
method public abstract android.content.Context createConfigurationContext(android.content.res.Configuration);
52655266
method public abstract android.content.Context createPackageContext(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
52665267
method public abstract java.lang.String[] databaseList();
52675268
method public abstract boolean deleteDatabase(java.lang.String);
@@ -5406,6 +5407,7 @@ package android.content {
54065407
method public int checkUriPermission(android.net.Uri, int, int, int);
54075408
method public int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int);
54085409
method public void clearWallpaper() throws java.io.IOException;
5410+
method public android.content.Context createConfigurationContext(android.content.res.Configuration);
54095411
method public android.content.Context createPackageContext(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
54105412
method public java.lang.String[] databaseList();
54115413
method public boolean deleteDatabase(java.lang.String);
@@ -21147,6 +21149,7 @@ package android.test.mock {
2114721149
method public int checkUriPermission(android.net.Uri, int, int, int);
2114821150
method public int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int);
2114921151
method public void clearWallpaper();
21152+
method public android.content.Context createConfigurationContext(android.content.res.Configuration);
2115021153
method public android.content.Context createPackageContext(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException;
2115121154
method public java.lang.String[] databaseList();
2115221155
method public boolean deleteDatabase(java.lang.String);
@@ -23351,6 +23354,7 @@ package android.view {
2335123354
public class ContextThemeWrapper extends android.content.ContextWrapper {
2335223355
ctor public ContextThemeWrapper();
2335323356
ctor public ContextThemeWrapper(android.content.Context, int);
23357+
method public void applyOverrideConfiguration(android.content.res.Configuration);
2335423358
method protected void onApplyThemeResource(android.content.res.Resources.Theme, int, boolean);
2335523359
}
2335623360

core/java/android/app/ActivityThread.java

Lines changed: 64 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1471,13 +1471,25 @@ public final boolean queueIdle() {
14711471

14721472
private static class ResourcesKey {
14731473
final private String mResDir;
1474+
final private Configuration mOverrideConfiguration;
14741475
final private float mScale;
14751476
final private int mHash;
14761477

1477-
ResourcesKey(String resDir, float scale) {
1478+
ResourcesKey(String resDir, Configuration overrideConfiguration, float scale) {
14781479
mResDir = resDir;
1480+
if (overrideConfiguration != null) {
1481+
if (Configuration.EMPTY.equals(overrideConfiguration)) {
1482+
overrideConfiguration = null;
1483+
}
1484+
}
1485+
mOverrideConfiguration = overrideConfiguration;
14791486
mScale = scale;
1480-
mHash = mResDir.hashCode() << 2 + (int) (mScale * 2);
1487+
int hash = 17;
1488+
hash = 31 * hash + mResDir.hashCode();
1489+
hash = 31 * hash + (mOverrideConfiguration != null
1490+
? mOverrideConfiguration.hashCode() : 0);
1491+
hash = 31 * hash + Float.floatToIntBits(mScale);
1492+
mHash = hash;
14811493
}
14821494

14831495
@Override
@@ -1491,7 +1503,21 @@ public boolean equals(Object obj) {
14911503
return false;
14921504
}
14931505
ResourcesKey peer = (ResourcesKey) obj;
1494-
return mResDir.equals(peer.mResDir) && mScale == peer.mScale;
1506+
if (!mResDir.equals(peer.mResDir)) {
1507+
return false;
1508+
}
1509+
if (mOverrideConfiguration != peer.mOverrideConfiguration) {
1510+
if (mOverrideConfiguration == null || peer.mOverrideConfiguration == null) {
1511+
return false;
1512+
}
1513+
if (!mOverrideConfiguration.equals(peer.mOverrideConfiguration)) {
1514+
return false;
1515+
}
1516+
}
1517+
if (mScale != peer.mScale) {
1518+
return false;
1519+
}
1520+
return true;
14951521
}
14961522
}
14971523

@@ -1562,8 +1588,10 @@ Configuration applyConfigCompatMainThread(int displayDensity, Configuration conf
15621588
* @param compInfo the compability info. It will use the default compatibility info when it's
15631589
* null.
15641590
*/
1565-
Resources getTopLevelResources(String resDir, CompatibilityInfo compInfo) {
1566-
ResourcesKey key = new ResourcesKey(resDir, compInfo.applicationScale);
1591+
Resources getTopLevelResources(String resDir, Configuration overrideConfiguration,
1592+
CompatibilityInfo compInfo) {
1593+
ResourcesKey key = new ResourcesKey(resDir, overrideConfiguration,
1594+
compInfo.applicationScale);
15671595
Resources r;
15681596
synchronized (mPackages) {
15691597
// Resources is app scale dependent.
@@ -1595,13 +1623,20 @@ Resources getTopLevelResources(String resDir, CompatibilityInfo compInfo) {
15951623

15961624
//Slog.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics);
15971625
DisplayMetrics metrics = getDisplayMetricsLocked(null, false);
1598-
r = new Resources(assets, metrics, getConfiguration(), compInfo);
1626+
Configuration config;
1627+
if (key.mOverrideConfiguration != null) {
1628+
config = new Configuration(getConfiguration());
1629+
config.updateFrom(key.mOverrideConfiguration);
1630+
} else {
1631+
config = getConfiguration();
1632+
}
1633+
r = new Resources(assets, metrics, config, compInfo);
15991634
if (false) {
16001635
Slog.i(TAG, "Created app resources " + resDir + " " + r + ": "
16011636
+ r.getConfiguration() + " appScale="
16021637
+ r.getCompatibilityInfo().applicationScale);
16031638
}
1604-
1639+
16051640
synchronized (mPackages) {
16061641
WeakReference<Resources> wr = mActiveResources.get(key);
16071642
Resources existing = wr != null ? wr.get() : null;
@@ -1621,8 +1656,10 @@ Resources getTopLevelResources(String resDir, CompatibilityInfo compInfo) {
16211656
/**
16221657
* Creates the top level resources for the given package.
16231658
*/
1624-
Resources getTopLevelResources(String resDir, LoadedApk pkgInfo) {
1625-
return getTopLevelResources(resDir, pkgInfo.mCompatibilityInfo.get());
1659+
Resources getTopLevelResources(String resDir, Configuration overrideConfiguration,
1660+
LoadedApk pkgInfo) {
1661+
return getTopLevelResources(resDir, overrideConfiguration,
1662+
pkgInfo.mCompatibilityInfo.get());
16261663
}
16271664

16281665
final Handler getHandler() {
@@ -3675,18 +3712,28 @@ final boolean applyConfigurationToResourcesLocked(Configuration config,
36753712

36763713
ApplicationPackageManager.configurationChanged();
36773714
//Slog.i(TAG, "Configuration changed in " + currentPackageName());
3678-
3679-
Iterator<WeakReference<Resources>> it =
3680-
mActiveResources.values().iterator();
3681-
//Iterator<Map.Entry<String, WeakReference<Resources>>> it =
3682-
// mActiveResources.entrySet().iterator();
3715+
3716+
Configuration tmpConfig = null;
3717+
3718+
Iterator<Map.Entry<ResourcesKey, WeakReference<Resources>>> it =
3719+
mActiveResources.entrySet().iterator();
36833720
while (it.hasNext()) {
3684-
WeakReference<Resources> v = it.next();
3685-
Resources r = v.get();
3721+
Map.Entry<ResourcesKey, WeakReference<Resources>> entry = it.next();
3722+
Resources r = entry.getValue().get();
36863723
if (r != null) {
36873724
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources "
36883725
+ r + " config to: " + config);
3689-
r.updateConfiguration(config, dm, compat);
3726+
Configuration override = entry.getKey().mOverrideConfiguration;
3727+
if (override != null) {
3728+
if (tmpConfig == null) {
3729+
tmpConfig = new Configuration();
3730+
}
3731+
tmpConfig.setTo(config);
3732+
tmpConfig.updateFrom(override);
3733+
r.updateConfiguration(tmpConfig, dm, compat);
3734+
} else {
3735+
r.updateConfiguration(config, dm, compat);
3736+
}
36903737
//Slog.i(TAG, "Updated app resources " + v.getKey()
36913738
// + " " + r + ": " + r.getConfiguration());
36923739
} else {

core/java/android/app/ApplicationPackageManager.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -713,7 +713,7 @@ public Drawable getApplicationLogo(String packageName)
713713
}
714714
Resources r = mContext.mMainThread.getTopLevelResources(
715715
app.uid == Process.myUid() ? app.sourceDir
716-
: app.publicSourceDir, mContext.mPackageInfo);
716+
: app.publicSourceDir, null, mContext.mPackageInfo);
717717
if (r != null) {
718718
return r;
719719
}

core/java/android/app/ContextImpl.java

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import android.content.pm.PackageManager;
3838
import android.content.res.AssetManager;
3939
import android.content.res.CompatibilityInfo;
40+
import android.content.res.Configuration;
4041
import android.content.res.Resources;
4142
import android.database.DatabaseErrorHandler;
4243
import android.database.sqlite.SQLiteDatabase;
@@ -525,7 +526,7 @@ static ContextImpl getImpl(Context context) {
525526

526527
@Override
527528
public AssetManager getAssets() {
528-
return mResources.getAssets();
529+
return getResources().getAssets();
529530
}
530531

531532
@Override
@@ -1590,6 +1591,16 @@ public Context createPackageContext(String packageName, int flags)
15901591
"Application package " + packageName + " not found");
15911592
}
15921593

1594+
@Override
1595+
public Context createConfigurationContext(Configuration overrideConfiguration) {
1596+
ContextImpl c = new ContextImpl();
1597+
c.init(mPackageInfo, null, mMainThread);
1598+
c.mResources = mMainThread.getTopLevelResources(
1599+
mPackageInfo.getResDir(), overrideConfiguration,
1600+
mResources.getCompatibilityInfo());
1601+
return c;
1602+
}
1603+
15931604
@Override
15941605
public boolean isRestricted() {
15951606
return mRestricted;
@@ -1659,12 +1670,11 @@ final void init(LoadedApk packageInfo,
16591670
" compatiblity info:" + container.getDisplayMetrics());
16601671
}
16611672
mResources = mainThread.getTopLevelResources(
1662-
mPackageInfo.getResDir(), container.getCompatibilityInfo());
1673+
mPackageInfo.getResDir(), null, container.getCompatibilityInfo());
16631674
}
16641675
mMainThread = mainThread;
16651676
mContentResolver = new ApplicationContentResolver(this, mainThread);
1666-
1667-
setActivityToken(activityToken);
1677+
mActivityToken = activityToken;
16681678
}
16691679

16701680
final void init(Resources resources, ActivityThread mainThread) {
@@ -1691,10 +1701,6 @@ final Context getReceiverRestrictedContext() {
16911701
return mReceiverRestrictedContext = new ReceiverRestrictedContext(getOuterContext());
16921702
}
16931703

1694-
final void setActivityToken(IBinder token) {
1695-
mActivityToken = token;
1696-
}
1697-
16981704
final void setOuterContext(Context context) {
16991705
mOuterContext = context;
17001706
}

core/java/android/app/LoadedApk.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -471,7 +471,7 @@ public AssetManager getAssets(ActivityThread mainThread) {
471471

472472
public Resources getResources(ActivityThread mainThread) {
473473
if (mResources == null) {
474-
mResources = mainThread.getTopLevelResources(mResDir, this);
474+
mResources = mainThread.getTopLevelResources(mResDir, null, this);
475475
}
476476
return mResources;
477477
}

core/java/android/content/Context.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import android.content.pm.ApplicationInfo;
2020
import android.content.pm.PackageManager;
2121
import android.content.res.AssetManager;
22+
import android.content.res.Configuration;
2223
import android.content.res.Resources;
2324
import android.content.res.TypedArray;
2425
import android.database.DatabaseErrorHandler;
@@ -2444,6 +2445,23 @@ public abstract void enforceUriPermission(
24442445
public abstract Context createPackageContext(String packageName,
24452446
int flags) throws PackageManager.NameNotFoundException;
24462447

2448+
/**
2449+
* Return a new Context object for the current Context but whose resources
2450+
* are adjusted to match the given Configuration. Each call to this method
2451+
* returns a new instance of a Contex object; Context objects are not
2452+
* shared, however common state (ClassLoader, other Resources for the
2453+
* same configuration) may be so the Context itself can be fairly lightweight.
2454+
*
2455+
* @param overrideConfiguration A {@link Configuration} specifying what
2456+
* values to modify in the base Configuration of the original Context's
2457+
* resources. If the base configuration changes (such as due to an
2458+
* orientation change), the resources of this context will also change except
2459+
* for those that have been explicitly overridden with a value here.
2460+
*
2461+
* @return A Context for the application.
2462+
*/
2463+
public abstract Context createConfigurationContext(Configuration overrideConfiguration);
2464+
24472465
/**
24482466
* Indicates whether this Context is restricted.
24492467
*

core/java/android/content/ContextWrapper.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import android.content.pm.ApplicationInfo;
2020
import android.content.pm.PackageManager;
2121
import android.content.res.AssetManager;
22+
import android.content.res.Configuration;
2223
import android.content.res.Resources;
2324
import android.database.DatabaseErrorHandler;
2425
import android.database.sqlite.SQLiteDatabase;
@@ -532,6 +533,11 @@ public Context createPackageContext(String packageName, int flags)
532533
return mBase.createPackageContext(packageName, flags);
533534
}
534535

536+
@Override
537+
public Context createConfigurationContext(Configuration overrideConfiguration) {
538+
return mBase.createConfigurationContext(overrideConfiguration);
539+
}
540+
535541
@Override
536542
public boolean isRestricted() {
537543
return mBase.isRestricted();

core/java/android/content/res/Configuration.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@
3535
* <pre>Configuration config = getResources().getConfiguration();</pre>
3636
*/
3737
public final class Configuration implements Parcelable, Comparable<Configuration> {
38+
/** @hide */
39+
public static final Configuration EMPTY = new Configuration();
40+
3841
/**
3942
* Current user preference for the scaling factor for fonts, relative
4043
* to the base density scaling.

core/java/android/content/res/Resources.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1435,9 +1435,12 @@ public void updateConfiguration(Configuration config,
14351435
int configChanges = 0xfffffff;
14361436
if (config != null) {
14371437
mTmpConfig.setTo(config);
1438+
int density = config.densityDpi;
1439+
if (density == Configuration.DENSITY_DPI_UNDEFINED) {
1440+
density = mMetrics.noncompatDensityDpi;
1441+
}
14381442
if (mCompatibilityInfo != null) {
1439-
mCompatibilityInfo.applyToConfiguration(mMetrics.noncompatDensityDpi,
1440-
mTmpConfig);
1443+
mCompatibilityInfo.applyToConfiguration(density, mTmpConfig);
14411444
}
14421445
if (mTmpConfig.locale == null) {
14431446
mTmpConfig.locale = Locale.getDefault();
@@ -1448,6 +1451,10 @@ public void updateConfiguration(Configuration config,
14481451
if (mConfiguration.locale == null) {
14491452
mConfiguration.locale = Locale.getDefault();
14501453
}
1454+
if (mConfiguration.densityDpi != Configuration.DENSITY_DPI_UNDEFINED) {
1455+
mMetrics.densityDpi = mConfiguration.densityDpi;
1456+
mMetrics.density = mConfiguration.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE;
1457+
}
14511458
mMetrics.scaledDensity = mMetrics.density * mConfiguration.fontScale;
14521459

14531460
String locale = null;

core/java/android/view/ContextThemeWrapper.java

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import android.content.Context;
2020
import android.content.ContextWrapper;
21+
import android.content.res.Configuration;
2122
import android.content.res.Resources;
2223
import android.os.Build;
2324

@@ -30,6 +31,8 @@ public class ContextThemeWrapper extends ContextWrapper {
3031
private int mThemeResource;
3132
private Resources.Theme mTheme;
3233
private LayoutInflater mInflater;
34+
private Configuration mOverrideConfiguration;
35+
private Resources mResources;
3336

3437
public ContextThemeWrapper() {
3538
super(null);
@@ -45,6 +48,41 @@ public ContextThemeWrapper(Context base, int themeres) {
4548
super.attachBaseContext(newBase);
4649
mBase = newBase;
4750
}
51+
52+
/**
53+
* Call to set an "override configuration" on this context -- this is
54+
* a configuration that replies one or more values of the standard
55+
* configuration that is applied to the context. See
56+
* {@link Context#createConfigurationContext(Configuration)} for more
57+
* information.
58+
*
59+
* <p>This method can only be called once, and must be called before any
60+
* calls to {@link #getResources()} are made.
61+
*/
62+
public void applyOverrideConfiguration(Configuration overrideConfiguration) {
63+
if (mResources != null) {
64+
throw new IllegalStateException("getResources() has already been called");
65+
}
66+
if (mOverrideConfiguration != null) {
67+
throw new IllegalStateException("Override configuration has already been set");
68+
}
69+
mOverrideConfiguration = new Configuration(overrideConfiguration);
70+
}
71+
72+
@Override
73+
public Resources getResources() {
74+
if (mResources != null) {
75+
return mResources;
76+
}
77+
if (mOverrideConfiguration == null) {
78+
mResources = super.getResources();
79+
return mResources;
80+
} else {
81+
Context resc = createConfigurationContext(mOverrideConfiguration);
82+
mResources = resc.getResources();
83+
return mResources;
84+
}
85+
}
4886

4987
@Override public void setTheme(int resid) {
5088
mThemeResource = resid;

0 commit comments

Comments
 (0)