diff options
author | Dianne Hackborn <hackbod@google.com> | 2012-08-14 16:45:30 -0700 |
---|---|---|
committer | Dianne Hackborn <hackbod@google.com> | 2012-08-14 16:51:38 -0700 |
commit | 756220bd1912535840388a6743830d2e59ad4964 (patch) | |
tree | ee97c666d4bb1f015fd3e89463af09c712ce47e4 | |
parent | 863b19bc8fcfa436011781b67a237fcce3cb703a (diff) | |
download | frameworks_base-756220bd1912535840388a6743830d2e59ad4964.zip frameworks_base-756220bd1912535840388a6743830d2e59ad4964.tar.gz frameworks_base-756220bd1912535840388a6743830d2e59ad4964.tar.bz2 |
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
-rw-r--r-- | api/current.txt | 4 | ||||
-rw-r--r-- | core/java/android/app/ActivityThread.java | 81 | ||||
-rw-r--r-- | core/java/android/app/ApplicationPackageManager.java | 2 | ||||
-rw-r--r-- | core/java/android/app/ContextImpl.java | 22 | ||||
-rw-r--r-- | core/java/android/app/LoadedApk.java | 2 | ||||
-rw-r--r-- | core/java/android/content/Context.java | 18 | ||||
-rw-r--r-- | core/java/android/content/ContextWrapper.java | 6 | ||||
-rw-r--r-- | core/java/android/content/res/Configuration.java | 3 | ||||
-rwxr-xr-x | core/java/android/content/res/Resources.java | 11 | ||||
-rw-r--r-- | core/java/android/view/ContextThemeWrapper.java | 38 | ||||
-rw-r--r-- | test-runner/src/android/test/mock/MockContext.java | 6 | ||||
-rw-r--r-- | tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java | 33 | ||||
-rw-r--r-- | tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java | 6 |
13 files changed, 203 insertions, 29 deletions
diff --git a/api/current.txt b/api/current.txt index da3dd3e..8dbf9fb 100644 --- a/api/current.txt +++ b/api/current.txt @@ -5262,6 +5262,7 @@ package android.content { method public abstract int checkUriPermission(android.net.Uri, int, int, int); method public abstract int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int); method public abstract deprecated void clearWallpaper() throws java.io.IOException; + method public abstract android.content.Context createConfigurationContext(android.content.res.Configuration); method public abstract android.content.Context createPackageContext(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException; method public abstract java.lang.String[] databaseList(); method public abstract boolean deleteDatabase(java.lang.String); @@ -5406,6 +5407,7 @@ package android.content { method public int checkUriPermission(android.net.Uri, int, int, int); method public int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int); method public void clearWallpaper() throws java.io.IOException; + method public android.content.Context createConfigurationContext(android.content.res.Configuration); method public android.content.Context createPackageContext(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException; method public java.lang.String[] databaseList(); method public boolean deleteDatabase(java.lang.String); @@ -21147,6 +21149,7 @@ package android.test.mock { method public int checkUriPermission(android.net.Uri, int, int, int); method public int checkUriPermission(android.net.Uri, java.lang.String, java.lang.String, int, int, int); method public void clearWallpaper(); + method public android.content.Context createConfigurationContext(android.content.res.Configuration); method public android.content.Context createPackageContext(java.lang.String, int) throws android.content.pm.PackageManager.NameNotFoundException; method public java.lang.String[] databaseList(); method public boolean deleteDatabase(java.lang.String); @@ -23351,6 +23354,7 @@ package android.view { public class ContextThemeWrapper extends android.content.ContextWrapper { ctor public ContextThemeWrapper(); ctor public ContextThemeWrapper(android.content.Context, int); + method public void applyOverrideConfiguration(android.content.res.Configuration); method protected void onApplyThemeResource(android.content.res.Resources.Theme, int, boolean); } diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index bb35ddd..0789c60 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -1471,13 +1471,25 @@ public final class ActivityThread { private static class ResourcesKey { final private String mResDir; + final private Configuration mOverrideConfiguration; final private float mScale; final private int mHash; - ResourcesKey(String resDir, float scale) { + ResourcesKey(String resDir, Configuration overrideConfiguration, float scale) { mResDir = resDir; + if (overrideConfiguration != null) { + if (Configuration.EMPTY.equals(overrideConfiguration)) { + overrideConfiguration = null; + } + } + mOverrideConfiguration = overrideConfiguration; mScale = scale; - mHash = mResDir.hashCode() << 2 + (int) (mScale * 2); + int hash = 17; + hash = 31 * hash + mResDir.hashCode(); + hash = 31 * hash + (mOverrideConfiguration != null + ? mOverrideConfiguration.hashCode() : 0); + hash = 31 * hash + Float.floatToIntBits(mScale); + mHash = hash; } @Override @@ -1491,7 +1503,21 @@ public final class ActivityThread { return false; } ResourcesKey peer = (ResourcesKey) obj; - return mResDir.equals(peer.mResDir) && mScale == peer.mScale; + if (!mResDir.equals(peer.mResDir)) { + return false; + } + if (mOverrideConfiguration != peer.mOverrideConfiguration) { + if (mOverrideConfiguration == null || peer.mOverrideConfiguration == null) { + return false; + } + if (!mOverrideConfiguration.equals(peer.mOverrideConfiguration)) { + return false; + } + } + if (mScale != peer.mScale) { + return false; + } + return true; } } @@ -1562,8 +1588,10 @@ public final class ActivityThread { * @param compInfo the compability info. It will use the default compatibility info when it's * null. */ - Resources getTopLevelResources(String resDir, CompatibilityInfo compInfo) { - ResourcesKey key = new ResourcesKey(resDir, compInfo.applicationScale); + Resources getTopLevelResources(String resDir, Configuration overrideConfiguration, + CompatibilityInfo compInfo) { + ResourcesKey key = new ResourcesKey(resDir, overrideConfiguration, + compInfo.applicationScale); Resources r; synchronized (mPackages) { // Resources is app scale dependent. @@ -1595,13 +1623,20 @@ public final class ActivityThread { //Slog.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics); DisplayMetrics metrics = getDisplayMetricsLocked(null, false); - r = new Resources(assets, metrics, getConfiguration(), compInfo); + Configuration config; + if (key.mOverrideConfiguration != null) { + config = new Configuration(getConfiguration()); + config.updateFrom(key.mOverrideConfiguration); + } else { + config = getConfiguration(); + } + r = new Resources(assets, metrics, config, compInfo); if (false) { Slog.i(TAG, "Created app resources " + resDir + " " + r + ": " + r.getConfiguration() + " appScale=" + r.getCompatibilityInfo().applicationScale); } - + synchronized (mPackages) { WeakReference<Resources> wr = mActiveResources.get(key); Resources existing = wr != null ? wr.get() : null; @@ -1621,8 +1656,10 @@ public final class ActivityThread { /** * Creates the top level resources for the given package. */ - Resources getTopLevelResources(String resDir, LoadedApk pkgInfo) { - return getTopLevelResources(resDir, pkgInfo.mCompatibilityInfo.get()); + Resources getTopLevelResources(String resDir, Configuration overrideConfiguration, + LoadedApk pkgInfo) { + return getTopLevelResources(resDir, overrideConfiguration, + pkgInfo.mCompatibilityInfo.get()); } final Handler getHandler() { @@ -3675,18 +3712,28 @@ public final class ActivityThread { ApplicationPackageManager.configurationChanged(); //Slog.i(TAG, "Configuration changed in " + currentPackageName()); - - Iterator<WeakReference<Resources>> it = - mActiveResources.values().iterator(); - //Iterator<Map.Entry<String, WeakReference<Resources>>> it = - // mActiveResources.entrySet().iterator(); + + Configuration tmpConfig = null; + + Iterator<Map.Entry<ResourcesKey, WeakReference<Resources>>> it = + mActiveResources.entrySet().iterator(); while (it.hasNext()) { - WeakReference<Resources> v = it.next(); - Resources r = v.get(); + Map.Entry<ResourcesKey, WeakReference<Resources>> entry = it.next(); + Resources r = entry.getValue().get(); if (r != null) { if (DEBUG_CONFIGURATION) Slog.v(TAG, "Changing resources " + r + " config to: " + config); - r.updateConfiguration(config, dm, compat); + Configuration override = entry.getKey().mOverrideConfiguration; + if (override != null) { + if (tmpConfig == null) { + tmpConfig = new Configuration(); + } + tmpConfig.setTo(config); + tmpConfig.updateFrom(override); + r.updateConfiguration(tmpConfig, dm, compat); + } else { + r.updateConfiguration(config, dm, compat); + } //Slog.i(TAG, "Updated app resources " + v.getKey() // + " " + r + ": " + r.getConfiguration()); } else { diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java index 2face4c..9b59e2c 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -713,7 +713,7 @@ final class ApplicationPackageManager extends PackageManager { } Resources r = mContext.mMainThread.getTopLevelResources( app.uid == Process.myUid() ? app.sourceDir - : app.publicSourceDir, mContext.mPackageInfo); + : app.publicSourceDir, null, mContext.mPackageInfo); if (r != null) { return r; } diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 4496ce8..1ef14eb 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -37,6 +37,7 @@ import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.res.AssetManager; import android.content.res.CompatibilityInfo; +import android.content.res.Configuration; import android.content.res.Resources; import android.database.DatabaseErrorHandler; import android.database.sqlite.SQLiteDatabase; @@ -525,7 +526,7 @@ class ContextImpl extends Context { @Override public AssetManager getAssets() { - return mResources.getAssets(); + return getResources().getAssets(); } @Override @@ -1591,6 +1592,16 @@ class ContextImpl extends Context { } @Override + public Context createConfigurationContext(Configuration overrideConfiguration) { + ContextImpl c = new ContextImpl(); + c.init(mPackageInfo, null, mMainThread); + c.mResources = mMainThread.getTopLevelResources( + mPackageInfo.getResDir(), overrideConfiguration, + mResources.getCompatibilityInfo()); + return c; + } + + @Override public boolean isRestricted() { return mRestricted; } @@ -1659,12 +1670,11 @@ class ContextImpl extends Context { " compatiblity info:" + container.getDisplayMetrics()); } mResources = mainThread.getTopLevelResources( - mPackageInfo.getResDir(), container.getCompatibilityInfo()); + mPackageInfo.getResDir(), null, container.getCompatibilityInfo()); } mMainThread = mainThread; mContentResolver = new ApplicationContentResolver(this, mainThread); - - setActivityToken(activityToken); + mActivityToken = activityToken; } final void init(Resources resources, ActivityThread mainThread) { @@ -1691,10 +1701,6 @@ class ContextImpl extends Context { return mReceiverRestrictedContext = new ReceiverRestrictedContext(getOuterContext()); } - final void setActivityToken(IBinder token) { - mActivityToken = token; - } - final void setOuterContext(Context context) { mOuterContext = context; } diff --git a/core/java/android/app/LoadedApk.java b/core/java/android/app/LoadedApk.java index be4b284..f4195d6 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -471,7 +471,7 @@ public final class LoadedApk { public Resources getResources(ActivityThread mainThread) { if (mResources == null) { - mResources = mainThread.getTopLevelResources(mResDir, this); + mResources = mainThread.getTopLevelResources(mResDir, null, this); } return mResources; } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index bf60a96..a90142a 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -19,6 +19,7 @@ package android.content; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.AssetManager; +import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.TypedArray; import android.database.DatabaseErrorHandler; @@ -2445,6 +2446,23 @@ public abstract class Context { int flags) throws PackageManager.NameNotFoundException; /** + * Return a new Context object for the current Context but whose resources + * are adjusted to match the given Configuration. Each call to this method + * returns a new instance of a Contex object; Context objects are not + * shared, however common state (ClassLoader, other Resources for the + * same configuration) may be so the Context itself can be fairly lightweight. + * + * @param overrideConfiguration A {@link Configuration} specifying what + * values to modify in the base Configuration of the original Context's + * resources. If the base configuration changes (such as due to an + * orientation change), the resources of this context will also change except + * for those that have been explicitly overridden with a value here. + * + * @return A Context for the application. + */ + public abstract Context createConfigurationContext(Configuration overrideConfiguration); + + /** * Indicates whether this Context is restricted. * * @return True if this Context is restricted, false otherwise. diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index ff4c9a1..fdf60ab 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -19,6 +19,7 @@ package android.content; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.AssetManager; +import android.content.res.Configuration; import android.content.res.Resources; import android.database.DatabaseErrorHandler; import android.database.sqlite.SQLiteDatabase; @@ -533,6 +534,11 @@ public class ContextWrapper extends Context { } @Override + public Context createConfigurationContext(Configuration overrideConfiguration) { + return mBase.createConfigurationContext(overrideConfiguration); + } + + @Override public boolean isRestricted() { return mBase.isRestricted(); } diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java index ea13a2a..52b6498 100644 --- a/core/java/android/content/res/Configuration.java +++ b/core/java/android/content/res/Configuration.java @@ -35,6 +35,9 @@ import java.util.Locale; * <pre>Configuration config = getResources().getConfiguration();</pre> */ public final class Configuration implements Parcelable, Comparable<Configuration> { + /** @hide */ + public static final Configuration EMPTY = new Configuration(); + /** * Current user preference for the scaling factor for fonts, relative * to the base density scaling. diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index d2af3e9..26512e2 100755 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -1435,9 +1435,12 @@ public class Resources { int configChanges = 0xfffffff; if (config != null) { mTmpConfig.setTo(config); + int density = config.densityDpi; + if (density == Configuration.DENSITY_DPI_UNDEFINED) { + density = mMetrics.noncompatDensityDpi; + } if (mCompatibilityInfo != null) { - mCompatibilityInfo.applyToConfiguration(mMetrics.noncompatDensityDpi, - mTmpConfig); + mCompatibilityInfo.applyToConfiguration(density, mTmpConfig); } if (mTmpConfig.locale == null) { mTmpConfig.locale = Locale.getDefault(); @@ -1448,6 +1451,10 @@ public class Resources { if (mConfiguration.locale == null) { mConfiguration.locale = Locale.getDefault(); } + if (mConfiguration.densityDpi != Configuration.DENSITY_DPI_UNDEFINED) { + mMetrics.densityDpi = mConfiguration.densityDpi; + mMetrics.density = mConfiguration.densityDpi * DisplayMetrics.DENSITY_DEFAULT_SCALE; + } mMetrics.scaledDensity = mMetrics.density * mConfiguration.fontScale; String locale = null; diff --git a/core/java/android/view/ContextThemeWrapper.java b/core/java/android/view/ContextThemeWrapper.java index 626f385..6c733f9 100644 --- a/core/java/android/view/ContextThemeWrapper.java +++ b/core/java/android/view/ContextThemeWrapper.java @@ -18,6 +18,7 @@ package android.view; import android.content.Context; import android.content.ContextWrapper; +import android.content.res.Configuration; import android.content.res.Resources; import android.os.Build; @@ -30,6 +31,8 @@ public class ContextThemeWrapper extends ContextWrapper { private int mThemeResource; private Resources.Theme mTheme; private LayoutInflater mInflater; + private Configuration mOverrideConfiguration; + private Resources mResources; public ContextThemeWrapper() { super(null); @@ -45,6 +48,41 @@ public class ContextThemeWrapper extends ContextWrapper { super.attachBaseContext(newBase); mBase = newBase; } + + /** + * Call to set an "override configuration" on this context -- this is + * a configuration that replies one or more values of the standard + * configuration that is applied to the context. See + * {@link Context#createConfigurationContext(Configuration)} for more + * information. + * + * <p>This method can only be called once, and must be called before any + * calls to {@link #getResources()} are made. + */ + public void applyOverrideConfiguration(Configuration overrideConfiguration) { + if (mResources != null) { + throw new IllegalStateException("getResources() has already been called"); + } + if (mOverrideConfiguration != null) { + throw new IllegalStateException("Override configuration has already been set"); + } + mOverrideConfiguration = new Configuration(overrideConfiguration); + } + + @Override + public Resources getResources() { + if (mResources != null) { + return mResources; + } + if (mOverrideConfiguration == null) { + mResources = super.getResources(); + return mResources; + } else { + Context resc = createConfigurationContext(mOverrideConfiguration); + mResources = resc.getResources(); + return mResources; + } + } @Override public void setTheme(int resid) { mThemeResource = resid; diff --git a/test-runner/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java index 9acffa3..36f2c14 100644 --- a/test-runner/src/android/test/mock/MockContext.java +++ b/test-runner/src/android/test/mock/MockContext.java @@ -28,6 +28,7 @@ import android.content.SharedPreferences; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.res.AssetManager; +import android.content.res.Configuration; import android.content.res.Resources; import android.database.DatabaseErrorHandler; import android.database.sqlite.SQLiteDatabase; @@ -477,6 +478,11 @@ public class MockContext extends Context { } @Override + public Context createConfigurationContext(Configuration overrideConfiguration) { + throw new UnsupportedOperationException(); + } + + @Override public boolean isRestricted() { throw new UnsupportedOperationException(); } diff --git a/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java b/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java index 0ec1f13..0577dbb 100644 --- a/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java +++ b/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java @@ -40,12 +40,16 @@ import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.content.Context; +import android.content.res.Configuration; import android.util.Log; public class ActivityTestMain extends Activity { static final String TAG = "ActivityTest"; + static final String KEY_CONFIGURATION = "configuration"; + ActivityManager mAm; + Configuration mOverrideConfig; class BroadcastResultReceiver extends BroadcastReceiver { @Override @@ -111,6 +115,12 @@ public class ActivityTestMain extends Activity { super.onCreate(savedInstanceState); mAm = (ActivityManager)getSystemService(ACTIVITY_SERVICE); + if (savedInstanceState != null) { + mOverrideConfig = savedInstanceState.getParcelable(KEY_CONFIGURATION); + if (mOverrideConfig != null) { + applyOverrideConfiguration(mOverrideConfig); + } + } } @Override @@ -182,6 +192,21 @@ public class ActivityTestMain extends Activity { return true; } }); + menu.add("Density!").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override public boolean onMenuItemClick(MenuItem item) { + if (mOverrideConfig == null) { + mOverrideConfig = new Configuration(); + } + if (mOverrideConfig.densityDpi == Configuration.DENSITY_DPI_UNDEFINED) { + mOverrideConfig.densityDpi = (getApplicationContext().getResources() + .getConfiguration().densityDpi*2)/3; + } else { + mOverrideConfig.densityDpi = Configuration.DENSITY_DPI_UNDEFINED; + } + recreate(); + return true; + } + }); return true; } @@ -191,6 +216,14 @@ public class ActivityTestMain extends Activity { buildUi(); } + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + if (mOverrideConfig != null) { + outState.putParcelable(KEY_CONFIGURATION, mOverrideConfig); + } + } + private View scrollWrap(View view) { ScrollView scroller = new ScrollView(this); scroller.addView(view, new ScrollView.LayoutParams(ScrollView.LayoutParams.MATCH_PARENT, diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java index c4a6906..0a1191b 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java @@ -917,6 +917,12 @@ public final class BridgeContext extends Context { } @Override + public Context createConfigurationContext(Configuration overrideConfiguration) { + // pass + return null; + } + + @Override public String[] databaseList() { // pass return null; |