diff options
author | Alan Viverette <alanv@google.com> | 2014-03-24 18:00:26 -0700 |
---|---|---|
committer | Alan Viverette <alanv@google.com> | 2014-03-24 18:00:26 -0700 |
commit | 52b999f0721b53e9c6e18a4bd664e89aeb65b2d5 (patch) | |
tree | 3ef7369c91bd51fae75769c23ebfb71e04d3989c /core | |
parent | 852472d9aa39eb4591eac43487ac3e0944e1daf6 (diff) | |
download | frameworks_base-52b999f0721b53e9c6e18a4bd664e89aeb65b2d5.zip frameworks_base-52b999f0721b53e9c6e18a4bd664e89aeb65b2d5.tar.gz frameworks_base-52b999f0721b53e9c6e18a4bd664e89aeb65b2d5.tar.bz2 |
Implement APIs for obtaining, caching themed Drawables
When Drawables are inflated during preload (or otherwise without a theme)
they cache their themeable attributes in their constant state as an array
keyed on attribute index. Drawables inflated with a theme will simply
resolve theme attributes as part of normal inflation, and they will not
cache any themeable attributes.
Drawables obtained from Resources are pulled from theme-specific cache
when possible. If an unthemed Drawable exists in the preload cache, a
new constant state will be obtained for the Drawable and the theme will
be applied by resolving the cached themeable attributes and overwriting
their respective constant state properties. If no cached version exists,
a new Drawable is inflated against the desired theme.
Constant states from themed drawables may be cached if the applied theme
is "pure" and was loaded from a style resource without any subsequent
modifications.
This CL does not handle applying themes to several Drawable types, but it
fully supports BitmapDrawable, GradientDrawable, NinePatchDrawable,
ColorDrawable, and TouchFeedbackDrawable.
BUG: 12611005
Change-Id: I4e794fbb62f7a371715f4ebdf946ee5f9a5ad1c9
Diffstat (limited to 'core')
-rw-r--r-- | core/java/android/content/res/AssetManager.java | 3 | ||||
-rw-r--r-- | core/java/android/content/res/Resources.java | 499 | ||||
-rw-r--r-- | core/java/android/content/res/TypedArray.java | 125 | ||||
-rw-r--r-- | core/java/android/widget/ImageView.java | 2 | ||||
-rw-r--r-- | core/java/android/widget/SuggestionsAdapter.java | 2 | ||||
-rw-r--r-- | core/jni/android_util_AssetManager.cpp | 194 |
6 files changed, 628 insertions, 197 deletions
diff --git a/core/java/android/content/res/AssetManager.java b/core/java/android/content/res/AssetManager.java index a41b4f9..2f8dd53 100644 --- a/core/java/android/content/res/AssetManager.java +++ b/core/java/android/content/res/AssetManager.java @@ -722,6 +722,9 @@ public final class AssetManager { /*package*/ native static final boolean applyStyle(long theme, int defStyleAttr, int defStyleRes, long xmlParser, int[] inAttrs, int[] outValues, int[] outIndices); + /*package*/ native static final boolean resolveAttrs(long theme, + int defStyleAttr, int defStyleRes, int[] inValues, + int[] inAttrs, int[] outValues, int[] outIndices); /*package*/ native final boolean retrieveAttributes( long xmlParser, int[] inAttrs, int[] outValues, int[] outIndices); /*package*/ native final int getArraySize(int resource); diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index 5c27072..affc784 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -34,6 +34,7 @@ import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; import android.util.Slog; +import android.util.SparseArray; import android.util.TypedValue; import android.util.LongSparseArray; @@ -74,7 +75,6 @@ public class Resources { private static final boolean DEBUG_LOAD = false; private static final boolean DEBUG_CONFIG = false; - private static final boolean DEBUG_ATTRIBUTES_CACHE = false; private static final boolean TRACE_FOR_PRELOAD = false; private static final boolean TRACE_FOR_MISS_PRELOAD = false; @@ -88,9 +88,9 @@ public class Resources { // Information about preloaded resources. Note that they are not // protected by a lock, because while preloading in zygote we are all // single-threaded, and after that these are immutable. - private static final LongSparseArray<Drawable.ConstantState>[] sPreloadedDrawables; - private static final LongSparseArray<Drawable.ConstantState> sPreloadedColorDrawables - = new LongSparseArray<Drawable.ConstantState>(); + private static final LongSparseArray<ConstantState>[] sPreloadedDrawables; + private static final LongSparseArray<ConstantState> sPreloadedColorDrawables + = new LongSparseArray<ConstantState>(); private static final LongSparseArray<ColorStateList> sPreloadedColorStateLists = new LongSparseArray<ColorStateList>(); @@ -103,12 +103,12 @@ public class Resources { // These are protected by mAccessLock. private final Object mAccessLock = new Object(); private final Configuration mTmpConfig = new Configuration(); - private final LongSparseArray<WeakReference<Drawable.ConstantState>> mDrawableCache - = new LongSparseArray<WeakReference<Drawable.ConstantState>>(0); - private final LongSparseArray<WeakReference<ColorStateList>> mColorStateListCache - = new LongSparseArray<WeakReference<ColorStateList>>(0); - private final LongSparseArray<WeakReference<Drawable.ConstantState>> mColorDrawableCache - = new LongSparseArray<WeakReference<Drawable.ConstantState>>(0); + private final ThemedCaches<ConstantState> mDrawableCache = + new ThemedCaches<ConstantState>(); + private final ThemedCaches<ConstantState> mColorDrawableCache = + new ThemedCaches<ConstantState>(); + private final LongSparseArray<WeakReference<ColorStateList>> mColorStateListCache = + new LongSparseArray<WeakReference<ColorStateList>>(); private TypedValue mTmpValue = new TypedValue(); private boolean mPreloading; @@ -126,12 +126,14 @@ public class Resources { private NativePluralRules mPluralRule; private CompatibilityInfo mCompatibilityInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO; + + @SuppressWarnings("unused") private WeakReference<IBinder> mToken; static { sPreloadedDrawables = new LongSparseArray[2]; - sPreloadedDrawables[0] = new LongSparseArray<Drawable.ConstantState>(); - sPreloadedDrawables[1] = new LongSparseArray<Drawable.ConstantState>(); + sPreloadedDrawables[0] = new LongSparseArray<ConstantState>(); + sPreloadedDrawables[1] = new LongSparseArray<ConstantState>(); } /** @hide */ @@ -518,7 +520,7 @@ public class Resources { + Integer.toHexString(id)); } - TypedArray array = getCachedStyledAttributes(len); + TypedArray array = TypedArray.obtain(this, len); array.mLength = mAssets.retrieveArray(id, array.mData); array.mIndices[0] = 0; @@ -1250,6 +1252,13 @@ public class Resources { */ public void applyStyle(int resid, boolean force) { AssetManager.applyThemeStyle(mTheme, resid, force); + + if (!mHasStyle) { + mHasStyle = true; + mThemeResId = resid; + } else if (resid != mThemeResId) { + mThemeResId = 0; + } } /** @@ -1263,6 +1272,9 @@ public class Resources { */ public void setTo(Theme other) { AssetManager.copyTheme(mTheme, other.mTheme); + + mHasStyle = other.mHasStyle; + mThemeResId = other.mThemeResId; } /** @@ -1286,11 +1298,9 @@ public class Resources { */ public TypedArray obtainStyledAttributes(int[] attrs) { final int len = attrs.length; - final TypedArray array = getCachedStyledAttributes(len); + final TypedArray array = TypedArray.obtain(Resources.this, len); array.mTheme = this; - array.mRsrcs = attrs; - AssetManager.applyStyle(mTheme, 0, 0, 0, attrs, - array.mData, array.mIndices); + AssetManager.applyStyle(mTheme, 0, 0, 0, attrs, array.mData, array.mIndices); return array; } @@ -1316,12 +1326,8 @@ public class Resources { */ public TypedArray obtainStyledAttributes(int resid, int[] attrs) throws NotFoundException { final int len = attrs.length; - final TypedArray array = getCachedStyledAttributes(len); + final TypedArray array = TypedArray.obtain(Resources.this, len); array.mTheme = this; - array.mRsrcs = attrs; - - AssetManager.applyStyle(mTheme, 0, resid, 0, attrs, - array.mData, array.mIndices); if (false) { int[] data = array.mData; @@ -1348,6 +1354,7 @@ public class Resources { } System.out.println(s); } + AssetManager.applyStyle(mTheme, 0, resid, 0, attrs, array.mData, array.mIndices); return array; } @@ -1402,7 +1409,7 @@ public class Resources { public TypedArray obtainStyledAttributes(AttributeSet set, int[] attrs, int defStyleAttr, int defStyleRes) { final int len = attrs.length; - final TypedArray array = getCachedStyledAttributes(len); + final TypedArray array = TypedArray.obtain(Resources.this, len); // XXX note that for now we only work with compiled XML files. // To support generic XML files we will need to manually parse @@ -1413,7 +1420,6 @@ public class Resources { parser != null ? parser.mParseState : 0, attrs, array.mData, array.mIndices); array.mTheme = this; - array.mRsrcs = attrs; array.mXml = parser; if (false) { @@ -1449,6 +1455,45 @@ public class Resources { } /** + * Retrieve the values for a set of attributes in the Theme. The + * contents of the typed array are ultimately filled in by + * {@link Resources#getValue}. + * + * @param values The base set of attribute values, must be equal + * in length to {@code attrs} or {@code null}. All values + * must be of type {@link TypedValue#TYPE_ATTRIBUTE}. + * @param attrs The desired attributes to be retrieved. + * @param defStyleAttr An attribute in the current theme that contains a + * reference to a style resource that supplies + * defaults values for the TypedArray. Can be + * 0 to not look for defaults. + * @param defStyleRes A resource identifier of a style resource that + * supplies default values for the TypedArray, + * used only if defStyleAttr is 0 or can not be found + * in the theme. Can be 0 to not look for defaults. + * @return Returns a TypedArray holding an array of the attribute + * values. Be sure to call {@link TypedArray#recycle()} + * when done with it. + * @hide + */ + public TypedArray resolveAttributes(int[] values, int[] attrs, + int defStyleAttr, int defStyleRes) { + final int len = attrs.length; + if (values != null && len != values.length) { + throw new IllegalArgumentException( + "Base attribute values must be null or the same length as attrs"); + } + + final TypedArray array = TypedArray.obtain(Resources.this, len); + AssetManager.resolveAttrs(mTheme, defStyleAttr, defStyleRes, + values, attrs, array.mData, array.mIndices); + array.mTheme = this; + array.mXml = null; + + return array; + } + + /** * Retrieve the value of an attribute in the Theme. The contents of * <var>outValue</var> are ultimately filled in by * {@link Resources#getValue}. @@ -1465,8 +1510,7 @@ public class Resources { * @return boolean Returns true if the attribute was found and * <var>outValue</var> is valid, else false. */ - public boolean resolveAttribute(int resid, TypedValue outValue, - boolean resolveRefs) { + public boolean resolveAttribute(int resid, TypedValue outValue, boolean resolveRefs) { boolean got = mAssets.getThemeValue(mTheme, resid, outValue, resolveRefs); if (false) { System.out.println( @@ -1478,6 +1522,15 @@ public class Resources { } /** + * Returns the resources to which this theme belongs. + * + * @return Resources to which this theme belongs. + */ + public Resources getResources() { + return Resources.this; + } + + /** * Return a drawable object associated with a particular resource ID * and styled for the Theme. * @@ -1509,6 +1562,10 @@ public class Resources { mAssets.releaseTheme(mTheme); } + /*package*/ boolean canCacheDrawables() { + return mHasStyle && mThemeResId != 0; + } + /*package*/ Theme() { mAssets = Resources.this.mAssets; mTheme = mAssets.createTheme(); @@ -1517,6 +1574,13 @@ public class Resources { @SuppressWarnings("hiding") private final AssetManager mAssets; private final long mTheme; + + /** + * Resource identifier for the theme. If multiple styles have been + * applied to this theme, this value will be 0 (invalid). + */ + private int mThemeResId = 0; + private boolean mHasStyle = false; } /** @@ -1543,7 +1607,7 @@ public class Resources { */ public TypedArray obtainAttributes(AttributeSet set, int[] attrs) { int len = attrs.length; - TypedArray array = getCachedStyledAttributes(len); + TypedArray array = TypedArray.obtain(this, len); // XXX note that for now we only work with compiled XML files. // To support generic XML files we will need to manually parse @@ -1553,7 +1617,6 @@ public class Resources { mAssets.retrieveAttributes(parser.mParseState, attrs, array.mData, array.mIndices); - array.mRsrcs = attrs; array.mXml = parser; return array; @@ -1658,8 +1721,8 @@ public class Resources { + " final compat is " + mCompatibilityInfo); } - clearDrawableCacheLocked(mDrawableCache, configChanges); - clearDrawableCacheLocked(mColorDrawableCache, configChanges); + clearDrawableCachesLocked(mDrawableCache, configChanges); + clearDrawableCachesLocked(mColorDrawableCache, configChanges); mColorStateListCache.clear(); @@ -1672,18 +1735,25 @@ public class Resources { } } + private void clearDrawableCachesLocked( + ThemedCaches<ConstantState> caches, int configChanges) { + final int N = caches.size(); + for (int i = 0; i < N; i++) { + clearDrawableCacheLocked(caches.valueAt(i), configChanges); + } + } + private void clearDrawableCacheLocked( - LongSparseArray<WeakReference<ConstantState>> cache, - int configChanges) { - int N = cache.size(); + LongSparseArray<WeakReference<ConstantState>> cache, int configChanges) { if (DEBUG_CONFIG) { Log.d(TAG, "Cleaning up drawables config changes: 0x" + Integer.toHexString(configChanges)); } - for (int i=0; i<N; i++) { - WeakReference<Drawable.ConstantState> ref = cache.valueAt(i); + final int N = cache.size(); + for (int i = 0; i < N; i++) { + final WeakReference<ConstantState> ref = cache.valueAt(i); if (ref != null) { - Drawable.ConstantState cs = ref.get(); + final ConstantState cs = ref.get(); if (cs != null) { if (Configuration.needNewResources( configChanges, cs.getChangingConfigurations())) { @@ -2080,7 +2150,7 @@ public class Resources { /** * @hide */ - public LongSparseArray<Drawable.ConstantState> getPreloadedDrawables() { + public LongSparseArray<ConstantState> getPreloadedDrawables() { return sPreloadedDrawables[0]; } @@ -2098,6 +2168,8 @@ public class Resources { } catch (NotFoundException e) { resName = "?"; } + // This should never happen in production, so we should log a + // warning even if we're not debugging. Log.w(TAG, "Preloaded " + name + " resource #0x" + Integer.toHexString(resourceId) + " (" + resName + ") that varies with configuration!!"); @@ -2128,155 +2200,211 @@ public class Resources { } } - boolean isColorDrawable = false; - if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT && - value.type <= TypedValue.TYPE_LAST_COLOR_INT) { + final boolean isColorDrawable; + final ThemedCaches<ConstantState> caches; + final long key; + if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT + && value.type <= TypedValue.TYPE_LAST_COLOR_INT) { isColorDrawable = true; + caches = mColorDrawableCache; + key = value.data; + } else { + isColorDrawable = false; + caches = mDrawableCache; + key = (((long) value.assetCookie) << 32) | value.data; } - final long key = isColorDrawable ? value.data : - (((long) value.assetCookie) << 32) | value.data; - Drawable dr = getCachedDrawable(isColorDrawable ? mColorDrawableCache : mDrawableCache, key); - - if (dr != null) { - return dr; + // First, check whether we have a cached version of this drawable + // that's valid for the specified theme. This may apply a theme to a + // cached drawable that has themeable attributes but was not previously + // themed. + if (!mPreloading) { + final Drawable cachedDrawable = getCachedDrawable(caches, key, theme); + if (cachedDrawable != null) { + return cachedDrawable; + } } - Drawable.ConstantState cs; + + // Next, check preloaded drawables. These are unthemed but may have + // themeable attributes. + final ConstantState cs; if (isColorDrawable) { cs = sPreloadedColorDrawables.get(key); } else { cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key); } + + final Drawable dr; if (cs != null) { - dr = cs.newDrawable(this); + dr = cs.newDrawable(this, theme); + } else if (isColorDrawable) { + dr = new ColorDrawable(value.data); } else { - if (isColorDrawable) { - dr = new ColorDrawable(value.data); - } + dr = loadDrawableForCookie(value, id, theme); + } - if (dr == null) { - if (value.string == null) { - throw new NotFoundException( - "Resource is not a Drawable (color or path): " + value); - } + // If we were able to obtain a drawable, attempt to place it in the + // appropriate cache (e.g. no theme, themed, themeable). + if (dr != null) { + dr.setChangingConfigurations(value.changingConfigurations); + cacheDrawable(value, theme, isColorDrawable, caches, key, dr); + } - String file = value.string.toString(); + return dr; + } - if (TRACE_FOR_MISS_PRELOAD) { - // Log only framework resources - if ((id >>> 24) == 0x1) { - final String name = getResourceName(id); - if (name != null) android.util.Log.d(TAG, "Loading framework drawable #" - + Integer.toHexString(id) + ": " + name - + " at " + file); - } - } + private void cacheDrawable(TypedValue value, Theme theme, boolean isColorDrawable, + ThemedCaches<ConstantState> caches, long key, Drawable dr) { + final ConstantState cs = dr.getConstantState(); + if (cs == null) { + return; + } - if (DEBUG_LOAD) Log.v(TAG, "Loading drawable for cookie " - + value.assetCookie + ": " + file); - - if (file.endsWith(".xml")) { - Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file); - try { - XmlResourceParser rp = loadXmlResourceParser( - file, id, value.assetCookie, "drawable"); - dr = Drawable.createFromXml(this, rp); - rp.close(); - } catch (Exception e) { - Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); - NotFoundException rnf = new NotFoundException( - "File " + file + " from drawable resource ID #0x" - + Integer.toHexString(id)); - rnf.initCause(e); - throw rnf; - } - Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); + // Abort if the drawable is themed, but the theme cannot be cached. + if (dr.canApplyTheme() && theme != null && !theme.canCacheDrawables()) { + return; + } - } else { - Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file); - try { - InputStream is = mAssets.openNonAsset( - value.assetCookie, file, AssetManager.ACCESS_STREAMING); - // System.out.println("Opened file " + file + ": " + is); - dr = Drawable.createFromResourceStream(this, value, is, - file, null); - is.close(); - // System.out.println("Created stream: " + dr); - } catch (Exception e) { - Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); - NotFoundException rnf = new NotFoundException( - "File " + file + " from drawable resource ID #0x" - + Integer.toHexString(id)); - rnf.initCause(e); - throw rnf; + if (mPreloading) { + // Preloaded drawables never have a theme, but may be themeable. + final int changingConfigs = cs.getChangingConfigurations(); + if (isColorDrawable) { + if (verifyPreloadConfig(changingConfigs, 0, value.resourceId, "drawable")) { + sPreloadedColorDrawables.put(key, cs); + } + } else { + if (verifyPreloadConfig( + changingConfigs, LAYOUT_DIR_CONFIG, value.resourceId, "drawable")) { + if ((changingConfigs & LAYOUT_DIR_CONFIG) == 0) { + // If this resource does not vary based on layout direction, + // we can put it in all of the preload maps. + sPreloadedDrawables[0].put(key, cs); + sPreloadedDrawables[1].put(key, cs); + } else { + // Otherwise, only in the layout dir we loaded it for. + sPreloadedDrawables[mConfiguration.getLayoutDirection()].put(key, cs); } - Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); } } + } else { + synchronized (mAccessLock) { + final LongSparseArray<WeakReference<ConstantState>> themedCache; + if (!dr.canApplyTheme()) { + themedCache = caches.getUnthemed(true); + } else { + themedCache = caches.getOrCreate(theme == null ? 0 : theme.mThemeResId); + } + themedCache.put(key, new WeakReference<ConstantState>(cs)); + } } + } - if (dr != null) { - dr.setChangingConfigurations(value.changingConfigurations); - cs = dr.getConstantState(); - if (cs != null) { - if (mPreloading) { - final int changingConfigs = cs.getChangingConfigurations(); - if (isColorDrawable) { - if (verifyPreloadConfig(changingConfigs, 0, value.resourceId, - "drawable")) { - sPreloadedColorDrawables.put(key, cs); - } - } else { - if (verifyPreloadConfig(changingConfigs, - LAYOUT_DIR_CONFIG, value.resourceId, "drawable")) { - if ((changingConfigs&LAYOUT_DIR_CONFIG) == 0) { - // If this resource does not vary based on layout direction, - // we can put it in all of the preload maps. - sPreloadedDrawables[0].put(key, cs); - sPreloadedDrawables[1].put(key, cs); - } else { - // Otherwise, only in the layout dir we loaded it for. - final LongSparseArray<Drawable.ConstantState> preloads - = sPreloadedDrawables[mConfiguration.getLayoutDirection()]; - preloads.put(key, cs); - } - } - } - } else { - synchronized (mAccessLock) { - //Log.i(TAG, "Saving cached drawable @ #" + - // Integer.toHexString(key.intValue()) - // + " in " + this + ": " + cs); - if (isColorDrawable) { - mColorDrawableCache.put(key, new WeakReference<Drawable.ConstantState>(cs)); - } else { - mDrawableCache.put(key, new WeakReference<Drawable.ConstantState>(cs)); - } - } + /** + * Loads a drawable from XML or resources stream. + */ + private Drawable loadDrawableForCookie(TypedValue value, int id, Theme theme) { + if (value.string == null) { + throw new NotFoundException( + "Resource is not a Drawable (color or path): " + value); + } + + final String file = value.string.toString(); + + if (TRACE_FOR_MISS_PRELOAD) { + // Log only framework resources + if ((id >>> 24) == 0x1) { + final String name = getResourceName(id); + if (name != null) { + Log.d(TAG, "Loading framework drawable #" + Integer.toHexString(id) + + ": " + name + " at " + file); } } } + if (DEBUG_LOAD) { + Log.v(TAG, "Loading drawable for cookie " + value.assetCookie + ": " + file); + } + + final Drawable dr; + + Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file); + try { + if (file.endsWith(".xml")) { + final XmlResourceParser rp = loadXmlResourceParser( + file, id, value.assetCookie, "drawable"); + dr = Drawable.createFromXmlThemed(this, rp, theme); + rp.close(); + } else { + final InputStream is = mAssets.openNonAsset( + value.assetCookie, file, AssetManager.ACCESS_STREAMING); + dr = Drawable.createFromResourceStreamThemed(this, value, is, file, null, theme); + is.close(); + } + } catch (Exception e) { + Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); + final NotFoundException rnf = new NotFoundException( + "File " + file + " from drawable resource ID #0x" + Integer.toHexString(id)); + rnf.initCause(e); + throw rnf; + } + Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); + return dr; } - private Drawable getCachedDrawable( - LongSparseArray<WeakReference<ConstantState>> drawableCache, - long key) { + private Drawable getCachedDrawable(ThemedCaches<ConstantState> caches, long key, Theme theme) { synchronized (mAccessLock) { - WeakReference<Drawable.ConstantState> wr = drawableCache.get(key); - if (wr != null) { // we have the key - Drawable.ConstantState entry = wr.get(); - if (entry != null) { - //Log.i(TAG, "Returning cached drawable @ #" + - // Integer.toHexString(((Integer)key).intValue()) - // + " in " + this + ": " + entry); - return entry.newDrawable(this); + // First, check for a matching unthemed drawable. + final LongSparseArray<WeakReference<ConstantState>> unthemed = caches.getUnthemed(false); + if (unthemed != null) { + final Drawable unthemedDrawable = getCachedDrawableLocked(unthemed, key); + if (unthemedDrawable != null) { + return unthemedDrawable; } - else { // our entry has been purged - drawableCache.delete(key); + } + + final boolean themeCannotCache = theme != null && !theme.canCacheDrawables(); + if (themeCannotCache) { + return null; + } + + // Next, check for a matching themed drawable. + final int themeKey = theme != null ? theme.mThemeResId : 0; + final LongSparseArray<WeakReference<ConstantState>> themedCache = caches.get(themeKey); + if (themedCache != null) { + final Drawable themedDrawable = getCachedDrawableLocked(themedCache, key); + if (themedDrawable != null) { + return themedDrawable; } } + + // No cached drawable, we'll need to create a new one. + return null; + } + } + + private ConstantState getConstantStateLocked( + LongSparseArray<WeakReference<ConstantState>> drawableCache, long key) { + final WeakReference<ConstantState> wr = drawableCache.get(key); + if (wr != null) { // we have the key + final ConstantState entry = wr.get(); + if (entry != null) { + //Log.i(TAG, "Returning cached drawable @ #" + + // Integer.toHexString(((Integer)key).intValue()) + // + " in " + this + ": " + entry); + return entry; + } else { // our entry has been purged + drawableCache.delete(key); + } + } + return null; + } + + private Drawable getCachedDrawableLocked( + LongSparseArray<WeakReference<ConstantState>> drawableCache, long key) { + final ConstantState entry = getConstantStateLocked(drawableCache, key); + if (entry != null) { + return entry.newDrawable(this); } return null; } @@ -2466,40 +2594,6 @@ public class Resources { } } - private TypedArray getCachedStyledAttributes(int len) { - synchronized (mAccessLock) { - TypedArray attrs = mCachedStyledAttributes; - if (attrs != null) { - mCachedStyledAttributes = null; - if (DEBUG_ATTRIBUTES_CACHE) { - mLastRetrievedAttrs = new RuntimeException("here"); - mLastRetrievedAttrs.fillInStackTrace(); - } - - attrs.mLength = len; - int fullLen = len * AssetManager.STYLE_NUM_ENTRIES; - if (attrs.mData.length >= fullLen) { - return attrs; - } - attrs.mData = new int[fullLen]; - attrs.mIndices = new int[1+len]; - return attrs; - } - if (DEBUG_ATTRIBUTES_CACHE) { - RuntimeException here = new RuntimeException("here"); - here.fillInStackTrace(); - if (mLastRetrievedAttrs != null) { - Log.i(TAG, "Allocated new TypedArray of " + len + " in " + this, here); - Log.i(TAG, "Last retrieved attributes here", mLastRetrievedAttrs); - } - mLastRetrievedAttrs = here; - } - return new TypedArray(this, - new int[len*AssetManager.STYLE_NUM_ENTRIES], - new int[1+len], len); - } - } - private Resources() { mAssets = AssetManager.getSystem(); // NOTE: Intentionally leaving this uninitialized (all values set @@ -2510,4 +2604,33 @@ public class Resources { updateConfiguration(null, null); mAssets.ensureStringBlocks(); } + + static class ThemedCaches<T> extends SparseArray<LongSparseArray<WeakReference<T>>> { + private LongSparseArray<WeakReference<T>> mUnthemed = null; + + /** + * Returns the cache of drawables with no themeable attributes. + */ + public LongSparseArray<WeakReference<T>> getUnthemed(boolean autoCreate) { + if (mUnthemed == null && autoCreate) { + mUnthemed = new LongSparseArray<WeakReference<T>>(1); + } + return mUnthemed; + } + + /** + * Returns the cache of drawables styled for the specified theme. + * <p> + * Drawables that have themeable attributes but were loaded without + * specifying a theme are cached at themeResId = 0. + */ + public LongSparseArray<WeakReference<T>> getOrCreate(int themeResId) { + LongSparseArray<WeakReference<T>> result = get(themeResId); + if (result == null) { + result = new LongSparseArray<WeakReference<T>>(1); + put(themeResId, result); + } + return result; + } + } } diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java index 4858d08..baf887e 100644 --- a/core/java/android/content/res/TypedArray.java +++ b/core/java/android/content/res/TypedArray.java @@ -20,6 +20,7 @@ import android.graphics.drawable.Drawable; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; +import android.util.Pools.SynchronizedPool; import android.util.TypedValue; import com.android.internal.util.XmlUtils; @@ -36,16 +37,40 @@ import java.util.Arrays; * the positions of the attributes given to obtainStyledAttributes. */ public class TypedArray { - private final Resources mResources; - private final DisplayMetrics mMetrics; - private final AssetManager mAssets; + private static final SynchronizedPool<TypedArray> mPool = new SynchronizedPool<TypedArray>(5); + + static TypedArray obtain(Resources res, int len) { + final TypedArray attrs = mPool.acquire(); + if (attrs != null) { + attrs.mLength = len; + attrs.mResources = res; + attrs.mMetrics = res.getDisplayMetrics(); + attrs.mAssets = res.getAssets(); + + final int fullLen = len * AssetManager.STYLE_NUM_ENTRIES; + if (attrs.mData.length >= fullLen) { + return attrs; + } + + attrs.mData = new int[fullLen]; + attrs.mIndices = new int[1 + len]; + return attrs; + } + + return new TypedArray(res, + new int[len*AssetManager.STYLE_NUM_ENTRIES], + new int[1+len], len); + } + + private Resources mResources; + private DisplayMetrics mMetrics; + private AssetManager mAssets; /*package*/ XmlBlock.Parser mXml; - /*package*/ int[] mRsrcs; + /*package*/ Resources.Theme mTheme; /*package*/ int[] mData; /*package*/ int[] mIndices; /*package*/ int mLength; /*package*/ TypedValue mValue = new TypedValue(); - /*package*/ Resources.Theme mTheme; /** * Return the number of values in this array. @@ -580,6 +605,25 @@ public class TypedArray { } /** + * Retrieve the theme attribute resource identifier for the attribute at + * <var>index</var>. + * + * @param index Index of attribute to retrieve. + * @param defValue Value to return if the attribute is not defined or not a + * resource. + * @return Theme attribute resource identifier, or defValue if not defined. + * @hide + */ + public int getThemeAttributeId(int index, int defValue) { + index *= AssetManager.STYLE_NUM_ENTRIES; + final int[] data = mData; + if (data[index + AssetManager.STYLE_TYPE] == TypedValue.TYPE_ATTRIBUTE) { + return data[index + AssetManager.STYLE_DATA]; + } + return defValue; + } + + /** * Retrieve the Drawable for the attribute at <var>index</var>. This * gets the resource ID of the selected attribute, and uses * {@link Resources#getDrawable Resources.getDrawable} of the owning @@ -647,6 +691,38 @@ public class TypedArray { } /** + * Determines whether this TypedArray contains an attribute of the specified + * type. + * + * @param type Type of data, e.g. {@link TypedValue#TYPE_ATTRIBUTE} + * @return True if the TypedArray contains an attribute of the specified + * type. + * @hide + */ + public boolean hasType(int type) { + final int[] data = mData; + final int N = getIndexCount(); + for (int i = 0; i < N; i++) { + final int index = getIndex(i) * AssetManager.STYLE_NUM_ENTRIES; + if (data[index + AssetManager.STYLE_TYPE] == type) { + return true; + } + } + return false; + } + + /** + * Returns the type of attribute at the specified index. + * + * @param index Index of attribute whose type to retrieve. + * @return Attribute type. + */ + public int getType(int index) { + index *= AssetManager.STYLE_NUM_ENTRIES; + return mData[index + AssetManager.STYLE_TYPE]; + } + + /** * Determines whether there is an attribute at <var>index</var>. * * @param index Index of attribute to retrieve. @@ -690,11 +766,46 @@ public class TypedArray { * Give back a previously retrieved array, for later re-use. */ public void recycle() { - mResources.recycleCachedStyledAttributes(this); + mResources = null; + mMetrics = null; + mAssets = null; + // These may have been set by the client. mXml = null; - mRsrcs = null; mTheme = null; + + synchronized (mPool) { + mPool.release(this); + } + } + + /** + * Extracts theme attributes from a typed array for later resolution using + * {@link Theme#resolveAttributes(int[], int[], int, int)}. + * + * @param array An array to populate with theme attributes. If the array is + * null or not large enough, a new array will be returned. + * @return an array of length {@link #getIndexCount()} populated with theme + * attributes, or null if there are no theme attributes in the + * typed array + * @hide + */ + public int[] extractThemeAttrs() { + int[] attrs = null; + + final int N = getIndexCount(); + for (int i = 0; i < N; i++) { + final int index = getIndex(i); + final int attrId = getThemeAttributeId(index, 0); + if (attrId != 0) { + if (attrs == null) { + attrs = new int[N]; + } + attrs[i] = attrId; + } + } + + return attrs; } private boolean getValueAt(int index, TypedValue outValue) { diff --git a/core/java/android/widget/ImageView.java b/core/java/android/widget/ImageView.java index 572302a..eedacb5 100644 --- a/core/java/android/widget/ImageView.java +++ b/core/java/android/widget/ImageView.java @@ -664,7 +664,7 @@ public class ImageView extends View { InputStream stream = null; try { stream = mContext.getContentResolver().openInputStream(mUri); - d = Drawable.createFromStream(stream, null); + d = Drawable.createFromStreamThemed(stream, null, mContext.getTheme()); } catch (Exception e) { Log.w("ImageView", "Unable to open content: " + mUri, e); } finally { diff --git a/core/java/android/widget/SuggestionsAdapter.java b/core/java/android/widget/SuggestionsAdapter.java index c8917e0..0203301 100644 --- a/core/java/android/widget/SuggestionsAdapter.java +++ b/core/java/android/widget/SuggestionsAdapter.java @@ -574,7 +574,7 @@ class SuggestionsAdapter extends ResourceCursorAdapter implements OnClickListene throw new FileNotFoundException("Failed to open " + uri); } try { - return Drawable.createFromStream(stream, null); + return Drawable.createFromStreamThemed(stream, null, mContext.getTheme()); } finally { try { stream.close(); diff --git a/core/jni/android_util_AssetManager.cpp b/core/jni/android_util_AssetManager.cpp index 7162a1c..9dde701 100644 --- a/core/jni/android_util_AssetManager.cpp +++ b/core/jni/android_util_AssetManager.cpp @@ -972,6 +972,200 @@ static void android_content_AssetManager_dumpTheme(JNIEnv* env, jobject clazz, theme->dumpToLog(); } +static jboolean android_content_AssetManager_resolveAttrs(JNIEnv* env, jobject clazz, + jlong themeToken, + jint defStyleAttr, + jint defStyleRes, + jintArray inValues, + jintArray attrs, + jintArray outValues, + jintArray outIndices) +{ + if (themeToken == 0) { + jniThrowNullPointerException(env, "theme token"); + return JNI_FALSE; + } + if (attrs == NULL) { + jniThrowNullPointerException(env, "attrs"); + return JNI_FALSE; + } + if (outValues == NULL) { + jniThrowNullPointerException(env, "out values"); + return JNI_FALSE; + } + + DEBUG_STYLES(LOGI("APPLY STYLE: theme=0x%x defStyleAttr=0x%x defStyleRes=0x%x", + themeToken, defStyleAttr, defStyleRes)); + + ResTable::Theme* theme = reinterpret_cast<ResTable::Theme*>(themeToken); + const ResTable& res = theme->getResTable(); + ResTable_config config; + Res_value value; + + const jsize NI = env->GetArrayLength(attrs); + const jsize NV = env->GetArrayLength(outValues); + if (NV < (NI*STYLE_NUM_ENTRIES)) { + jniThrowException(env, "java/lang/IndexOutOfBoundsException", "out values too small"); + return JNI_FALSE; + } + + jint* src = (jint*)env->GetPrimitiveArrayCritical(attrs, 0); + if (src == NULL) { + return JNI_FALSE; + } + + jint* srcValues = (jint*)env->GetPrimitiveArrayCritical(inValues, 0); + const jsize NSV = srcValues == NULL ? 0 : env->GetArrayLength(inValues); + + jint* baseDest = (jint*)env->GetPrimitiveArrayCritical(outValues, 0); + jint* dest = baseDest; + if (dest == NULL) { + env->ReleasePrimitiveArrayCritical(attrs, src, 0); + return JNI_FALSE; + } + + jint* indices = NULL; + int indicesIdx = 0; + if (outIndices != NULL) { + if (env->GetArrayLength(outIndices) > NI) { + indices = (jint*)env->GetPrimitiveArrayCritical(outIndices, 0); + } + } + + // Load default style from attribute, if specified... + uint32_t defStyleBagTypeSetFlags = 0; + if (defStyleAttr != 0) { + Res_value value; + if (theme->getAttribute(defStyleAttr, &value, &defStyleBagTypeSetFlags) >= 0) { + if (value.dataType == Res_value::TYPE_REFERENCE) { + defStyleRes = value.data; + } + } + } + + // Now lock down the resource object and start pulling stuff from it. + res.lock(); + + // Retrieve the default style bag, if requested. + const ResTable::bag_entry* defStyleEnt = NULL; + uint32_t defStyleTypeSetFlags = 0; + ssize_t bagOff = defStyleRes != 0 + ? res.getBagLocked(defStyleRes, &defStyleEnt, &defStyleTypeSetFlags) : -1; + defStyleTypeSetFlags |= defStyleBagTypeSetFlags; + const ResTable::bag_entry* endDefStyleEnt = defStyleEnt + + (bagOff >= 0 ? bagOff : 0);; + + // Now iterate through all of the attributes that the client has requested, + // filling in each with whatever data we can find. + ssize_t block = 0; + uint32_t typeSetFlags; + for (jsize ii=0; ii<NI; ii++) { + const uint32_t curIdent = (uint32_t)src[ii]; + + DEBUG_STYLES(LOGI("RETRIEVING ATTR 0x%08x...", curIdent)); + + // Try to find a value for this attribute... we prioritize values + // coming from, first XML attributes, then XML style, then default + // style, and finally the theme. + value.dataType = Res_value::TYPE_NULL; + value.data = 0; + typeSetFlags = 0; + config.density = 0; + + // Retrieve the current input value if available. + if (NSV > 0 && srcValues[ii] != 0) { + block = -1; + value.dataType = Res_value::TYPE_ATTRIBUTE; + value.data = srcValues[ii]; + DEBUG_STYLES(LOGI("-> From values: type=0x%x, data=0x%08x", + value.dataType, value.data)); + } + + // Skip through the default style values until the end or the next possible match. + while (defStyleEnt < endDefStyleEnt && curIdent > defStyleEnt->map.name.ident) { + defStyleEnt++; + } + // Retrieve the current default style attribute if it matches, and step to next. + if (defStyleEnt < endDefStyleEnt && curIdent == defStyleEnt->map.name.ident) { + if (value.dataType == Res_value::TYPE_NULL) { + block = defStyleEnt->stringBlock; + typeSetFlags = defStyleTypeSetFlags; + value = defStyleEnt->map.value; + DEBUG_STYLES(LOGI("-> From def style: type=0x%x, data=0x%08x", + value.dataType, value.data)); + } + defStyleEnt++; + } + + uint32_t resid = 0; + if (value.dataType != Res_value::TYPE_NULL) { + // Take care of resolving the found resource to its final value. + ssize_t newBlock = theme->resolveAttributeReference(&value, block, + &resid, &typeSetFlags, &config); + if (newBlock >= 0) block = newBlock; + DEBUG_STYLES(LOGI("-> Resolved attr: type=0x%x, data=0x%08x", + value.dataType, value.data)); + } else { + // If we still don't have a value for this attribute, try to find + // it in the theme! + ssize_t newBlock = theme->getAttribute(curIdent, &value, &typeSetFlags); + if (newBlock >= 0) { + DEBUG_STYLES(LOGI("-> From theme: type=0x%x, data=0x%08x", + value.dataType, value.data)); + newBlock = res.resolveReference(&value, block, &resid, + &typeSetFlags, &config); +#if THROW_ON_BAD_ID + if (newBlock == BAD_INDEX) { + jniThrowException(env, "java/lang/IllegalStateException", "Bad resource!"); + return JNI_FALSE; + } +#endif + if (newBlock >= 0) block = newBlock; + DEBUG_STYLES(LOGI("-> Resolved theme: type=0x%x, data=0x%08x", + value.dataType, value.data)); + } + } + + // Deal with the special @null value -- it turns back to TYPE_NULL. + if (value.dataType == Res_value::TYPE_REFERENCE && value.data == 0) { + DEBUG_STYLES(LOGI("-> Setting to @null!")); + value.dataType = Res_value::TYPE_NULL; + block = -1; + } + + DEBUG_STYLES(LOGI("Attribute 0x%08x: type=0x%x, data=0x%08x", + curIdent, value.dataType, value.data)); + + // Write the final value back to Java. + dest[STYLE_TYPE] = value.dataType; + dest[STYLE_DATA] = value.data; + dest[STYLE_ASSET_COOKIE] = + block != -1 ? reinterpret_cast<jint>(res.getTableCookie(block)) : (jint)-1; + dest[STYLE_RESOURCE_ID] = resid; + dest[STYLE_CHANGING_CONFIGURATIONS] = typeSetFlags; + dest[STYLE_DENSITY] = config.density; + + if (indices != NULL && value.dataType != Res_value::TYPE_NULL) { + indicesIdx++; + indices[indicesIdx] = ii; + } + + dest += STYLE_NUM_ENTRIES; + } + + res.unlock(); + + if (indices != NULL) { + indices[0] = indicesIdx; + env->ReleasePrimitiveArrayCritical(outIndices, indices, 0); + } + env->ReleasePrimitiveArrayCritical(outValues, baseDest, 0); + env->ReleasePrimitiveArrayCritical(inValues, srcValues, 0); + env->ReleasePrimitiveArrayCritical(attrs, src, 0); + + return JNI_TRUE; +} + static jboolean android_content_AssetManager_applyStyle(JNIEnv* env, jobject clazz, jlong themeToken, jint defStyleAttr, |