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 | |
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
27 files changed, 2285 insertions, 764 deletions
diff --git a/api/current.txt b/api/current.txt index ec1900e..9fd6dad 100644 --- a/api/current.txt +++ b/api/current.txt @@ -8125,6 +8125,7 @@ package android.content.res { method public void applyStyle(int, boolean); method public void dump(int, java.lang.String, java.lang.String); method public android.graphics.drawable.Drawable getDrawable(int) throws android.content.res.Resources.NotFoundException; + method public android.content.res.Resources getResources(); method public android.content.res.TypedArray obtainStyledAttributes(int[]); method public android.content.res.TypedArray obtainStyledAttributes(int, int[]) throws android.content.res.Resources.NotFoundException; method public android.content.res.TypedArray obtainStyledAttributes(android.util.AttributeSet, int[], int, int); @@ -8155,6 +8156,7 @@ package android.content.res { method public java.lang.String getString(int); method public java.lang.CharSequence getText(int); method public java.lang.CharSequence[] getTextArray(int); + method public int getType(int); method public boolean getValue(int, android.util.TypedValue); method public boolean hasValue(int); method public int length(); @@ -10492,15 +10494,22 @@ package android.graphics.drawable { public abstract class Drawable { ctor public Drawable(); + method public void applyTheme(android.content.res.Resources.Theme); + method public boolean canApplyTheme(); method public void clearColorFilter(); method public final void copyBounds(android.graphics.Rect); method public final android.graphics.Rect copyBounds(); method public static android.graphics.drawable.Drawable createFromPath(java.lang.String); method public static android.graphics.drawable.Drawable createFromResourceStream(android.content.res.Resources, android.util.TypedValue, java.io.InputStream, java.lang.String); method public static android.graphics.drawable.Drawable createFromResourceStream(android.content.res.Resources, android.util.TypedValue, java.io.InputStream, java.lang.String, android.graphics.BitmapFactory.Options); + method public static android.graphics.drawable.Drawable createFromResourceStreamThemed(android.content.res.Resources, android.util.TypedValue, java.io.InputStream, java.lang.String, android.content.res.Resources.Theme); + method public static android.graphics.drawable.Drawable createFromResourceStreamThemed(android.content.res.Resources, android.util.TypedValue, java.io.InputStream, java.lang.String, android.graphics.BitmapFactory.Options, android.content.res.Resources.Theme); method public static android.graphics.drawable.Drawable createFromStream(java.io.InputStream, java.lang.String); + method public static android.graphics.drawable.Drawable createFromStreamThemed(java.io.InputStream, java.lang.String, android.content.res.Resources.Theme); method public static android.graphics.drawable.Drawable createFromXml(android.content.res.Resources, org.xmlpull.v1.XmlPullParser) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; method public static android.graphics.drawable.Drawable createFromXmlInner(android.content.res.Resources, org.xmlpull.v1.XmlPullParser, android.util.AttributeSet) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; + method public static android.graphics.drawable.Drawable createFromXmlInnerThemed(android.content.res.Resources, org.xmlpull.v1.XmlPullParser, android.util.AttributeSet, android.content.res.Resources.Theme) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; + method public static android.graphics.drawable.Drawable createFromXmlThemed(android.content.res.Resources, org.xmlpull.v1.XmlPullParser, android.content.res.Resources.Theme) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; method public abstract void draw(android.graphics.Canvas); method public int getAlpha(); method public final android.graphics.Rect getBounds(); @@ -10520,6 +10529,7 @@ package android.graphics.drawable { method public int[] getState(); method public android.graphics.Region getTransparentRegion(); method public void inflate(android.content.res.Resources, org.xmlpull.v1.XmlPullParser, android.util.AttributeSet) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; + method public void inflate(android.content.res.Resources, org.xmlpull.v1.XmlPullParser, android.util.AttributeSet, android.content.res.Resources.Theme) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException; method public void invalidateSelf(); method public boolean isAutoMirrored(); method public boolean isStateful(); @@ -10555,9 +10565,11 @@ package android.graphics.drawable { public static abstract class Drawable.ConstantState { ctor public Drawable.ConstantState(); + method public boolean canApplyTheme(); method public abstract int getChangingConfigurations(); method public abstract android.graphics.drawable.Drawable newDrawable(); method public android.graphics.drawable.Drawable newDrawable(android.content.res.Resources); + method public android.graphics.drawable.Drawable newDrawable(android.content.res.Resources, android.content.res.Resources.Theme); } public class DrawableContainer extends android.graphics.drawable.Drawable implements android.graphics.drawable.Drawable.Callback { 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, diff --git a/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java b/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java index 9accbbc..a37ceef 100644 --- a/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimatedRotateDrawable.java @@ -21,10 +21,12 @@ import android.graphics.Rect; import android.graphics.ColorFilter; import android.content.res.Resources; import android.content.res.TypedArray; +import android.content.res.Resources.Theme; import android.util.AttributeSet; import android.util.TypedValue; import android.util.Log; import android.os.SystemClock; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -223,7 +225,7 @@ public class AnimatedRotateDrawable extends Drawable implements Drawable.Callbac } @Override - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) + public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { final TypedArray a = r.obtainAttributes(attrs, R.styleable.AnimatedRotateDrawable); @@ -258,7 +260,7 @@ public class AnimatedRotateDrawable extends Drawable implements Drawable.Callbac continue; } - if ((drawable = Drawable.createFromXmlInner(r, parser, attrs)) == null) { + if ((drawable = Drawable.createFromXmlInnerThemed(r, parser, attrs, theme)) == null) { Log.w("drawable", "Bad element under <animated-rotate>: " + parser .getName()); } diff --git a/graphics/java/android/graphics/drawable/AnimationDrawable.java b/graphics/java/android/graphics/drawable/AnimationDrawable.java index 02b2588..3f94e26 100644 --- a/graphics/java/android/graphics/drawable/AnimationDrawable.java +++ b/graphics/java/android/graphics/drawable/AnimationDrawable.java @@ -23,6 +23,7 @@ import org.xmlpull.v1.XmlPullParserException; import android.content.res.Resources; import android.content.res.TypedArray; +import android.content.res.Resources.Theme; import android.os.SystemClock; import android.util.AttributeSet; @@ -236,7 +237,7 @@ public class AnimationDrawable extends DrawableContainer implements Runnable, An } @Override - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) + public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { TypedArray a = r.obtainAttributes(attrs, @@ -292,7 +293,7 @@ public class AnimationDrawable extends DrawableContainer implements Runnable, An ": <item> tag requires a 'drawable' attribute or child tag" + " defining a drawable"); } - dr = Drawable.createFromXmlInner(r, parser, attrs); + dr = Drawable.createFromXmlInnerThemed(r, parser, attrs, theme); } mAnimationState.addFrame(dr, duration); diff --git a/graphics/java/android/graphics/drawable/BitmapDrawable.java b/graphics/java/android/graphics/drawable/BitmapDrawable.java index fe08f4b..19131f2 100644 --- a/graphics/java/android/graphics/drawable/BitmapDrawable.java +++ b/graphics/java/android/graphics/drawable/BitmapDrawable.java @@ -18,6 +18,7 @@ package android.graphics.drawable; import android.content.res.ColorStateList; import android.content.res.Resources; +import android.content.res.Resources.Theme; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -37,6 +38,8 @@ import android.util.DisplayMetrics; import android.util.LayoutDirection; import android.view.Gravity; +import com.android.internal.R; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -114,7 +117,7 @@ public class BitmapDrawable extends Drawable { */ @Deprecated public BitmapDrawable(Bitmap bitmap) { - this(new BitmapState(bitmap), null); + this(new BitmapState(bitmap), null, null); } /** @@ -122,7 +125,7 @@ public class BitmapDrawable extends Drawable { * the display metrics of the resources. */ public BitmapDrawable(Resources res, Bitmap bitmap) { - this(new BitmapState(bitmap), res); + this(new BitmapState(bitmap), res, null); mBitmapState.mTargetDensity = mTargetDensity; } @@ -133,7 +136,7 @@ public class BitmapDrawable extends Drawable { */ @Deprecated public BitmapDrawable(String filepath) { - this(new BitmapState(BitmapFactory.decodeFile(filepath)), null); + this(new BitmapState(BitmapFactory.decodeFile(filepath)), null, null); if (mBitmap == null) { android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath); } @@ -144,7 +147,7 @@ public class BitmapDrawable extends Drawable { */ @SuppressWarnings({"UnusedParameters"}) public BitmapDrawable(Resources res, String filepath) { - this(new BitmapState(BitmapFactory.decodeFile(filepath)), null); + this(new BitmapState(BitmapFactory.decodeFile(filepath)), null, null); mBitmapState.mTargetDensity = mTargetDensity; if (mBitmap == null) { android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath); @@ -158,7 +161,7 @@ public class BitmapDrawable extends Drawable { */ @Deprecated public BitmapDrawable(java.io.InputStream is) { - this(new BitmapState(BitmapFactory.decodeStream(is)), null); + this(new BitmapState(BitmapFactory.decodeStream(is)), null, null); if (mBitmap == null) { android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is); } @@ -169,7 +172,7 @@ public class BitmapDrawable extends Drawable { */ @SuppressWarnings({"UnusedParameters"}) public BitmapDrawable(Resources res, java.io.InputStream is) { - this(new BitmapState(BitmapFactory.decodeStream(is)), null); + this(new BitmapState(BitmapFactory.decodeStream(is)), null, null); mBitmapState.mTargetDensity = mTargetDensity; if (mBitmap == null) { android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is); @@ -633,6 +636,7 @@ public class BitmapDrawable extends Drawable { /** * @hide Candidate for future API inclusion */ + @Override public void setXfermode(Xfermode xfermode) { mBitmapState.mPaint.setXfermode(xfermode); invalidateSelf(); @@ -676,64 +680,228 @@ public class BitmapDrawable extends Drawable { } @Override - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) + public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { - super.inflate(r, parser, attrs); + super.inflate(r, parser, attrs, theme); + + final TypedArray a = obtainAttributes( + r, theme, attrs, R.styleable.BitmapDrawable); + inflateStateFromTypedArray(a); + a.recycle(); + } + /** + * Initializes the constant state from the values in the typed array. + */ + private void inflateStateFromTypedArray(TypedArray a) throws XmlPullParserException { + final Resources r = a.getResources(); final BitmapState state = mBitmapState; - final TypedArray a = r.obtainAttributes(attrs, com.android.internal.R.styleable.BitmapDrawable); - final int id = a.getResourceId(com.android.internal.R.styleable.BitmapDrawable_src, 0); - if (id == 0) { - throw new XmlPullParserException(parser.getPositionDescription() + - ": <bitmap> requires a valid src attribute"); + // Extract the theme attributes, if any. + final int[] themeAttrs = a.extractThemeAttrs(); + state.mThemeAttrs = themeAttrs; + + if (themeAttrs == null || themeAttrs[R.styleable.BitmapDrawable_src] == 0) { + final int id = a.getResourceId(R.styleable.BitmapDrawable_src, 0); + if (id == 0) { + throw new XmlPullParserException(a.getPositionDescription() + + ": <bitmap> requires a valid src attribute"); + } + + final Bitmap bitmap = BitmapFactory.decodeResource(r, id); + if (bitmap == null) { + throw new XmlPullParserException(a.getPositionDescription() + + ": <bitmap> requires a valid src attribute"); + } + state.mBitmap = bitmap; + setBitmap(bitmap); } - final Bitmap bitmap = BitmapFactory.decodeResource(r, id); - if (bitmap == null) { - throw new XmlPullParserException(parser.getPositionDescription() + - ": <bitmap> requires a valid src attribute"); + + setTargetDensity(r.getDisplayMetrics()); + + if (themeAttrs == null || themeAttrs[R.styleable.BitmapDrawable_mipMap] == 0) { + final boolean defMipMap = state.mBitmap != null ? state.mBitmap.hasMipMap() : false; + final boolean mipMap = a.getBoolean( + R.styleable.BitmapDrawable_mipMap, defMipMap); + setMipMap(mipMap); } - state.mBitmap = bitmap; - setBitmap(bitmap); + + if (themeAttrs == null || themeAttrs[R.styleable.BitmapDrawable_autoMirrored] == 0) { + final boolean autoMirrored = a.getBoolean( + R.styleable.BitmapDrawable_autoMirrored, false); + setAutoMirrored(autoMirrored); + } + + if (themeAttrs == null || themeAttrs[R.styleable.BitmapDrawable_tintMode] == 0) { + final int tintModeValue = a.getInt( + R.styleable.BitmapDrawable_tintMode, -1); + state.mTintMode = Drawable.parseTintMode(tintModeValue, Mode.SRC_IN); + } + + if (themeAttrs == null || themeAttrs[R.styleable.BitmapDrawable_tint] == 0) { + state.mTint = a.getColorStateList(R.styleable.BitmapDrawable_tint); + if (state.mTint != null) { + final int color = state.mTint.getColorForState(getState(), 0); + mTintFilter = new PorterDuffColorFilter(color, mBitmapState.mTintMode); + } + } + + final Paint paint = mBitmapState.mPaint; + + if (themeAttrs == null || themeAttrs[R.styleable.BitmapDrawable_antialias] == 0) { + final boolean antiAlias = a.getBoolean( + R.styleable.BitmapDrawable_antialias, paint.isAntiAlias()); + paint.setAntiAlias(antiAlias); + } + + if (themeAttrs == null || themeAttrs[R.styleable.BitmapDrawable_filter] == 0) { + final boolean filter = a.getBoolean( + R.styleable.BitmapDrawable_filter, paint.isFilterBitmap()); + paint.setFilterBitmap(filter); + } + + if (themeAttrs == null || themeAttrs[R.styleable.BitmapDrawable_dither] == 0) { + final boolean dither = a.getBoolean( + R.styleable.BitmapDrawable_dither, paint.isDither()); + paint.setDither(dither); + } + + if (themeAttrs == null || themeAttrs[R.styleable.BitmapDrawable_gravity] == 0) { + final int gravity = a.getInt( + R.styleable.BitmapDrawable_gravity, Gravity.FILL); + setGravity(gravity); + } + + if (themeAttrs == null || themeAttrs[R.styleable.BitmapDrawable_tileMode] == 0) { + final int tileMode = a.getInt( + R.styleable.BitmapDrawable_tileMode, -1); + setTileModeInternal(tileMode); + } + } + + @Override + public void applyTheme(Theme t) { + super.applyTheme(t); + + final BitmapState state = mBitmapState; + if (state == null) { + throw new RuntimeException("Can't apply theme to <bitmap> with no constant state"); + } + + final int[] themeAttrs = state.mThemeAttrs; + if (themeAttrs != null) { + final TypedArray a = t.resolveAttributes(themeAttrs, R.styleable.BitmapDrawable, 0, 0); + updateStateFromTypedArray(a); + a.recycle(); + } + } + + /** + * Updates the constant state from the values in the typed array. + */ + private void updateStateFromTypedArray(TypedArray a) { + final Resources r = a.getResources(); + final BitmapState state = mBitmapState; + final Paint paint = mBitmapState.mPaint; + + if (a.hasValue(R.styleable.BitmapDrawable_antialias)) { + final boolean antiAlias = a.getBoolean( + R.styleable.BitmapDrawable_antialias, paint.isAntiAlias()); + paint.setAntiAlias(antiAlias); + } + + if (a.hasValue(R.styleable.BitmapDrawable_filter)) { + final boolean filter = a.getBoolean( + R.styleable.BitmapDrawable_filter, paint.isFilterBitmap()); + paint.setFilterBitmap(filter); + } + + if (a.hasValue(R.styleable.BitmapDrawable_dither)) { + final boolean dither = a.getBoolean( + R.styleable.BitmapDrawable_dither, paint.isDither()); + paint.setDither(dither); + } + + if (a.hasValue(R.styleable.BitmapDrawable_gravity)) { + final int gravity = a.getInt( + R.styleable.BitmapDrawable_gravity, Gravity.FILL); + setGravity(gravity); + } + + if (a.hasValue(R.styleable.BitmapDrawable_tileMode)) { + final int tileMode = a.getInt( + R.styleable.BitmapDrawable_tileMode, -1); + setTileModeInternal(tileMode); + } + + if (a.hasValue(R.styleable.BitmapDrawable_src)) { + final int id = a.getResourceId(R.styleable.BitmapDrawable_src, 0); + if (id == 0) { + throw new RuntimeException(a.getPositionDescription() + + ": <bitmap> requires a valid src attribute"); + } + + final Bitmap bitmap = BitmapFactory.decodeResource(r, id); + if (bitmap == null) { + throw new RuntimeException(a.getPositionDescription() + + ": <bitmap> requires a valid src attribute"); + } + + setBitmap(bitmap); + } + setTargetDensity(r.getDisplayMetrics()); - setMipMap(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_mipMap, - bitmap.hasMipMap())); - setAutoMirrored(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_autoMirrored, - false)); - - final int tintModeValue = a.getInt( - com.android.internal.R.styleable.BitmapDrawable_tintMode, -1); - state.mTintMode = Drawable.parseTintMode(tintModeValue, Mode.SRC_IN); - state.mTint = a.getColorStateList(com.android.internal.R.styleable.BitmapDrawable_tint); - if (state.mTint != null) { - final int color = state.mTint.getColorForState(getState(), 0); - mTintFilter = new PorterDuffColorFilter(color, mBitmapState.mTintMode); + + if (a.hasValue(R.styleable.BitmapDrawable_mipMap)) { + final boolean mipMap = a.getBoolean( + R.styleable.BitmapDrawable_mipMap, + state.mBitmap.hasMipMap()); + setMipMap(mipMap); } - final Paint paint = state.mPaint; - paint.setAntiAlias(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_antialias, - paint.isAntiAlias())); - paint.setFilterBitmap(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_filter, - paint.isFilterBitmap())); - paint.setDither(a.getBoolean(com.android.internal.R.styleable.BitmapDrawable_dither, - paint.isDither())); - setGravity(a.getInt(com.android.internal.R.styleable.BitmapDrawable_gravity, Gravity.FILL)); - int tileMode = a.getInt(com.android.internal.R.styleable.BitmapDrawable_tileMode, -1); - if (tileMode != -1) { - switch (tileMode) { - case 0: - setTileModeXY(Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); - break; - case 1: - setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.REPEAT); - break; - case 2: - setTileModeXY(Shader.TileMode.MIRROR, Shader.TileMode.MIRROR); - break; + if (a.hasValue(R.styleable.BitmapDrawable_autoMirrored)) { + final boolean autoMirrored = a.getBoolean( + R.styleable.BitmapDrawable_autoMirrored, false); + setAutoMirrored(autoMirrored); + } + + if (a.hasValue(R.styleable.BitmapDrawable_tintMode)) { + final int modeValue = a.getInt( + R.styleable.BitmapDrawable_tintMode, -1); + state.mTintMode = Drawable.parseTintMode(modeValue, Mode.SRC_IN); + } + + if (a.hasValue(R.styleable.BitmapDrawable_tint)) { + final ColorStateList tint = a.getColorStateList( + R.styleable.BitmapDrawable_tint); + if (tint != null) { + state.mTint = tint; + final int color = tint.getColorForState(getState(), 0); + mTintFilter = new PorterDuffColorFilter(color, state.mTintMode); } } + } - a.recycle(); + private void setTileModeInternal(final int tileMode) { + switch (tileMode) { + case -1: + // Do nothing. + break; + case 0: + setTileModeXY(Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); + break; + case 1: + setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.REPEAT); + break; + case 2: + setTileModeXY(Shader.TileMode.MIRROR, Shader.TileMode.MIRROR); + break; + } + } + + @Override + public boolean canApplyTheme() { + return mBitmapState != null && mBitmapState.mThemeAttrs != null; } @Override @@ -766,6 +934,7 @@ public class BitmapDrawable extends Drawable { Bitmap mBitmap; ColorStateList mTint; Mode mTintMode = Mode.SRC_IN; + int[] mThemeAttrs; int mChangingConfigurations; int mGravity = Gravity.FILL; Paint mPaint = new Paint(DEFAULT_PAINT_FLAGS); @@ -783,6 +952,7 @@ public class BitmapDrawable extends Drawable { mBitmap = bitmapState.mBitmap; mTint = bitmapState.mTint; mTintMode = bitmapState.mTintMode; + mThemeAttrs = bitmapState.mThemeAttrs; mChangingConfigurations = bitmapState.mChangingConfigurations; mGravity = bitmapState.mGravity; mTileModeX = bitmapState.mTileModeX; @@ -794,18 +964,28 @@ public class BitmapDrawable extends Drawable { } @Override + public boolean canApplyTheme() { + return mThemeAttrs != null; + } + + @Override public Bitmap getBitmap() { return mBitmap; } @Override public Drawable newDrawable() { - return new BitmapDrawable(this, null); + return new BitmapDrawable(this, null, null); } @Override public Drawable newDrawable(Resources res) { - return new BitmapDrawable(this, res); + return new BitmapDrawable(this, res, null); + } + + @Override + public Drawable newDrawable(Resources res, Theme theme) { + return new BitmapDrawable(this, res, theme); } @Override @@ -814,9 +994,21 @@ public class BitmapDrawable extends Drawable { } } - private BitmapDrawable(BitmapState state, Resources res) { - mBitmapState = state; + private BitmapDrawable(BitmapState state, Resources res, Theme theme) { + if (theme != null && state.canApplyTheme()) { + mBitmapState = new BitmapState(state); + applyTheme(theme); + } else { + mBitmapState = state; + } + + initializeWithState(state, res); + } + /** + * Initializes local dynamic properties from state. + */ + private void initializeWithState(BitmapState state, Resources res) { if (res != null) { mTargetDensity = res.getDisplayMetrics().densityDpi; } else { @@ -828,6 +1020,6 @@ public class BitmapDrawable extends Drawable { mTintFilter = new PorterDuffColorFilter(color, state.mTintMode); } - setBitmap(state != null ? state.mBitmap : null); + setBitmap(state.mBitmap); } } diff --git a/graphics/java/android/graphics/drawable/ClipDrawable.java b/graphics/java/android/graphics/drawable/ClipDrawable.java index 2a9a14b..3dbd235 100644 --- a/graphics/java/android/graphics/drawable/ClipDrawable.java +++ b/graphics/java/android/graphics/drawable/ClipDrawable.java @@ -21,6 +21,7 @@ import org.xmlpull.v1.XmlPullParserException; import android.content.res.Resources; import android.content.res.TypedArray; +import android.content.res.Resources.Theme; import android.graphics.*; import android.view.Gravity; import android.util.AttributeSet; @@ -72,9 +73,9 @@ public class ClipDrawable extends Drawable implements Drawable.Callback { } @Override - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) + public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { - super.inflate(r, parser, attrs); + super.inflate(r, parser, attrs, theme); int type; @@ -94,7 +95,7 @@ public class ClipDrawable extends Drawable implements Drawable.Callback { if (type != XmlPullParser.START_TAG) { continue; } - dr = Drawable.createFromXmlInner(r, parser, attrs); + dr = Drawable.createFromXmlInnerThemed(r, parser, attrs, theme); } if (dr == null) { diff --git a/graphics/java/android/graphics/drawable/ColorDrawable.java b/graphics/java/android/graphics/drawable/ColorDrawable.java index 61dd675..8243b7c 100644 --- a/graphics/java/android/graphics/drawable/ColorDrawable.java +++ b/graphics/java/android/graphics/drawable/ColorDrawable.java @@ -18,9 +18,13 @@ package android.graphics.drawable; import android.graphics.*; import android.content.res.Resources; +import android.content.res.Resources.Theme; import android.content.res.TypedArray; import android.util.AttributeSet; import android.view.ViewDebug; + +import com.android.internal.R; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -36,7 +40,7 @@ import java.io.IOException; */ public class ColorDrawable extends Drawable { @ViewDebug.ExportedProperty(deepExport = true, prefix = "state_") - private ColorState mState; + private ColorState mColorState; private final Paint mPaint = new Paint(); private boolean mMutated; @@ -44,7 +48,7 @@ public class ColorDrawable extends Drawable { * Creates a new black ColorDrawable. */ public ColorDrawable() { - this(null); + mColorState = new ColorState(); } /** @@ -53,17 +57,14 @@ public class ColorDrawable extends Drawable { * @param color The color to draw. */ public ColorDrawable(int color) { - this(null); - setColor(color); - } + mColorState = new ColorState(); - private ColorDrawable(ColorState state) { - mState = new ColorState(state); + setColor(color); } @Override public int getChangingConfigurations() { - return super.getChangingConfigurations() | mState.mChangingConfigurations; + return super.getChangingConfigurations() | mColorState.mChangingConfigurations; } /** @@ -75,7 +76,7 @@ public class ColorDrawable extends Drawable { @Override public Drawable mutate() { if (!mMutated && super.mutate() == this) { - mState = new ColorState(mState); + mColorState = new ColorState(mColorState); mMutated = true; } return this; @@ -83,8 +84,8 @@ public class ColorDrawable extends Drawable { @Override public void draw(Canvas canvas) { - if ((mState.mUseColor >>> 24) != 0) { - mPaint.setColor(mState.mUseColor); + if ((mColorState.mUseColor >>> 24) != 0) { + mPaint.setColor(mColorState.mUseColor); canvas.drawRect(getBounds(), mPaint); } } @@ -95,19 +96,20 @@ public class ColorDrawable extends Drawable { * @return int The color to draw. */ public int getColor() { - return mState.mUseColor; + return mColorState.mUseColor; } /** - * Sets the drawable's color value. This action will clobber the results of prior calls to - * {@link #setAlpha(int)} on this object, which side-affected the underlying color. + * Sets the drawable's color value. This action will clobber the results of + * prior calls to {@link #setAlpha(int)} on this object, which side-affected + * the underlying color. * * @param color The color to draw. */ public void setColor(int color) { - if (mState.mBaseColor != color || mState.mUseColor != color) { + if (mColorState.mBaseColor != color || mColorState.mUseColor != color) { + mColorState.mBaseColor = mColorState.mUseColor = color; invalidateSelf(); - mState.mBaseColor = mState.mUseColor = color; } } @@ -118,7 +120,7 @@ public class ColorDrawable extends Drawable { */ @Override public int getAlpha() { - return mState.mUseColor >>> 24; + return mColorState.mUseColor >>> 24; } /** @@ -126,13 +128,14 @@ public class ColorDrawable extends Drawable { * * @param alpha The alpha value to set, between 0 and 255. */ + @Override public void setAlpha(int alpha) { alpha += alpha >> 7; // make it 0..256 - int baseAlpha = mState.mBaseColor >>> 24; - int useAlpha = baseAlpha * alpha >> 8; - int oldUseColor = mState.mUseColor; - mState.mUseColor = (mState.mBaseColor << 8 >>> 8) | (useAlpha << 24); - if (oldUseColor != mState.mUseColor) { + final int baseAlpha = mColorState.mBaseColor >>> 24; + final int useAlpha = baseAlpha * alpha >> 8; + final int useColor = (mColorState.mBaseColor << 8 >>> 8) | (useAlpha << 24); + if (mColorState.mUseColor != useColor) { + mColorState.mUseColor = useColor; invalidateSelf(); } } @@ -142,11 +145,13 @@ public class ColorDrawable extends Drawable { * * @param colorFilter Ignore. */ + @Override public void setColorFilter(ColorFilter colorFilter) { } + @Override public int getOpacity() { - switch (mState.mUseColor >>> 24) { + switch (mColorState.mUseColor >>> 24) { case 255: return PixelFormat.OPAQUE; case 0: @@ -156,23 +161,67 @@ public class ColorDrawable extends Drawable { } @Override - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) + public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { - super.inflate(r, parser, attrs); + super.inflate(r, parser, attrs, theme); + + final TypedArray a = obtainAttributes( + r, theme, attrs, R.styleable.ColorDrawable); + inflateStateFromTypedArray(a); + a.recycle(); + } - TypedArray a = r.obtainAttributes(attrs, com.android.internal.R.styleable.ColorDrawable); + /** + * Initializes the constant state from the values in the typed array. + */ + private void inflateStateFromTypedArray(TypedArray a) { + final ColorState state = mColorState; - int color = mState.mBaseColor; - color = a.getColor(com.android.internal.R.styleable.ColorDrawable_color, color); - mState.mBaseColor = mState.mUseColor = color; + // Extract the theme attributes, if any. + final int[] themeAttrs = a.extractThemeAttrs(); + state.mThemeAttrs = themeAttrs; - a.recycle(); + if (themeAttrs == null || themeAttrs[R.styleable.ColorDrawable_color] == 0) { + final int color = a.getColor(R.styleable.ColorDrawable_color, 0); + state.mBaseColor = color; + state.mUseColor = color; + } + } + + @Override + public void applyTheme(Theme t) { + super.applyTheme(t); + + final ColorState state = mColorState; + if (state == null) { + throw new RuntimeException("Can't apply theme to <color> with no constant state"); + } + + final int[] themeAttrs = state.mThemeAttrs; + if (themeAttrs != null) { + final TypedArray a = t.resolveAttributes(themeAttrs, R.styleable.ColorDrawable, 0, 0); + updateStateFromTypedArray(a); + a.recycle(); + } + } + + /** + * Updates the constant state from the values in the typed array. + */ + private void updateStateFromTypedArray(TypedArray a) { + final ColorState state = mColorState; + + if (a.hasValue(R.styleable.ColorDrawable_color)) { + final int color = a.getColor(R.styleable.ColorDrawable_color, 0); + state.mBaseColor = color; + state.mUseColor = color; + } } @Override public ConstantState getConstantState() { - mState.mChangingConfigurations = getChangingConfigurations(); - return mState; + mColorState.mChangingConfigurations = getChangingConfigurations(); + return mColorState; } final static class ColorState extends ConstantState { @@ -180,23 +229,37 @@ public class ColorDrawable extends Drawable { @ViewDebug.ExportedProperty int mUseColor; // basecolor modulated by setAlpha() int mChangingConfigurations; + int[] mThemeAttrs; + + ColorState() { + // Empty constructor. + } ColorState(ColorState state) { - if (state != null) { - mBaseColor = state.mBaseColor; - mUseColor = state.mUseColor; - mChangingConfigurations = state.mChangingConfigurations; - } + mBaseColor = state.mBaseColor; + mUseColor = state.mUseColor; + mChangingConfigurations = state.mChangingConfigurations; + mThemeAttrs = state.mThemeAttrs; + } + + @Override + public boolean canApplyTheme() { + return mThemeAttrs != null; } @Override public Drawable newDrawable() { - return new ColorDrawable(this); + return new ColorDrawable(this, null, null); } @Override public Drawable newDrawable(Resources res) { - return new ColorDrawable(this); + return new ColorDrawable(this, res, null); + } + + @Override + public Drawable newDrawable(Resources res, Theme theme) { + return new ColorDrawable(this, res, theme); } @Override @@ -204,4 +267,15 @@ public class ColorDrawable extends Drawable { return mChangingConfigurations; } } + + private ColorDrawable(ColorState state, Resources res, Theme theme) { + if (theme != null && state.canApplyTheme()) { + mColorState = new ColorState(state); + applyTheme(theme); + } else { + mColorState = state; + } + + // No local properties to initialize. + } } diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java index eb6b536..de2b68f 100644 --- a/graphics/java/android/graphics/drawable/Drawable.java +++ b/graphics/java/android/graphics/drawable/Drawable.java @@ -24,6 +24,7 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import android.content.res.Resources; +import android.content.res.Resources.Theme; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -697,6 +698,16 @@ public abstract class Drawable { } /** + * Applies the specified theme to this Drawable and its children. + */ + public void applyTheme(@SuppressWarnings("unused") Theme t) { + } + + public boolean canApplyTheme() { + return false; + } + + /** * Return the opacity/transparency of this Drawable. The returned value is * one of the abstract format constants in * {@link android.graphics.PixelFormat}: @@ -900,9 +911,13 @@ public abstract class Drawable { * Create a drawable from an inputstream */ public static Drawable createFromStream(InputStream is, String srcName) { + return createFromStreamThemed(is, srcName, null); + } + + public static Drawable createFromStreamThemed(InputStream is, String srcName, Theme theme) { Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, srcName != null ? srcName : "Unknown drawable"); try { - return createFromResourceStream(null, null, is, srcName, null); + return createFromResourceStreamThemed(null, null, is, srcName, theme); } finally { Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); } @@ -914,9 +929,14 @@ public abstract class Drawable { */ public static Drawable createFromResourceStream(Resources res, TypedValue value, InputStream is, String srcName) { + return createFromResourceStreamThemed(res, value, is, srcName, null); + } + + public static Drawable createFromResourceStreamThemed(Resources res, TypedValue value, + InputStream is, String srcName, Theme theme) { Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, srcName != null ? srcName : "Unknown drawable"); try { - return createFromResourceStream(res, value, is, srcName, null); + return createFromResourceStreamThemed(res, value, is, srcName, null, theme); } finally { Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); } @@ -928,7 +948,11 @@ public abstract class Drawable { */ public static Drawable createFromResourceStream(Resources res, TypedValue value, InputStream is, String srcName, BitmapFactory.Options opts) { + return createFromResourceStreamThemed(res, value, is, srcName, opts, null); + } + public static Drawable createFromResourceStreamThemed(Resources res, TypedValue value, + InputStream is, String srcName, BitmapFactory.Options opts, Theme theme) { if (is == null) { return null; } @@ -976,6 +1000,16 @@ public abstract class Drawable { */ public static Drawable createFromXml(Resources r, XmlPullParser parser) throws XmlPullParserException, IOException { + return createFromXmlThemed(r, parser, null); + } + + /** + * Create a themed drawable from an XML document. For more information on + * how to create resources in XML, see + * <a href="{@docRoot}guide/topics/resources/drawable-resource.html">Drawable Resources</a>. + */ + public static Drawable createFromXmlThemed(Resources r, XmlPullParser parser, Theme theme) + throws XmlPullParserException, IOException { AttributeSet attrs = Xml.asAttributeSet(parser); int type; @@ -988,7 +1022,7 @@ public abstract class Drawable { throw new XmlPullParserException("No start tag found"); } - Drawable drawable = createFromXmlInner(r, parser, attrs); + Drawable drawable = createFromXmlInnerThemed(r, parser, attrs, theme); if (drawable == null) { throw new RuntimeException("Unknown initial tag: " + parser.getName()); @@ -1003,11 +1037,15 @@ public abstract class Drawable { * Returns null if the tag is not a valid drawable. */ public static Drawable createFromXmlInner(Resources r, XmlPullParser parser, AttributeSet attrs) - throws XmlPullParserException, IOException { - Drawable drawable; + throws XmlPullParserException, IOException { + return createFromXmlInnerThemed(r, parser, attrs, null); + } - final String name = parser.getName(); + public static Drawable createFromXmlInnerThemed(Resources r, XmlPullParser parser, + AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { + final Drawable drawable; + final String name = parser.getName(); if (name.equals("selector")) { drawable = new StateListDrawable(); } else if (name.equals("level-list")) { @@ -1052,7 +1090,7 @@ public abstract class Drawable { ": invalid drawable tag " + name); } - drawable.inflate(r, parser, attrs); + drawable.inflate(r, parser, attrs, theme); return drawable; } @@ -1079,12 +1117,35 @@ public abstract class Drawable { } /** - * Inflate this Drawable from an XML resource. + * Inflate this Drawable from an XML resource. Does not apply a theme. + * + * @see #inflate(Resources, XmlPullParser, AttributeSet, Theme) */ public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) throws XmlPullParserException, IOException { + inflate(r, parser, attrs, null); + } + + /** + * Inflate this Drawable from an XML resource optionally styled by a theme. + * + * @param r Resources used to resolve attribute values + * @param parser XML parser from which to inflate this Drawable + * @param attrs Base set of attribute values + * @param theme Theme to apply, may be null + * @throws XmlPullParserException + * @throws IOException + */ + public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) + throws XmlPullParserException, IOException { + final TypedArray a; + if (theme != null) { + a = theme.obtainStyledAttributes( + attrs, com.android.internal.R.styleable.Drawable, 0, 0); + } else { + a = r.obtainAttributes(attrs, com.android.internal.R.styleable.Drawable); + } - TypedArray a = r.obtainAttributes(attrs, com.android.internal.R.styleable.Drawable); inflateWithAttributes(r, parser, a, com.android.internal.R.styleable.Drawable_visible); a.recycle(); } @@ -1095,10 +1156,8 @@ public abstract class Drawable { * @throws XmlPullParserException * @throws IOException */ - void inflateWithAttributes(Resources r, XmlPullParser parser, - TypedArray attrs, int visibleAttr) + void inflateWithAttributes(Resources r, XmlPullParser parser, TypedArray attrs, int visibleAttr) throws XmlPullParserException, IOException { - mVisible = attrs.getBoolean(visibleAttr, mVisible); } @@ -1125,6 +1184,7 @@ public abstract class Drawable { * instead to provide a resource. */ public abstract Drawable newDrawable(); + /** * Create a new Drawable instance from its constant state. This * must be implemented for drawables that change based on the target @@ -1134,6 +1194,15 @@ public abstract class Drawable { public Drawable newDrawable(Resources res) { return newDrawable(); } + + /** + * Create a new Drawable instance from its constant state. This must be + * implemented for drawables that can have a theme applied. + */ + public Drawable newDrawable(Resources res, Theme theme) { + return newDrawable(); + } + /** * Return a bit mask of configuration changes that will impact * this drawable (and thus require completely reloading it). @@ -1146,6 +1215,13 @@ public abstract class Drawable { public Bitmap getBitmap() { return null; } + + /** + * Return whether this constant state can have a theme applied. + */ + public boolean canApplyTheme() { + return false; + } } /** @@ -1170,6 +1246,18 @@ public abstract class Drawable { } /** + * Obtains styled attributes from the theme, if available, or unstyled + * resources if the theme is null. + */ + static TypedArray obtainAttributes( + Resources res, Theme theme, AttributeSet set, int[] attrs) { + if (theme == null) { + return res.obtainAttributes(set, attrs); + } + return theme.obtainStyledAttributes(set, attrs, 0, 0); + } + + /** * Parses a {@link android.graphics.PorterDuff.Mode} from a tintMode * attribute's enum value. */ diff --git a/graphics/java/android/graphics/drawable/DrawableContainer.java b/graphics/java/android/graphics/drawable/DrawableContainer.java index 6144e69..05df3bc 100644 --- a/graphics/java/android/graphics/drawable/DrawableContainer.java +++ b/graphics/java/android/graphics/drawable/DrawableContainer.java @@ -17,6 +17,7 @@ package android.graphics.drawable; import android.content.res.Resources; +import android.content.res.Resources.Theme; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.Insets; @@ -456,6 +457,16 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { } @Override + public void applyTheme(Theme theme) { + mDrawableContainerState.applyTheme(theme); + } + + @Override + public boolean canApplyTheme() { + return mDrawableContainerState.canApplyTheme(); + } + + @Override public ConstantState getConstantState() { if (mDrawableContainerState.canConstantState()) { mDrawableContainerState.mChangingConfigurations = getChangingConfigurations(); @@ -483,6 +494,8 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { final DrawableContainer mOwner; final Resources mRes; + Theme mTheme; + SparseArray<ConstantStateFuture> mDrawableFutures; int mChangingConfigurations; @@ -680,6 +693,41 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { mLayoutDirection = layoutDirection; } + final void applyTheme(Theme theme) { + // No need to call createAllFutures, since future drawables will + // apply the theme when they are prepared. + final int N = mNumChildren; + final Drawable[] drawables = mDrawables; + for (int i = 0; i < N; i++) { + if (drawables[i] != null) { + drawables[i].applyTheme(theme); + } + } + + mTheme = theme; + } + + @Override + public boolean canApplyTheme() { + final int N = mNumChildren; + final Drawable[] drawables = mDrawables; + for (int i = 0; i < N; i++) { + final Drawable d = drawables[i]; + if (d != null) { + if (d.canApplyTheme()) { + return true; + } + } else { + final ConstantStateFuture future = mDrawableFutures.get(i); + if (future != null && future.canApplyTheme()) { + return true; + } + } + } + + return false; + } + final void mutate() { // No need to call createAllFutures, since future drawables will // mutate when they are prepared. @@ -898,8 +946,14 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { * @return a prepared Drawable */ public Drawable get(DrawableContainerState state) { - final Drawable result = (state.mRes == null) ? - mConstantState.newDrawable() : mConstantState.newDrawable(state.mRes); + final Drawable result; + if (state.mRes == null) { + result = mConstantState.newDrawable(); + } else if (state.mTheme == null) { + result = mConstantState.newDrawable(state.mRes); + } else { + result = mConstantState.newDrawable(state.mRes, state.mTheme); + } result.setLayoutDirection(state.mLayoutDirection); result.setCallback(state.mOwner); @@ -909,6 +963,14 @@ public class DrawableContainer extends Drawable implements Drawable.Callback { return result; } + + /** + * Whether the constant state wrapped by this future can apply a + * theme. + */ + public boolean canApplyTheme() { + return mConstantState.canApplyTheme(); + } } } diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java index 8f22add..1568e99 100644 --- a/graphics/java/android/graphics/drawable/GradientDrawable.java +++ b/graphics/java/android/graphics/drawable/GradientDrawable.java @@ -19,6 +19,7 @@ package android.graphics.drawable; import android.content.res.ColorStateList; import android.content.res.Resources; import android.content.res.TypedArray; +import android.content.res.Resources.Theme; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; @@ -33,11 +34,12 @@ import android.graphics.Rect; import android.graphics.RectF; import android.graphics.Shader; import android.graphics.SweepGradient; -import android.os.Build; import android.util.AttributeSet; import android.util.Log; import android.util.TypedValue; +import com.android.internal.R; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -123,6 +125,9 @@ public class GradientDrawable extends Drawable { /** Radius is a fraction of the bounds size. */ private static final int RADIUS_TYPE_FRACTION_PARENT = 2; + private static final float DEFAULT_INNER_RADIUS_RATIO = 3.0f; + private static final float DEFAULT_THICKNESS_RATIO = 9.0f; + private GradientState mGradientState; private final Paint mFillPaint = new Paint(Paint.ANTI_ALIAS_FLAG); @@ -168,7 +173,7 @@ public class GradientDrawable extends Drawable { } public GradientDrawable() { - this(new GradientState(Orientation.TOP_BOTTOM, null)); + this(new GradientState(Orientation.TOP_BOTTOM, null), null); } /** @@ -176,7 +181,7 @@ public class GradientDrawable extends Drawable { * of colors for the gradient. */ public GradientDrawable(Orientation orientation, int[] colors) { - this(new GradientState(orientation, colors)); + this(new GradientState(orientation, colors), null); } @Override @@ -857,7 +862,7 @@ public class GradientDrawable extends Drawable { float x0, x1, y0, y1; if (st.mGradient == LINEAR_GRADIENT) { - final float level = st.mUseLevel ? (float) getLevel() / 10000.0f : 1.0f; + final float level = st.mUseLevel ? getLevel() / 10000.0f : 1.0f; switch (st.mOrientation) { case TOP_BOTTOM: x0 = r.left; y0 = r.top; @@ -937,12 +942,12 @@ public class GradientDrawable extends Drawable { tempColors[length] = colors[length - 1]; tempPositions = st.mTempPositions; - final float fraction = 1.0f / (float) (length - 1); + final float fraction = 1.0f / (length - 1); if (tempPositions == null || tempPositions.length != length + 1) { tempPositions = st.mTempPositions = new float[length + 1]; } - final float level = (float) getLevel() / 10000.0f; + final float level = getLevel() / 10000.0f; for (int i = 0; i < length; i++) { tempPositions[i] = i * fraction * level; } @@ -963,45 +968,196 @@ public class GradientDrawable extends Drawable { } @Override - public void inflate(Resources r, XmlPullParser parser, - AttributeSet attrs) + public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { - - final GradientState st = mGradientState; - - TypedArray a = r.obtainAttributes(attrs, - com.android.internal.R.styleable.GradientDrawable); + final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.GradientDrawable); + super.inflateWithAttributes(r, parser, a, R.styleable.GradientDrawable_visible); - super.inflateWithAttributes(r, parser, a, - com.android.internal.R.styleable.GradientDrawable_visible); - - int shapeType = a.getInt( - com.android.internal.R.styleable.GradientDrawable_shape, RECTANGLE); - boolean dither = a.getBoolean( - com.android.internal.R.styleable.GradientDrawable_dither, false); - - if (shapeType == RING) { - st.mInnerRadius = a.getDimensionPixelSize( - com.android.internal.R.styleable.GradientDrawable_innerRadius, -1); - if (st.mInnerRadius == -1) { - st.mInnerRadiusRatio = a.getFloat( - com.android.internal.R.styleable.GradientDrawable_innerRadiusRatio, 3.0f); + inflateStateFromTypedArray(a); + a.recycle(); + + inflateChildElements(r, parser, attrs, theme); + + mGradientState.computeOpacity(); + } + + /** + * Initializes the constant state from the values in the typed array. + */ + private void inflateStateFromTypedArray(TypedArray a) { + final GradientState state = mGradientState; + + // Extract the theme attributes, if any. + final int[] themeAttrs = a.extractThemeAttrs(); + state.mThemeAttrs = themeAttrs; + + final boolean needsRingAttrs; + if (themeAttrs == null || themeAttrs[R.styleable.GradientDrawable_shape] == 0) { + final int shapeType = a.getInt(R.styleable.GradientDrawable_shape, RECTANGLE); + setShape(shapeType); + needsRingAttrs = shapeType == RING; + } else { + needsRingAttrs = true; + } + + // We only need to load ring attributes if the shape type is a theme + // attribute (e.g. unknown) or defined in XML as RING. + if (needsRingAttrs) { + if (themeAttrs == null || themeAttrs[R.styleable.GradientDrawable_innerRadius] == 0) { + state.mInnerRadius = a.getDimensionPixelSize( + R.styleable.GradientDrawable_innerRadius, -1); } - st.mThickness = a.getDimensionPixelSize( - com.android.internal.R.styleable.GradientDrawable_thickness, -1); - if (st.mThickness == -1) { - st.mThicknessRatio = a.getFloat( - com.android.internal.R.styleable.GradientDrawable_thicknessRatio, 9.0f); + + if (state.mInnerRadius == -1 + && themeAttrs == null || themeAttrs[R.styleable.GradientDrawable_thicknessRatio] == 0) { + state.mInnerRadiusRatio = a.getFloat( + R.styleable.GradientDrawable_innerRadiusRatio, DEFAULT_INNER_RADIUS_RATIO); + } + + if (themeAttrs == null || themeAttrs[R.styleable.GradientDrawable_thickness] == 0) { + state.mThickness = a.getDimensionPixelSize( + R.styleable.GradientDrawable_thickness, -1); + } + + if (state.mThickness == -1 + && themeAttrs == null || themeAttrs[R.styleable.GradientDrawable_thicknessRatio] == 0) { + state.mThicknessRatio = a.getFloat( + R.styleable.GradientDrawable_thicknessRatio, DEFAULT_THICKNESS_RATIO); + } + + if (themeAttrs == null || themeAttrs[R.styleable.GradientDrawable_useLevel] == 0) { + state.mUseLevelForShape = a.getBoolean( + R.styleable.GradientDrawable_useLevel, true); } - st.mUseLevelForShape = a.getBoolean( - com.android.internal.R.styleable.GradientDrawable_useLevel, true); } - - a.recycle(); - - setShape(shapeType); - setDither(dither); + if (themeAttrs == null || themeAttrs[R.styleable.GradientDrawable_dither] == 0) { + final boolean dither = a.getBoolean(R.styleable.GradientDrawable_dither, false); + setDither(dither); + } + } + + @Override + public void applyTheme(Theme t) { + super.applyTheme(t); + + final GradientState state = mGradientState; + if (state == null) { + throw new RuntimeException("Can't apply theme to <shape> with no constant state"); + } + + final int[] themeAttrs = state.mThemeAttrs; + if (themeAttrs != null) { + final TypedArray a = t.resolveAttributes( + themeAttrs, R.styleable.GradientDrawable, 0, 0); + updateStateFromTypedArray(a); + a.recycle(); + + applyThemeChildElements(t); + + mGradientState.computeOpacity(); + } + } + + /** + * Updates the constant state from the values in the typed array. + */ + private void updateStateFromTypedArray(TypedArray a) { + final GradientState state = mGradientState; + + if (a.hasValue(R.styleable.GradientDrawable_shape)) { + final int shapeType = a.getInt(R.styleable.GradientDrawable_shape, RECTANGLE); + setShape(shapeType); + } + + if (a.hasValue(R.styleable.GradientDrawable_dither)) { + final boolean dither = a.getBoolean(R.styleable.GradientDrawable_dither, false); + setDither(dither); + } + + if (state.mShape == RING) { + if (a.hasValue(R.styleable.GradientDrawable_innerRadius)) { + state.mInnerRadius = a.getDimensionPixelSize( + R.styleable.GradientDrawable_innerRadius, -1); + } + + if (state.mInnerRadius == -1 && a.hasValue( + R.styleable.GradientDrawable_innerRadiusRatio)) { + state.mInnerRadiusRatio = a.getFloat( + R.styleable.GradientDrawable_innerRadiusRatio, DEFAULT_INNER_RADIUS_RATIO); + } + + if (a.hasValue(R.styleable.GradientDrawable_thickness)) { + state.mThickness = a.getDimensionPixelSize( + R.styleable.GradientDrawable_thickness, -1); + } + + if (state.mThickness == -1 && a.hasValue( + R.styleable.GradientDrawable_thicknessRatio)) { + state.mThicknessRatio = a.getFloat( + R.styleable.GradientDrawable_thicknessRatio, DEFAULT_THICKNESS_RATIO); + } + + if (a.hasValue(R.styleable.GradientDrawable_useLevel)) { + state.mUseLevelForShape = a.getBoolean( + R.styleable.GradientDrawable_useLevel, true); + } + } + } + + @Override + public boolean canApplyTheme() { + final GradientState state = mGradientState; + return state != null && (state.mThemeAttrs != null || state.mAttrSize != null + || state.mAttrGradient != null || state.mAttrSolid != null + || state.mAttrStroke != null || state.mAttrCorners != null + || state.mAttrPadding != null); + } + + private void applyThemeChildElements(Theme t) { + final GradientState state = mGradientState; + TypedArray a; + + if (state.mAttrSize != null) { + a = t.resolveAttributes(state.mAttrSize, R.styleable.GradientDrawableSize, 0, 0); + // TODO: updateGradientDrawableSize(a); + a.recycle(); + } + + if (state.mAttrGradient != null) { + a = t.resolveAttributes(state.mAttrGradient, R.styleable.GradientDrawableGradient, 0, 0); + // TODO: updateGradientDrawableGradient(a); + a.recycle(); + } + + if (state.mAttrSolid != null) { + a = t.resolveAttributes(state.mAttrSolid, R.styleable.GradientDrawableSolid, 0, 0); + // TODO: updateGradientDrawableSolid(a); + a.recycle(); + } + + if (state.mAttrStroke != null) { + a = t.resolveAttributes(state.mAttrStroke, R.styleable.GradientDrawableStroke, 0, 0); + // TODO: updateGradientDrawableStroke(a); + a.recycle(); + } + + if (state.mAttrCorners != null) { + a = t.resolveAttributes(state.mAttrCorners, R.styleable.DrawableCorners, 0, 0); + // TODO: updateDrawableCorners(a); + a.recycle(); + } + + if (state.mAttrPadding != null) { + a = t.resolveAttributes(state.mAttrPadding, R.styleable.GradientDrawablePadding, 0, 0); + // TODO: updateGradientDrawablePadding(a); + a.recycle(); + } + } + + private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs, + Theme theme) throws XmlPullParserException, IOException { + TypedArray a; int type; final int innerDepth = parser.getDepth() + 1; @@ -1016,203 +1172,233 @@ public class GradientDrawable extends Drawable { if (depth > innerDepth) { continue; } - + String name = parser.getName(); if (name.equals("size")) { - a = r.obtainAttributes(attrs, - com.android.internal.R.styleable.GradientDrawableSize); - int width = a.getDimensionPixelSize( - com.android.internal.R.styleable.GradientDrawableSize_width, -1); - int height = a.getDimensionPixelSize( - com.android.internal.R.styleable.GradientDrawableSize_height, -1); + a = obtainAttributes( + r, theme, attrs, R.styleable.GradientDrawableSize); + applyGradientDrawableSize(a); a.recycle(); - setSize(width, height); } else if (name.equals("gradient")) { - a = r.obtainAttributes(attrs, - com.android.internal.R.styleable.GradientDrawableGradient); - int startColor = a.getColor( - com.android.internal.R.styleable.GradientDrawableGradient_startColor, 0); - boolean hasCenterColor = a - .hasValue(com.android.internal.R.styleable.GradientDrawableGradient_centerColor); - int centerColor = a.getColor( - com.android.internal.R.styleable.GradientDrawableGradient_centerColor, 0); - int endColor = a.getColor( - com.android.internal.R.styleable.GradientDrawableGradient_endColor, 0); - int gradientType = a.getInt( - com.android.internal.R.styleable.GradientDrawableGradient_type, - LINEAR_GRADIENT); - - st.mCenterX = getFloatOrFraction( - a, - com.android.internal.R.styleable.GradientDrawableGradient_centerX, - 0.5f); - - st.mCenterY = getFloatOrFraction( - a, - com.android.internal.R.styleable.GradientDrawableGradient_centerY, - 0.5f); - - st.mUseLevel = a.getBoolean( - com.android.internal.R.styleable.GradientDrawableGradient_useLevel, false); - st.mGradient = gradientType; - - if (gradientType == LINEAR_GRADIENT) { - int angle = (int)a.getFloat( - com.android.internal.R.styleable.GradientDrawableGradient_angle, 0); - angle %= 360; - if (angle % 45 != 0) { - throw new XmlPullParserException(a.getPositionDescription() - + "<gradient> tag requires 'angle' attribute to " - + "be a multiple of 45"); - } - - switch (angle) { - case 0: - st.mOrientation = Orientation.LEFT_RIGHT; - break; - case 45: - st.mOrientation = Orientation.BL_TR; - break; - case 90: - st.mOrientation = Orientation.BOTTOM_TOP; - break; - case 135: - st.mOrientation = Orientation.BR_TL; - break; - case 180: - st.mOrientation = Orientation.RIGHT_LEFT; - break; - case 225: - st.mOrientation = Orientation.TR_BL; - break; - case 270: - st.mOrientation = Orientation.TOP_BOTTOM; - break; - case 315: - st.mOrientation = Orientation.TL_BR; - break; - } - } else { - final TypedValue tv = a.peekValue( - com.android.internal.R.styleable.GradientDrawableGradient_gradientRadius); - if (tv != null) { - final float radius; - final int radiusType; - if (tv.type == TypedValue.TYPE_FRACTION) { - radius = tv.getFraction(1.0f, 1.0f); - - final int unit = (tv.data >> TypedValue.COMPLEX_UNIT_SHIFT) - & TypedValue.COMPLEX_UNIT_MASK; - if (unit == TypedValue.COMPLEX_UNIT_FRACTION_PARENT) { - radiusType = RADIUS_TYPE_FRACTION_PARENT; - } else { - radiusType = RADIUS_TYPE_FRACTION; - } - } else { - radius = tv.getDimension(r.getDisplayMetrics()); - radiusType = RADIUS_TYPE_PIXELS; - } - - st.mGradientRadius = radius; - st.mGradientRadiusType = radiusType; - } else if (gradientType == RADIAL_GRADIENT) { - throw new XmlPullParserException( - a.getPositionDescription() - + "<gradient> tag requires 'gradientRadius' " - + "attribute with radial type"); - } - } - + a = obtainAttributes( + r, theme, attrs, R.styleable.GradientDrawableGradient); + applyGradientDrawableGradient(r, a); a.recycle(); - - if (hasCenterColor) { - st.mColors = new int[3]; - st.mColors[0] = startColor; - st.mColors[1] = centerColor; - st.mColors[2] = endColor; - - st.mPositions = new float[3]; - st.mPositions[0] = 0.0f; - // Since 0.5f is default value, try to take the one that isn't 0.5f - st.mPositions[1] = st.mCenterX != 0.5f ? st.mCenterX : st.mCenterY; - st.mPositions[2] = 1f; - } else { - st.mColors = new int[2]; - st.mColors[0] = startColor; - st.mColors[1] = endColor; - } - } else if (name.equals("solid")) { - a = r.obtainAttributes(attrs, - com.android.internal.R.styleable.GradientDrawableSolid); - final ColorStateList colorStateList = a.getColorStateList( - com.android.internal.R.styleable.GradientDrawableSolid_color); + a = obtainAttributes( + r, theme, attrs, R.styleable.GradientDrawableSolid); + applyGradientDrawableSolid(a); a.recycle(); - setColor(colorStateList); } else if (name.equals("stroke")) { - a = r.obtainAttributes(attrs, - com.android.internal.R.styleable.GradientDrawableStroke); - final int width = a.getDimensionPixelSize( - com.android.internal.R.styleable.GradientDrawableStroke_width, 0); - final ColorStateList colorStateList = a.getColorStateList( - com.android.internal.R.styleable.GradientDrawableStroke_color); - final float dashWidth = a.getDimension( - com.android.internal.R.styleable.GradientDrawableStroke_dashWidth, 0); - if (dashWidth != 0.0f) { - final float dashGap = a.getDimension( - com.android.internal.R.styleable.GradientDrawableStroke_dashGap, 0); - setStroke(width, colorStateList, dashWidth, dashGap); - } else { - setStroke(width, colorStateList); - } + a = obtainAttributes( + r, theme, attrs, R.styleable.GradientDrawableStroke); + applyGradientDrawableStroke(a); a.recycle(); } else if (name.equals("corners")) { - a = r.obtainAttributes(attrs, - com.android.internal.R.styleable.DrawableCorners); - int radius = a.getDimensionPixelSize( - com.android.internal.R.styleable.DrawableCorners_radius, 0); - setCornerRadius(radius); - int topLeftRadius = a.getDimensionPixelSize( - com.android.internal.R.styleable.DrawableCorners_topLeftRadius, radius); - int topRightRadius = a.getDimensionPixelSize( - com.android.internal.R.styleable.DrawableCorners_topRightRadius, radius); - int bottomLeftRadius = a.getDimensionPixelSize( - com.android.internal.R.styleable.DrawableCorners_bottomLeftRadius, radius); - int bottomRightRadius = a.getDimensionPixelSize( - com.android.internal.R.styleable.DrawableCorners_bottomRightRadius, radius); - if (topLeftRadius != radius || topRightRadius != radius || - bottomLeftRadius != radius || bottomRightRadius != radius) { - // The corner radii are specified in clockwise order (see Path.addRoundRect()) - setCornerRadii(new float[] { - topLeftRadius, topLeftRadius, - topRightRadius, topRightRadius, - bottomRightRadius, bottomRightRadius, - bottomLeftRadius, bottomLeftRadius - }); - } + a = obtainAttributes(r + , theme, attrs, R.styleable.DrawableCorners); + applyDrawableCorners(a); a.recycle(); } else if (name.equals("padding")) { - a = r.obtainAttributes(attrs, - com.android.internal.R.styleable.GradientDrawablePadding); - mPadding = new Rect( - a.getDimensionPixelOffset( - com.android.internal.R.styleable.GradientDrawablePadding_left, 0), - a.getDimensionPixelOffset( - com.android.internal.R.styleable.GradientDrawablePadding_top, 0), - a.getDimensionPixelOffset( - com.android.internal.R.styleable.GradientDrawablePadding_right, 0), - a.getDimensionPixelOffset( - com.android.internal.R.styleable.GradientDrawablePadding_bottom, 0)); + a = obtainAttributes( + r, theme, attrs, R.styleable.GradientDrawablePadding); + applyGradientDrawablePadding(a); a.recycle(); - mGradientState.mPadding = mPadding; } else { Log.w("drawable", "Bad element under <shape>: " + name); } + } + } + + private void applyGradientDrawablePadding(TypedArray a) { + mPadding = new Rect( + a.getDimensionPixelOffset( + R.styleable.GradientDrawablePadding_left, 0), + a.getDimensionPixelOffset( + R.styleable.GradientDrawablePadding_top, 0), + a.getDimensionPixelOffset( + R.styleable.GradientDrawablePadding_right, 0), + a.getDimensionPixelOffset( + R.styleable.GradientDrawablePadding_bottom, 0)); + mGradientState.mPadding = mPadding; + + // Extract the theme attributes, if any. + mGradientState.mAttrPadding = a.extractThemeAttrs(); + } + private void applyDrawableCorners(TypedArray a) { + int radius = a.getDimensionPixelSize( + R.styleable.DrawableCorners_radius, 0); + setCornerRadius(radius); + int topLeftRadius = a.getDimensionPixelSize( + R.styleable.DrawableCorners_topLeftRadius, radius); + int topRightRadius = a.getDimensionPixelSize( + R.styleable.DrawableCorners_topRightRadius, radius); + int bottomLeftRadius = a.getDimensionPixelSize( + R.styleable.DrawableCorners_bottomLeftRadius, radius); + int bottomRightRadius = a.getDimensionPixelSize( + R.styleable.DrawableCorners_bottomRightRadius, radius); + if (topLeftRadius != radius || topRightRadius != radius || + bottomLeftRadius != radius || bottomRightRadius != radius) { + // The corner radii are specified in clockwise order (see Path.addRoundRect()) + setCornerRadii(new float[] { + topLeftRadius, topLeftRadius, + topRightRadius, topRightRadius, + bottomRightRadius, bottomRightRadius, + bottomLeftRadius, bottomLeftRadius + }); } - mGradientState.computeOpacity(); + // Extract the theme attributes, if any. + mGradientState.mAttrCorners = a.extractThemeAttrs(); + } + + private void applyGradientDrawableStroke(TypedArray a) { + final int width = a.getDimensionPixelSize( + R.styleable.GradientDrawableStroke_width, 0); + final ColorStateList colorStateList = a.getColorStateList( + R.styleable.GradientDrawableStroke_color); + final float dashWidth = a.getDimension( + R.styleable.GradientDrawableStroke_dashWidth, 0); + if (dashWidth != 0.0f) { + final float dashGap = a.getDimension( + R.styleable.GradientDrawableStroke_dashGap, 0); + setStroke(width, colorStateList, dashWidth, dashGap); + } else { + setStroke(width, colorStateList); + } + + // Extract the theme attributes, if any. + mGradientState.mAttrStroke = a.extractThemeAttrs(); + } + + private void applyGradientDrawableSolid(TypedArray a) { + final ColorStateList colorStateList = a.getColorStateList( + R.styleable.GradientDrawableSolid_color); + setColor(colorStateList); + + // Extract the theme attributes, if any. + mGradientState.mAttrSolid = a.extractThemeAttrs(); + } + + private void applyGradientDrawableGradient(Resources r, TypedArray a) + throws XmlPullParserException { + final GradientState st = mGradientState; + final int startColor = a.getColor( + R.styleable.GradientDrawableGradient_startColor, 0); + final boolean hasCenterColor = a.hasValue( + R.styleable.GradientDrawableGradient_centerColor); + final int centerColor = a.getColor( + R.styleable.GradientDrawableGradient_centerColor, 0); + final int endColor = a.getColor( + R.styleable.GradientDrawableGradient_endColor, 0); + + if (hasCenterColor) { + st.mColors = new int[3]; + st.mColors[0] = startColor; + st.mColors[1] = centerColor; + st.mColors[2] = endColor; + + st.mPositions = new float[3]; + st.mPositions[0] = 0.0f; + // Since 0.5f is default value, try to take the one that isn't 0.5f + st.mPositions[1] = st.mCenterX != 0.5f ? st.mCenterX : st.mCenterY; + st.mPositions[2] = 1f; + } else { + st.mColors = new int[2]; + st.mColors[0] = startColor; + st.mColors[1] = endColor; + } + + st.mCenterX = getFloatOrFraction( + a, R.styleable.GradientDrawableGradient_centerX, 0.5f); + st.mCenterY = getFloatOrFraction( + a, R.styleable.GradientDrawableGradient_centerY, 0.5f); + st.mUseLevel = a.getBoolean( + R.styleable.GradientDrawableGradient_useLevel, false); + st.mGradient = a.getInt( + R.styleable.GradientDrawableGradient_type, LINEAR_GRADIENT); + + if (st.mGradient == LINEAR_GRADIENT) { + int angle = (int) a.getFloat( + R.styleable.GradientDrawableGradient_angle, 0); + angle %= 360; + + if (angle % 45 != 0) { + throw new XmlPullParserException(a.getPositionDescription() + + "<gradient> tag requires 'angle' attribute to " + + "be a multiple of 45"); + } + + switch (angle) { + case 0: + st.mOrientation = Orientation.LEFT_RIGHT; + break; + case 45: + st.mOrientation = Orientation.BL_TR; + break; + case 90: + st.mOrientation = Orientation.BOTTOM_TOP; + break; + case 135: + st.mOrientation = Orientation.BR_TL; + break; + case 180: + st.mOrientation = Orientation.RIGHT_LEFT; + break; + case 225: + st.mOrientation = Orientation.TR_BL; + break; + case 270: + st.mOrientation = Orientation.TOP_BOTTOM; + break; + case 315: + st.mOrientation = Orientation.TL_BR; + break; + } + } else { + final TypedValue tv = a.peekValue( + R.styleable.GradientDrawableGradient_gradientRadius); + if (tv != null) { + final float radius; + final int radiusType; + if (tv.type == TypedValue.TYPE_FRACTION) { + radius = tv.getFraction(1.0f, 1.0f); + + final int unit = (tv.data >> TypedValue.COMPLEX_UNIT_SHIFT) + & TypedValue.COMPLEX_UNIT_MASK; + if (unit == TypedValue.COMPLEX_UNIT_FRACTION_PARENT) { + radiusType = RADIUS_TYPE_FRACTION_PARENT; + } else { + radiusType = RADIUS_TYPE_FRACTION; + } + } else { + radius = tv.getDimension(r.getDisplayMetrics()); + radiusType = RADIUS_TYPE_PIXELS; + } + + st.mGradientRadius = radius; + st.mGradientRadiusType = radiusType; + } else if (st.mGradient == RADIAL_GRADIENT) { + throw new XmlPullParserException( + a.getPositionDescription() + + "<gradient> tag requires 'gradientRadius' " + + "attribute with radial type"); + } + } + + // Extract the theme attributes, if any. + mGradientState.mAttrGradient = a.extractThemeAttrs(); + } + + private void applyGradientDrawableSize(TypedArray a) { + int width = a.getDimensionPixelSize(R.styleable.GradientDrawableSize_width, -1); + int height = a.getDimensionPixelSize(R.styleable.GradientDrawableSize_height, -1); + setSize(width, height); + + // Extract the theme attributes, if any. + mGradientState.mAttrSize = a.extractThemeAttrs(); } private static float getFloatOrFraction(TypedArray a, int index, float defaultValue) { @@ -1322,6 +1508,14 @@ public class GradientDrawable extends Drawable { private boolean mUseLevelForShape; private boolean mOpaque; + int[] mThemeAttrs; + int[] mAttrSize; + int[] mAttrGradient; + int[] mAttrSolid; + int[] mAttrStroke; + int[] mAttrCorners; + int[] mAttrPadding; + GradientState(Orientation orientation, int[] colors) { mOrientation = orientation; setColors(colors); @@ -1363,16 +1557,33 @@ public class GradientDrawable extends Drawable { mUseLevel = state.mUseLevel; mUseLevelForShape = state.mUseLevelForShape; mOpaque = state.mOpaque; + mThemeAttrs = state.mThemeAttrs; + mAttrSize = state.mAttrSize; + mAttrGradient = state.mAttrGradient; + mAttrSolid = state.mAttrSolid; + mAttrStroke = state.mAttrStroke; + mAttrCorners = state.mAttrCorners; + mAttrPadding = state.mAttrPadding; + } + + @Override + public boolean canApplyTheme() { + return mThemeAttrs != null; } @Override public Drawable newDrawable() { - return new GradientDrawable(this); + return new GradientDrawable(this, null); } @Override public Drawable newDrawable(Resources res) { - return new GradientDrawable(this); + return new GradientDrawable(this, null); + } + + @Override + public Drawable newDrawable(Resources res, Theme theme) { + return new GradientDrawable(this, theme); } @Override @@ -1482,8 +1693,20 @@ public class GradientDrawable extends Drawable { } } - private GradientDrawable(GradientState state) { - mGradientState = state; + /** + * Creates a new themed GradientDrawable based on the specified constant state. + * <p> + * The resulting drawable is guaranteed to have a new constant state. + * + * @param state Constant state from which the drawable inherits + * @param theme Theme to apply to the drawable + */ + private GradientDrawable(GradientState state, Theme theme) { + mGradientState = new GradientState(state); + if (theme != null && state.canApplyTheme()) { + applyTheme(theme); + } + initializeWithState(state); mRectIsDirty = true; mMutated = false; diff --git a/graphics/java/android/graphics/drawable/InsetDrawable.java b/graphics/java/android/graphics/drawable/InsetDrawable.java index f841d6a..59a0c93 100644 --- a/graphics/java/android/graphics/drawable/InsetDrawable.java +++ b/graphics/java/android/graphics/drawable/InsetDrawable.java @@ -21,6 +21,7 @@ import org.xmlpull.v1.XmlPullParserException; import android.content.res.Resources; import android.content.res.TypedArray; +import android.content.res.Resources.Theme; import android.graphics.*; import android.util.AttributeSet; import android.util.Log; @@ -72,10 +73,10 @@ public class InsetDrawable extends Drawable implements Drawable.Callback drawable.setCallback(this); } } - - @Override public void inflate(Resources r, XmlPullParser parser, - AttributeSet attrs) - throws XmlPullParserException, IOException { + + @Override + public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) + throws XmlPullParserException, IOException { int type; TypedArray a = r.obtainAttributes(attrs, @@ -110,7 +111,7 @@ public class InsetDrawable extends Drawable implements Drawable.Callback + ": <inset> tag requires a 'drawable' attribute or " + "child tag defining a drawable"); } - dr = Drawable.createFromXmlInner(r, parser, attrs); + dr = Drawable.createFromXmlInnerThemed(r, parser, attrs, theme); } if (dr == null) { diff --git a/graphics/java/android/graphics/drawable/LayerDrawable.java b/graphics/java/android/graphics/drawable/LayerDrawable.java index 2e098a0..3d48cda 100644 --- a/graphics/java/android/graphics/drawable/LayerDrawable.java +++ b/graphics/java/android/graphics/drawable/LayerDrawable.java @@ -16,10 +16,8 @@ package android.graphics.drawable; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - import android.content.res.Resources; +import android.content.res.Resources.Theme; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.ColorFilter; @@ -28,6 +26,11 @@ import android.graphics.Rect; import android.util.AttributeSet; import android.view.View; +import com.android.internal.R; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + import java.io.IOException; /** @@ -84,7 +87,7 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { * @param state The constant drawable state. */ LayerDrawable(Drawable[] layers, LayerState state) { - this(state, null); + this(state, null, null); int length = layers.length; ChildDrawable[] r = new ChildDrawable[length]; @@ -101,15 +104,18 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { } LayerDrawable() { - this((LayerState) null, null); + this((LayerState) null, null, null); } - LayerDrawable(LayerState state, Resources res) { - LayerState as = createConstantState(state, res); + LayerDrawable(LayerState state, Resources res, Theme theme) { + final LayerState as = createConstantState(state, res); mLayerState = as; if (as.mNum > 0) { ensurePadding(); } + if (theme != null && canApplyTheme()) { + applyTheme(theme); + } } LayerState createConstantState(LayerState state, Resources res) { @@ -117,26 +123,53 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { } @Override - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) + public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { - super.inflate(r, parser, attrs); + super.inflate(r, parser, attrs, theme); - int type; + final TypedArray a = obtainAttributes( + r, theme, attrs, R.styleable.LayerDrawable); + inflateStateFromTypedArray(a); + a.recycle(); - TypedArray a = r.obtainAttributes(attrs, com.android.internal.R.styleable.LayerDrawable); + inflateLayers(r, parser, attrs, theme); - mOpacityOverride = a.getInt(com.android.internal.R.styleable.LayerDrawable_opacity, - PixelFormat.UNKNOWN); + ensurePadding(); + onStateChange(getState()); + } + + /** + * Initializes the constant state from the values in the typed array. + */ + private void inflateStateFromTypedArray(TypedArray a) { + final LayerState state = mLayerState; - setAutoMirrored(a.getBoolean(com.android.internal.R.styleable.LayerDrawable_autoMirrored, - false)); + // Extract the theme attributes, if any. + final int[] themeAttrs = a.extractThemeAttrs(); + state.mThemeAttrs = themeAttrs; - mLayerState.mPaddingMode = a.getInteger( - com.android.internal.R.styleable.LayerDrawableItem_drawable, PADDING_MODE_NEST); + if (themeAttrs == null || themeAttrs[R.styleable.LayerDrawable_opacity] == 0) { + mOpacityOverride = a.getInt(R.styleable.LayerDrawable_opacity, PixelFormat.UNKNOWN); + } - a.recycle(); + if (themeAttrs == null || themeAttrs[R.styleable.LayerDrawable_autoMirrored] == 0) { + state.mAutoMirrored = a.getBoolean(R.styleable.LayerDrawable_autoMirrored, false); + } + if (themeAttrs == null || themeAttrs[R.styleable.LayerDrawableItem_drawable] == 0) { + state.mPaddingMode = a.getInteger( + R.styleable.LayerDrawableItem_drawable, PADDING_MODE_NEST); + } + } + + /** + * Inflates child layers using the specified parser. + */ + private void inflateLayers(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) + throws XmlPullParserException, IOException { + TypedArray a; final int innerDepth = parser.getDepth() + 1; + int type; int depth; while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) { @@ -148,27 +181,28 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { continue; } - a = r.obtainAttributes(attrs, - com.android.internal.R.styleable.LayerDrawableItem); - - int left = a.getDimensionPixelOffset( - com.android.internal.R.styleable.LayerDrawableItem_left, 0); - int top = a.getDimensionPixelOffset( - com.android.internal.R.styleable.LayerDrawableItem_top, 0); - int right = a.getDimensionPixelOffset( - com.android.internal.R.styleable.LayerDrawableItem_right, 0); - int bottom = a.getDimensionPixelOffset( - com.android.internal.R.styleable.LayerDrawableItem_bottom, 0); - int drawableRes = a.getResourceId( - com.android.internal.R.styleable.LayerDrawableItem_drawable, 0); - int id = a.getResourceId(com.android.internal.R.styleable.LayerDrawableItem_id, - View.NO_ID); - + a = obtainAttributes( + r, theme, attrs, R.styleable.LayerDrawableItem); + + final int left = a.getDimensionPixelOffset( + R.styleable.LayerDrawableItem_left, 0); + final int top = a.getDimensionPixelOffset( + R.styleable.LayerDrawableItem_top, 0); + final int right = a.getDimensionPixelOffset( + R.styleable.LayerDrawableItem_right, 0); + final int bottom = a.getDimensionPixelOffset( + R.styleable.LayerDrawableItem_bottom, 0); + final int drawableRes = a.getResourceId( + R.styleable.LayerDrawableItem_drawable, 0); + final int id = a.getResourceId( + R.styleable.LayerDrawableItem_id, View.NO_ID); + + // TODO: Cache typed array, if necessary. a.recycle(); - Drawable dr; + final Drawable dr; if (drawableRes != 0) { - dr = r.getDrawable(drawableRes); + dr = r.getDrawable(drawableRes, theme); } else { while ((type = parser.next()) == XmlPullParser.TEXT) { } @@ -177,17 +211,87 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { + ": <item> tag requires a 'drawable' attribute or " + "child tag defining a drawable"); } - dr = Drawable.createFromXmlInner(r, parser, attrs); + dr = Drawable.createFromXmlInnerThemed(r, parser, attrs, theme); } addLayer(dr, id, left, top, right, bottom); } + } + + @Override + public void applyTheme(Theme t) { + super.applyTheme(t); + + final LayerState state = mLayerState; + if (state == null) { + throw new RuntimeException("Can't apply theme to <layer-list> with no constant state"); + } + + final int[] themeAttrs = state.mThemeAttrs; + if (themeAttrs != null) { + final TypedArray a = t.resolveAttributes(themeAttrs, R.styleable.LayerDrawable, 0, 0); + updateStateFromTypedArray(a); + a.recycle(); + } + + // TODO: Update layer positions from cached typed arrays. + + final ChildDrawable[] array = mLayerState.mChildren; + final int N = mLayerState.mNum; + for (int i = 0; i < N; i++) { + final Drawable layer = array[i].mDrawable; + if (layer.canApplyTheme()) { + layer.applyTheme(t); + } + } ensurePadding(); onStateChange(getState()); } /** + * Updates the constant state from the values in the typed array. + */ + private void updateStateFromTypedArray(TypedArray a) { + final LayerState state = mLayerState; + + if (a.hasValue(R.styleable.LayerDrawable_opacity)) { + mOpacityOverride = a.getInt(R.styleable.LayerDrawable_opacity, PixelFormat.UNKNOWN); + } + + if (a.hasValue(R.styleable.LayerDrawable_autoMirrored)) { + state.mAutoMirrored = a.getBoolean(R.styleable.LayerDrawable_autoMirrored, false); + } + + if (a.hasValue(R.styleable.LayerDrawableItem_drawable)) { + state.mPaddingMode = a.getInteger( + R.styleable.LayerDrawableItem_drawable, PADDING_MODE_NEST); + } + } + + @Override + public boolean canApplyTheme() { + final LayerState state = mLayerState; + if (state == null) { + return false; + } + + if (state.mThemeAttrs != null) { + return true; + } + + final ChildDrawable[] array = state.mChildren; + final int N = state.mNum; + for (int i = 0; i < N; i++) { + if (array[i].mDrawable.canApplyTheme()) { + return true; + } + } + + return false; + } + + /** * Add a new layer to this drawable. The new layer is identified by an id. * * @param layer The drawable to add as a layer. @@ -783,13 +887,35 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { static class ChildDrawable { public Drawable mDrawable; + public int[] mThemeAttrs; public int mInsetL, mInsetT, mInsetR, mInsetB; public int mId; + + ChildDrawable() { + // Default empty constructor. + } + + ChildDrawable(ChildDrawable or, LayerDrawable owner, Resources res) { + if (res != null) { + mDrawable = or.mDrawable.getConstantState().newDrawable(res); + } else { + mDrawable = or.mDrawable.getConstantState().newDrawable(); + } + mDrawable.setCallback(owner); + mDrawable.setLayoutDirection(or.mDrawable.getLayoutDirection()); + mThemeAttrs = or.mThemeAttrs; + mInsetL = or.mInsetL; + mInsetT = or.mInsetT; + mInsetR = or.mInsetR; + mInsetB = or.mInsetB; + mId = or.mId; + } } static class LayerState extends ConstantState { int mNum; ChildDrawable[] mChildren; + int[] mThemeAttrs; int mChangingConfigurations; int mChildrenChangingConfigurations; @@ -819,20 +945,8 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { mChildrenChangingConfigurations = orig.mChildrenChangingConfigurations; for (int i = 0; i < N; i++) { - final ChildDrawable r = mChildren[i] = new ChildDrawable(); final ChildDrawable or = origChildDrawable[i]; - if (res != null) { - r.mDrawable = or.mDrawable.getConstantState().newDrawable(res); - } else { - r.mDrawable = or.mDrawable.getConstantState().newDrawable(); - } - r.mDrawable.setCallback(owner); - r.mDrawable.setLayoutDirection(or.mDrawable.getLayoutDirection()); - r.mInsetL = or.mInsetL; - r.mInsetT = or.mInsetT; - r.mInsetR = or.mInsetR; - r.mInsetB = or.mInsetB; - r.mId = or.mId; + mChildren[i] = new ChildDrawable(or, owner, res); } mHaveOpacity = orig.mHaveOpacity; @@ -842,6 +956,7 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { mCheckedConstantState = mCanConstantState = true; mAutoMirrored = orig.mAutoMirrored; mPaddingMode = orig.mPaddingMode; + mThemeAttrs = orig.mThemeAttrs; } else { mNum = 0; mChildren = null; @@ -849,13 +964,23 @@ public class LayerDrawable extends Drawable implements Drawable.Callback { } @Override + public boolean canApplyTheme() { + return mThemeAttrs != null; + } + + @Override public Drawable newDrawable() { - return new LayerDrawable(this, null); + return new LayerDrawable(this, null, null); } @Override public Drawable newDrawable(Resources res) { - return new LayerDrawable(this, res); + return new LayerDrawable(this, res, null); + } + + @Override + public Drawable newDrawable(Resources res, Theme theme) { + return new LayerDrawable(this, res, theme); } @Override diff --git a/graphics/java/android/graphics/drawable/LevelListDrawable.java b/graphics/java/android/graphics/drawable/LevelListDrawable.java index 872fdce..9f6c0ad 100644 --- a/graphics/java/android/graphics/drawable/LevelListDrawable.java +++ b/graphics/java/android/graphics/drawable/LevelListDrawable.java @@ -23,6 +23,7 @@ import org.xmlpull.v1.XmlPullParserException; import android.content.res.Resources; import android.content.res.TypedArray; +import android.content.res.Resources.Theme; import android.util.AttributeSet; /** @@ -83,10 +84,9 @@ public class LevelListDrawable extends DrawableContainer { } @Override - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) + public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { - - super.inflate(r, parser, attrs); + super.inflate(r, parser, attrs, theme); int type; @@ -124,7 +124,7 @@ public class LevelListDrawable extends DrawableContainer { Drawable dr; if (drawableRes != 0) { - dr = r.getDrawable(drawableRes); + dr = r.getDrawable(drawableRes, theme); } else { while ((type = parser.next()) == XmlPullParser.TEXT) { } @@ -134,7 +134,7 @@ public class LevelListDrawable extends DrawableContainer { + ": <item> tag requires a 'drawable' attribute or " + "child tag defining a drawable"); } - dr = Drawable.createFromXmlInner(r, parser, attrs); + dr = Drawable.createFromXmlInnerThemed(r, parser, attrs, theme); } mLevelListState.addLevel(low, high, dr); diff --git a/graphics/java/android/graphics/drawable/NinePatchDrawable.java b/graphics/java/android/graphics/drawable/NinePatchDrawable.java index 44584a7..66193a5 100644 --- a/graphics/java/android/graphics/drawable/NinePatchDrawable.java +++ b/graphics/java/android/graphics/drawable/NinePatchDrawable.java @@ -18,6 +18,7 @@ package android.graphics.drawable; import android.content.res.ColorStateList; import android.content.res.Resources; +import android.content.res.Resources.Theme; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -36,6 +37,8 @@ import android.util.DisplayMetrics; import android.util.LayoutDirection; import android.util.TypedValue; +import com.android.internal.R; + import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -73,6 +76,7 @@ public class NinePatchDrawable extends Drawable { private int mBitmapHeight; NinePatchDrawable() { + mNinePatchState = new NinePatchState(); } /** @@ -82,7 +86,7 @@ public class NinePatchDrawable extends Drawable { */ @Deprecated public NinePatchDrawable(Bitmap bitmap, byte[] chunk, Rect padding, String srcName) { - this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding), null); + this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding), null, null); } /** @@ -91,7 +95,7 @@ public class NinePatchDrawable extends Drawable { */ public NinePatchDrawable(Resources res, Bitmap bitmap, byte[] chunk, Rect padding, String srcName) { - this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding), res); + this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding), res, null); mNinePatchState.mTargetDensity = mTargetDensity; } @@ -103,7 +107,8 @@ public class NinePatchDrawable extends Drawable { */ public NinePatchDrawable(Resources res, Bitmap bitmap, byte[] chunk, Rect padding, Rect opticalInsets, String srcName) { - this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding, opticalInsets), res); + this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding, opticalInsets), + res, null); mNinePatchState.mTargetDensity = mTargetDensity; } @@ -114,7 +119,7 @@ public class NinePatchDrawable extends Drawable { */ @Deprecated public NinePatchDrawable(NinePatch patch) { - this(new NinePatchState(patch, new Rect()), null); + this(new NinePatchState(patch, new Rect()), null, null); } /** @@ -122,35 +127,10 @@ public class NinePatchDrawable extends Drawable { * based on the display metrics of the resources. */ public NinePatchDrawable(Resources res, NinePatch patch) { - this(new NinePatchState(patch, new Rect()), res); + this(new NinePatchState(patch, new Rect()), res, null); mNinePatchState.mTargetDensity = mTargetDensity; } - private void setNinePatchState(NinePatchState state, Resources res) { - mNinePatchState = state; - mNinePatch = state.mNinePatch; - mPadding = state.mPadding; - mTargetDensity = res != null ? res.getDisplayMetrics().densityDpi - : state.mTargetDensity; - //noinspection PointlessBooleanExpression - if (state.mDither != DEFAULT_DITHER) { - // avoid calling the setter unless we need to, since it does a - // lazy allocation of a paint - setDither(state.mDither); - } - - if (state.mTint != null) { - final int color = state.mTint.getColorForState(getState(), 0); - mTintFilter = new PorterDuffColorFilter(color, state.mTintMode); - } - - setAutoMirrored(state.mAutoMirrored); - - if (mNinePatch != null) { - computeBitmapSize(); - } - } - /** * Set the density scale at which this drawable will be rendered. This * method assumes the drawable will be rendered at the same density as the @@ -228,6 +208,19 @@ public class NinePatchDrawable extends Drawable { } } + private void setNinePatch(NinePatch ninePatch) { + if (ninePatch != mNinePatch) { + mNinePatch = ninePatch; + if (ninePatch != null) { + computeBitmapSize(); + } else { + mBitmapWidth = mBitmapHeight = -1; + mOpticalInsets = Insets.NONE; + } + invalidateSelf(); + } + } + @Override public void draw(Canvas canvas) { final Rect bounds = getBounds(); @@ -266,12 +259,17 @@ public class NinePatchDrawable extends Drawable { @Override public boolean getPadding(Rect padding) { - if (needsMirroring()) { - padding.set(mPadding.right, mPadding.top, mPadding.left, mPadding.bottom); - } else { - padding.set(mPadding); + final Rect scaledPadding = mPadding; + if (scaledPadding != null) { + if (needsMirroring()) { + padding.set(scaledPadding.right, scaledPadding.top, + scaledPadding.left, scaledPadding.bottom); + } else { + padding.set(scaledPadding); + } + return (padding.left | padding.top | padding.right | padding.bottom) != 0; } - return (padding.left | padding.top | padding.right | padding.bottom) != 0; + return false; } /** @@ -408,70 +406,195 @@ public class NinePatchDrawable extends Drawable { } @Override - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) + public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { - super.inflate(r, parser, attrs); + super.inflate(r, parser, attrs, theme); + + final TypedArray a = obtainAttributes( + r, theme, attrs, R.styleable.NinePatchDrawable); + inflateStateFromTypedArray(a); + a.recycle(); + } + + /** + * Initializes the constant state from the values in the typed array. + */ + private void inflateStateFromTypedArray(TypedArray a) throws XmlPullParserException { + final Resources r = a.getResources(); + final NinePatchState ninePatchState = mNinePatchState; - TypedArray a = r.obtainAttributes(attrs, com.android.internal.R.styleable.NinePatchDrawable); + // Extract the theme attributes, if any. + final int[] themeAttrs = a.extractThemeAttrs(); + ninePatchState.mThemeAttrs = themeAttrs; - final int id = a.getResourceId(com.android.internal.R.styleable.NinePatchDrawable_src, 0); - if (id == 0) { - throw new XmlPullParserException(parser.getPositionDescription() + - ": <nine-patch> requires a valid src attribute"); + if (themeAttrs == null || themeAttrs[R.styleable.NinePatchDrawable_dither] == 0) { + final boolean dither = a.getBoolean( + R.styleable.NinePatchDrawable_dither, DEFAULT_DITHER); + ninePatchState.mDither = dither; } - final boolean dither = a.getBoolean( - com.android.internal.R.styleable.NinePatchDrawable_dither, DEFAULT_DITHER); - final BitmapFactory.Options options = new BitmapFactory.Options(); - if (dither) { - options.inDither = false; + if (themeAttrs == null || themeAttrs[R.styleable.NinePatchDrawable_src] == 0) { + final int id = a.getResourceId(R.styleable.NinePatchDrawable_src, 0); + if (id == 0) { + throw new XmlPullParserException(a.getPositionDescription() + + ": <nine-patch> requires a valid src attribute"); + } + + final BitmapFactory.Options options = new BitmapFactory.Options(); + options.inDither = !ninePatchState.mDither; + options.inScreenDensity = r.getDisplayMetrics().noncompatDensityDpi; + + final Rect padding = new Rect(); + final Rect opticalInsets = new Rect(); + Bitmap bitmap = null; + + try { + final TypedValue value = new TypedValue(); + final InputStream is = r.openRawResource(id, value); + + bitmap = BitmapFactory.decodeResourceStream(r, value, is, padding, options); + + is.close(); + } catch (IOException e) { + // Ignore + } + + if (bitmap == null) { + throw new XmlPullParserException(a.getPositionDescription() + + ": <nine-patch> requires a valid src attribute"); + } else if (bitmap.getNinePatchChunk() == null) { + throw new XmlPullParserException(a.getPositionDescription() + + ": <nine-patch> requires a valid 9-patch source image"); + } + + final NinePatch ninePatch = new NinePatch(bitmap, bitmap.getNinePatchChunk()); + ninePatchState.mNinePatch = ninePatch; + ninePatchState.mPadding = padding; + ninePatchState.mOpticalInsets = Insets.of(opticalInsets); } - options.inScreenDensity = r.getDisplayMetrics().noncompatDensityDpi; - final Rect padding = new Rect(); - final Rect opticalInsets = new Rect(); - Bitmap bitmap = null; + if (themeAttrs == null || themeAttrs[R.styleable.NinePatchDrawable_autoMirrored] == 0) { + final boolean autoMirrored = a.getBoolean( + R.styleable.NinePatchDrawable_autoMirrored, false); + ninePatchState.mAutoMirrored = autoMirrored; + } - try { - final TypedValue value = new TypedValue(); - final InputStream is = r.openRawResource(id, value); + if (themeAttrs == null || themeAttrs[R.styleable.NinePatchDrawable_tintMode] == 0) { + final int tintModeValue = a.getInt(R.styleable.NinePatchDrawable_tintMode, -1); + ninePatchState.mTintMode = Drawable.parseTintMode(tintModeValue, Mode.SRC_IN); + } - bitmap = BitmapFactory.decodeResourceStream(r, value, is, padding, options); + if (themeAttrs == null || themeAttrs[R.styleable.NinePatchDrawable_tint] == 0) { + ninePatchState.mTint = a.getColorStateList(R.styleable.NinePatchDrawable_tint); + if (ninePatchState.mTint != null) { + final int color = ninePatchState.mTint.getColorForState(getState(), 0); + mTintFilter = new PorterDuffColorFilter(color, ninePatchState.mTintMode); + } + } - is.close(); - } catch (IOException e) { - // Ignore + // Apply the constant state to the paint. + initializeWithState(ninePatchState, r); + + // Push density applied by setNinePatchState into state. + ninePatchState.mTargetDensity = mTargetDensity; + } + + @Override + public void applyTheme(Theme t) { + super.applyTheme(t); + + final NinePatchState state = mNinePatchState; + if (state == null) { + throw new RuntimeException("Can't apply theme to <nine-patch> with no constant state"); } - if (bitmap == null) { - throw new XmlPullParserException(parser.getPositionDescription() + - ": <nine-patch> requires a valid src attribute"); - } else if (bitmap.getNinePatchChunk() == null) { - throw new XmlPullParserException(parser.getPositionDescription() + - ": <nine-patch> requires a valid 9-patch source image"); + final int[] themeAttrs = state.mThemeAttrs; + if (themeAttrs != null) { + final TypedArray a = t.resolveAttributes( + themeAttrs, R.styleable.NinePatchDrawable, 0, 0); + updateStateFromTypedArray(a); + a.recycle(); } + } - final boolean automirrored = a.getBoolean( - com.android.internal.R.styleable.NinePatchDrawable_autoMirrored, false); - final NinePatchState ninePatchState = new NinePatchState( - new NinePatch(bitmap, bitmap.getNinePatchChunk()), padding, opticalInsets, dither, - automirrored); + /** + * Updates the constant state from the values in the typed array. + */ + private void updateStateFromTypedArray(TypedArray a) { + final Resources r = a.getResources(); + final NinePatchState state = mNinePatchState; - final int tintModeValue = a.getInt( - com.android.internal.R.styleable.NinePatchDrawable_tintMode, -1); - ninePatchState.mTintMode = Drawable.parseTintMode(tintModeValue, Mode.SRC_IN); - ninePatchState.mTint = a.getColorStateList( - com.android.internal.R.styleable.NinePatchDrawable_tint); - if (ninePatchState.mTint != null) { - final int color = ninePatchState.mTint.getColorForState(getState(), 0); - mTintFilter = new PorterDuffColorFilter(color, ninePatchState.mTintMode); + if (a.hasValue(R.styleable.NinePatchDrawable_dither)) { + state.mDither = a.getBoolean(R.styleable.NinePatchDrawable_dither, DEFAULT_DITHER); } - setNinePatchState(ninePatchState, r); + if (a.hasValue(R.styleable.NinePatchDrawable_autoMirrored)) { + state.mAutoMirrored = a.getBoolean(R.styleable.NinePatchDrawable_autoMirrored, false); + } - mNinePatchState.mTargetDensity = mTargetDensity; + if (a.hasValue(R.styleable.NinePatchDrawable_src)) { + final int id = a.getResourceId(R.styleable.NinePatchDrawable_src, 0); + if (id == 0) { + throw new RuntimeException(a.getPositionDescription() + + ": <nine-patch> requires a valid src attribute"); + } - a.recycle(); + final BitmapFactory.Options options = new BitmapFactory.Options(); + options.inDither = !state.mDither; + options.inScreenDensity = r.getDisplayMetrics().noncompatDensityDpi; + + final Rect padding = new Rect(); + final Rect opticalInsets = new Rect(); + Bitmap bitmap = null; + + try { + final TypedValue value = new TypedValue(); + final InputStream is = r.openRawResource(id, value); + + bitmap = BitmapFactory.decodeResourceStream(r, value, is, padding, options); + + is.close(); + } catch (IOException e) { + // Ignore + } + + if (bitmap == null) { + throw new RuntimeException(a.getPositionDescription() + + ": <nine-patch> requires a valid src attribute"); + } else if (bitmap.getNinePatchChunk() == null) { + throw new RuntimeException(a.getPositionDescription() + + ": <nine-patch> requires a valid 9-patch source image"); + } + + state.mNinePatch = new NinePatch(bitmap, bitmap.getNinePatchChunk()); + state.mPadding = padding; + state.mOpticalInsets = Insets.of(opticalInsets); + } + + if (a.hasValue(R.styleable.NinePatchDrawable_tintMode)) { + final int modeValue = a.getInt(R.styleable.NinePatchDrawable_tintMode, -1); + state.mTintMode = Drawable.parseTintMode(modeValue, Mode.SRC_IN); + } + + if (a.hasValue(R.styleable.NinePatchDrawable_tint)) { + final ColorStateList tint = a.getColorStateList(R.styleable.NinePatchDrawable_tint); + if (tint != null) { + state.mTint = tint; + final int color = tint.getColorForState(getState(), 0); + mTintFilter = new PorterDuffColorFilter(color, state.mTintMode); + } + } + + // Apply the constant state to the paint. + initializeWithState(state, r); + + // Push density applied by setNinePatchState into state. + state.mTargetDensity = mTargetDensity; + } + + @Override + public boolean canApplyTheme() { + return mNinePatchState != null && mNinePatchState.mThemeAttrs != null; } public Paint getPaint() { @@ -568,9 +691,14 @@ public class NinePatchDrawable extends Drawable { Rect mPadding; Insets mOpticalInsets; boolean mDither; + int[] mThemeAttrs; int mChangingConfigurations; int mTargetDensity = DisplayMetrics.DENSITY_DEFAULT; boolean mAutoMirrored; + + NinePatchState() { + // Empty constructor. + } NinePatchState(NinePatch ninePatch, Rect padding) { this(ninePatch, padding, new Rect(), DEFAULT_DITHER, false); @@ -581,7 +709,7 @@ public class NinePatchDrawable extends Drawable { } NinePatchState(NinePatch ninePatch, Rect rect, Rect opticalInsets, boolean dither, - boolean autoMirror) { + boolean autoMirror) { mNinePatch = ninePatch; mPadding = rect; mOpticalInsets = Insets.of(opticalInsets); @@ -596,6 +724,7 @@ public class NinePatchDrawable extends Drawable { mNinePatch = state.mNinePatch; mTint = state.mTint; mTintMode = state.mTintMode; + mThemeAttrs = state.mThemeAttrs; mPadding = state.mPadding; mOpticalInsets = state.mOpticalInsets; mDither = state.mDither; @@ -605,18 +734,28 @@ public class NinePatchDrawable extends Drawable { } @Override + public boolean canApplyTheme() { + return mThemeAttrs != null; + } + + @Override public Bitmap getBitmap() { return mNinePatch.getBitmap(); } @Override public Drawable newDrawable() { - return new NinePatchDrawable(this, null); + return new NinePatchDrawable(this, null, null); } @Override public Drawable newDrawable(Resources res) { - return new NinePatchDrawable(this, res); + return new NinePatchDrawable(this, res, null); + } + + @Override + public Drawable newDrawable(Resources res, Theme theme) { + return new NinePatchDrawable(this, res, theme); } @Override @@ -625,7 +764,40 @@ public class NinePatchDrawable extends Drawable { } } - private NinePatchDrawable(NinePatchState state, Resources res) { - setNinePatchState(state, res); + private NinePatchDrawable(NinePatchState state, Resources res, Theme theme) { + if (theme != null && state.canApplyTheme()) { + mNinePatchState = new NinePatchState(state); + applyTheme(theme); + } else { + mNinePatchState = state; + } + + initializeWithState(state, res); + } + + /** + * Initializes local dynamic properties from state. + */ + private void initializeWithState(NinePatchState state, Resources res) { + if (res != null) { + mTargetDensity = res.getDisplayMetrics().densityDpi; + } else { + mTargetDensity = state.mTargetDensity; + } + + // If we can, avoid calling any methods that initialize Paint. + if (state.mDither != DEFAULT_DITHER) { + setDither(state.mDither); + } + + if (state.mTint != null) { + final int color = state.mTint.getColorForState(getState(), 0); + mTintFilter = new PorterDuffColorFilter(color, state.mTintMode); + } + + final Rect statePadding = state.mPadding; + mPadding = statePadding != null ? new Rect(statePadding) : null; + + setNinePatch(state.mNinePatch); } } diff --git a/graphics/java/android/graphics/drawable/RevealDrawable.java b/graphics/java/android/graphics/drawable/RevealDrawable.java index 91de638..2f96fe4 100644 --- a/graphics/java/android/graphics/drawable/RevealDrawable.java +++ b/graphics/java/android/graphics/drawable/RevealDrawable.java @@ -17,6 +17,7 @@ package android.graphics.drawable; import android.content.res.Resources; +import android.content.res.Resources.Theme; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.PorterDuff.Mode; @@ -88,11 +89,15 @@ public class RevealDrawable extends LayerDrawable { * @see #RevealDrawable(Drawable[]) */ RevealDrawable() { - this(new RevealState(null, null, null), (Resources) null); + this(new RevealState(null, null, null), (Resources) null, null); } private RevealDrawable(RevealState state, Resources res) { - super(state, res); + super(state, res, null); + } + + private RevealDrawable(RevealState state, Resources res, Theme theme) { + super(state, res, theme); } private RevealDrawable(RevealState state, Drawable[] layers) { @@ -100,9 +105,9 @@ public class RevealDrawable extends LayerDrawable { } @Override - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) + public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { - super.inflate(r, parser, attrs); + super.inflate(r, parser, attrs, theme); setTargetDensity(r.getDisplayMetrics()); setPaddingMode(PADDING_MODE_STACK); @@ -303,5 +308,10 @@ public class RevealDrawable extends LayerDrawable { public Drawable newDrawable(Resources res) { return new RevealDrawable(this, res); } + + @Override + public Drawable newDrawable(Resources res, Theme theme) { + return new RevealDrawable(this, res, theme); + } } } diff --git a/graphics/java/android/graphics/drawable/RotateDrawable.java b/graphics/java/android/graphics/drawable/RotateDrawable.java index 630dc2e..edf1091 100644 --- a/graphics/java/android/graphics/drawable/RotateDrawable.java +++ b/graphics/java/android/graphics/drawable/RotateDrawable.java @@ -24,6 +24,7 @@ import android.graphics.ColorFilter; import android.graphics.Rect; import android.content.res.Resources; import android.content.res.TypedArray; +import android.content.res.Resources.Theme; import android.util.TypedValue; import android.util.AttributeSet; import android.util.Log; @@ -358,7 +359,7 @@ public class RotateDrawable extends Drawable implements Drawable.Callback { } @Override - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) + public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { final TypedArray a = r.obtainAttributes(attrs, com.android.internal.R.styleable.RotateDrawable); @@ -411,7 +412,7 @@ public class RotateDrawable extends Drawable implements Drawable.Callback { continue; } - if ((drawable = Drawable.createFromXmlInner(r, parser, attrs)) == null) { + if ((drawable = Drawable.createFromXmlInnerThemed(r, parser, attrs, theme)) == null) { Log.w("drawable", "Bad element under <rotate>: " + parser .getName()); } diff --git a/graphics/java/android/graphics/drawable/ScaleDrawable.java b/graphics/java/android/graphics/drawable/ScaleDrawable.java index ec6b2c1..4c4d9af 100644 --- a/graphics/java/android/graphics/drawable/ScaleDrawable.java +++ b/graphics/java/android/graphics/drawable/ScaleDrawable.java @@ -21,6 +21,7 @@ import org.xmlpull.v1.XmlPullParserException; import android.content.res.Resources; import android.content.res.TypedArray; +import android.content.res.Resources.Theme; import android.graphics.*; import android.view.Gravity; import android.util.AttributeSet; @@ -84,9 +85,9 @@ public class ScaleDrawable extends Drawable implements Drawable.Callback { } @Override - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) + public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { - super.inflate(r, parser, attrs); + super.inflate(r, parser, attrs, theme); int type; @@ -107,7 +108,7 @@ public class ScaleDrawable extends Drawable implements Drawable.Callback { if (type != XmlPullParser.START_TAG) { continue; } - dr = Drawable.createFromXmlInner(r, parser, attrs); + dr = Drawable.createFromXmlInnerThemed(r, parser, attrs, theme); } if (dr == null) { diff --git a/graphics/java/android/graphics/drawable/ShapeDrawable.java b/graphics/java/android/graphics/drawable/ShapeDrawable.java index 16de9f3..96309f9 100644 --- a/graphics/java/android/graphics/drawable/ShapeDrawable.java +++ b/graphics/java/android/graphics/drawable/ShapeDrawable.java @@ -28,6 +28,7 @@ import android.graphics.PorterDuffColorFilter; import android.graphics.Rect; import android.graphics.Shader; import android.graphics.drawable.shapes.Shape; +import android.content.res.Resources.Theme; import android.util.AttributeSet; import org.xmlpull.v1.XmlPullParser; @@ -443,9 +444,9 @@ public class ShapeDrawable extends Drawable { } @Override - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) + public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { - super.inflate(r, parser, attrs); + super.inflate(r, parser, attrs, theme); TypedArray a = r.obtainAttributes(attrs, com.android.internal.R.styleable.ShapeDrawable); diff --git a/graphics/java/android/graphics/drawable/StateListDrawable.java b/graphics/java/android/graphics/drawable/StateListDrawable.java index 48d66b7..271af2b 100644 --- a/graphics/java/android/graphics/drawable/StateListDrawable.java +++ b/graphics/java/android/graphics/drawable/StateListDrawable.java @@ -24,6 +24,7 @@ import java.util.Arrays; import android.content.res.Resources; import android.content.res.TypedArray; +import android.content.res.Resources.Theme; import android.util.AttributeSet; import android.util.StateSet; @@ -110,8 +111,7 @@ public class StateListDrawable extends DrawableContainer { } @Override - public void inflate(Resources r, XmlPullParser parser, - AttributeSet attrs) + public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { TypedArray a = r.obtainAttributes(attrs, @@ -183,7 +183,7 @@ public class StateListDrawable extends DrawableContainer { + ": <item> tag requires a 'drawable' attribute or " + "child tag defining a drawable"); } - dr = Drawable.createFromXmlInner(r, parser, attrs); + dr = Drawable.createFromXmlInnerThemed(r, parser, attrs, theme); } mStateListState.addStateSet(states, dr); diff --git a/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java b/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java index f4f545c..ef91494 100644 --- a/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java +++ b/graphics/java/android/graphics/drawable/TouchFeedbackDrawable.java @@ -18,6 +18,7 @@ package android.graphics.drawable; import android.content.res.ColorStateList; import android.content.res.Resources; +import android.content.res.Resources.Theme; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Color; @@ -78,20 +79,10 @@ public class TouchFeedbackDrawable extends DrawableWrapper { /** The drawable to use as the mask. */ private Drawable mMask; - /* package */TouchFeedbackDrawable() { - this(null, null); + TouchFeedbackDrawable() { + this(new TouchFeedbackState(null), null, null); } - TouchFeedbackDrawable(TouchFeedbackState state, Resources res) { - mState = new TouchFeedbackState(state); - - setConstantState(mState, res); - - if (res != null) { - mDensity = res.getDisplayMetrics().density; - } - } - private void setConstantState(TouchFeedbackState wrapperState, Resources res) { super.setConstantState(wrapperState, res); @@ -103,6 +94,10 @@ public class TouchFeedbackDrawable extends DrawableWrapper { } else { mMask = wrapperState.mMaskState.newDrawable(); } + + if (res != null) { + mDensity = res.getDisplayMetrics().density; + } } @Override @@ -152,60 +147,31 @@ public class TouchFeedbackDrawable extends DrawableWrapper { } @Override - public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) + public void inflate(Resources r, XmlPullParser parser, AttributeSet attrs, Theme theme) throws XmlPullParserException, IOException { - super.inflate(r, parser, attrs); - - final TypedArray a = r.obtainAttributes(attrs, R.styleable.TouchFeedbackDrawable); + super.inflate(r, parser, attrs, theme); - mState.mTint = a.getColorStateList(R.styleable.TouchFeedbackDrawable_tint); - mState.mTintMode = Drawable.parseTintMode( - a.getInt(R.styleable.TouchFeedbackDrawable_tintMode, -1), Mode.SRC_ATOP); - mState.mPinned = a.getBoolean(R.styleable.TouchFeedbackDrawable_pinned, false); - - if (mState.mTint == null) { - throw new XmlPullParserException(parser.getPositionDescription() - + ": <touch-feedback> tag requires a 'tint' attribute"); - } - - Drawable mask = a.getDrawable(R.styleable.TouchFeedbackDrawable_mask); - final int drawableRes = a.getResourceId(R.styleable.TouchFeedbackDrawable_drawable, 0); + final TypedArray a = obtainAttributes( + r, theme, attrs, R.styleable.TouchFeedbackDrawable); + inflateStateFromTypedArray(r, a); a.recycle(); + + inflateChildElements(r, parser, attrs, theme); - final Drawable dr; - if (drawableRes != 0) { - dr = r.getDrawable(drawableRes); - } else { - int type; - while ((type = parser.next()) == XmlPullParser.TEXT) { - // Find the next non-text element. - } - - if (type == XmlPullParser.START_TAG) { - dr = Drawable.createFromXmlInner(r, parser, attrs); - } else { - dr = null; - } - } - - // If no mask is set, implicitly use the lower drawable. - if (mask == null) { - mask = dr; + setTargetDensity(r.getDisplayMetrics()); + } + + private void inflateChildElements(Resources r, XmlPullParser parser, AttributeSet attrs, + Theme theme) throws XmlPullParserException, IOException { + int type; + while ((type = parser.next()) == XmlPullParser.TEXT) { + // Find the next non-text element. } - // If neither a mask not a bottom layer was specified, assume we're - // projecting onto a parent surface. - mState.mProjected = mask == null && dr == null; - - if (dr != null) { + if (type == XmlPullParser.START_TAG) { + final Drawable dr = Drawable.createFromXmlInner(r, parser, attrs); setDrawable(dr, r); - } else { - // For now at least, we MUST have a wrapped drawable. - setDrawable(new ColorDrawable(Color.TRANSPARENT), r); } - - setMaskDrawable(mask, r); - setTargetDensity(r.getDisplayMetrics()); } /** @@ -228,6 +194,62 @@ public class TouchFeedbackDrawable extends DrawableWrapper { } /** + * Initializes the constant state from the values in the typed array. + */ + private void inflateStateFromTypedArray(Resources r, TypedArray a) { + final TouchFeedbackState state = mState; + + // Extract the theme attributes, if any. + final int[] themeAttrs = a.extractThemeAttrs(); + state.mThemeAttrs = themeAttrs; + + if (themeAttrs == null || themeAttrs[R.styleable.TouchFeedbackDrawable_tint] == 0) { + mState.mTint = a.getColorStateList(R.styleable.TouchFeedbackDrawable_tint); + + if (mState.mTint == null) { + throw new RuntimeException("<touch-feedback> tag requires a 'tint' attribute"); + } + } + + if (themeAttrs == null || themeAttrs[R.styleable.TouchFeedbackDrawable_tintMode] == 0) { + mState.mTintMode = Drawable.parseTintMode( + a.getInt(R.styleable.TouchFeedbackDrawable_tintMode, -1), Mode.SRC_ATOP); + } + + if (themeAttrs == null || themeAttrs[R.styleable.TouchFeedbackDrawable_pinned] == 0) { + mState.mPinned = a.getBoolean(R.styleable.TouchFeedbackDrawable_pinned, false); + } + + Drawable mask = mMask; + if (themeAttrs == null || themeAttrs[R.styleable.TouchFeedbackDrawable_mask] == 0) { + mask = a.getDrawable(R.styleable.TouchFeedbackDrawable_mask); + } + + Drawable dr = super.getDrawable(); + if (themeAttrs == null || themeAttrs[R.styleable.TouchFeedbackDrawable_drawable] == 0) { + final int drawableRes = a.getResourceId(R.styleable.TouchFeedbackDrawable_drawable, 0); + if (drawableRes != 0) { + dr = r.getDrawable(drawableRes); + } + } + + // If neither a mask not a bottom layer was specified, assume we're + // projecting onto a parent surface. + mState.mProjected = mask == null && dr == null; + + if (dr != null) { + setDrawable(dr, r); + } else { + // For now at least, we MUST have a wrapped drawable. + setDrawable(new ColorDrawable(Color.TRANSPARENT), r); + } + + if (mask != null) { + setMaskDrawable(mask, r); + } + } + + /** * Set the density at which this drawable will be rendered. * * @param metrics The display metrics for this drawable. @@ -239,6 +261,78 @@ public class TouchFeedbackDrawable extends DrawableWrapper { } } + @Override + public void applyTheme(Theme t) { + super.applyTheme(t); + + final TouchFeedbackState state = mState; + if (state == null) { + throw new RuntimeException( + "Can't apply theme to <touch-feedback> with no constant state"); + } + + final int[] themeAttrs = state.mThemeAttrs; + if (themeAttrs != null) { + final TypedArray a = t.resolveAttributes( + themeAttrs, R.styleable.TouchFeedbackDrawable, 0, 0); + updateStateFromTypedArray(a); + a.recycle(); + } + } + + /** + * Updates the constant state from the values in the typed array. + */ + private void updateStateFromTypedArray(TypedArray a) { + final TouchFeedbackState state = mState; + + if (a.hasValue(R.styleable.TouchFeedbackDrawable_tint)) { + state.mTint = a.getColorStateList(R.styleable.TouchFeedbackDrawable_tint); + } + + if (a.hasValue(R.styleable.TouchFeedbackDrawable_tintMode)) { + mState.mTintMode = Drawable.parseTintMode( + a.getInt(R.styleable.TouchFeedbackDrawable_tintMode, -1), Mode.SRC_ATOP); + } + + if (a.hasValue(R.styleable.TouchFeedbackDrawable_pinned)) { + mState.mPinned = a.getBoolean(R.styleable.TouchFeedbackDrawable_pinned, false); + } + + Drawable mask = mMask; + if (a.hasValue(R.styleable.TouchFeedbackDrawable_mask)) { + mask = a.getDrawable(R.styleable.TouchFeedbackDrawable_mask); + } + + Drawable dr = super.getDrawable(); + if (a.hasValue(R.styleable.TouchFeedbackDrawable_drawable)) { + final int drawableRes = a.getResourceId(R.styleable.TouchFeedbackDrawable_drawable, 0); + if (drawableRes != 0) { + dr = a.getResources().getDrawable(drawableRes); + } + } + + // If neither a mask not a bottom layer was specified, assume we're + // projecting onto a parent surface. + mState.mProjected = mask == null && dr == null; + + if (dr != null) { + setDrawable(dr, a.getResources()); + } else { + // For now at least, we MUST have a wrapped drawable. + setDrawable(new ColorDrawable(Color.TRANSPARENT), a.getResources()); + } + + if (mask != null) { + setMaskDrawable(mask, a.getResources()); + } + } + + @Override + public boolean canApplyTheme() { + return mState != null && mState.mThemeAttrs != null; + } + /** * @hide until hotspot APIs are finalized */ @@ -333,7 +427,7 @@ public class TouchFeedbackDrawable extends DrawableWrapper { if (mAnimationRunnable == null) { mAnimationRunnable = new Runnable() { - @Override + @Override public void run() { mAnimating = false; scheduleAnimation(); @@ -357,7 +451,7 @@ public class TouchFeedbackDrawable extends DrawableWrapper { } final ArrayList<Ripple> activeRipples = mActiveRipples; - final Drawable mask = mMask; + final Drawable mask = mMask == null && !mState.mProjected ? getDrawable() : null; final Rect bounds = mask == null ? null : mask.getBounds(); // Draw ripples into a layer that merges using SRC_IN. @@ -450,6 +544,7 @@ public class TouchFeedbackDrawable extends DrawableWrapper { } static class TouchFeedbackState extends WrapperState { + int[] mThemeAttrs; ConstantState mMaskState; ColorStateList mTint; Mode mTintMode; @@ -460,6 +555,7 @@ public class TouchFeedbackDrawable extends DrawableWrapper { super(orig); if (orig != null) { + mThemeAttrs = orig.mThemeAttrs; mTint = orig.mTint; mTintMode = orig.mTintMode; mMaskState = orig.mMaskState; @@ -469,13 +565,34 @@ public class TouchFeedbackDrawable extends DrawableWrapper { } @Override + public boolean canApplyTheme() { + return mThemeAttrs != null; + } + + @Override public Drawable newDrawable() { - return new TouchFeedbackDrawable(this, null); + return new TouchFeedbackDrawable(this, null, null); } @Override public Drawable newDrawable(Resources res) { - return new TouchFeedbackDrawable(this, res); + return new TouchFeedbackDrawable(this, res, null); + } + + @Override + public Drawable newDrawable(Resources res, Theme theme) { + return new TouchFeedbackDrawable(this, res, theme); } } + + private TouchFeedbackDrawable(TouchFeedbackState state, Resources res, Theme theme) { + if (theme != null && state.canApplyTheme()) { + mState = new TouchFeedbackState(state); + applyTheme(theme); + } else { + mState = state; + } + + setConstantState(state, res); + } } diff --git a/graphics/java/android/graphics/drawable/TransitionDrawable.java b/graphics/java/android/graphics/drawable/TransitionDrawable.java index 483fa56..622e90b 100644 --- a/graphics/java/android/graphics/drawable/TransitionDrawable.java +++ b/graphics/java/android/graphics/drawable/TransitionDrawable.java @@ -17,6 +17,7 @@ package android.graphics.drawable; import android.content.res.Resources; +import android.content.res.Resources.Theme; import android.graphics.Canvas; import android.os.SystemClock; @@ -85,11 +86,11 @@ public class TransitionDrawable extends LayerDrawable implements Drawable.Callba * @see #TransitionDrawable(Drawable[]) */ TransitionDrawable() { - this(new TransitionState(null, null, null), (Resources)null); + this(new TransitionState(null, null, null), null, null); } - private TransitionDrawable(TransitionState state, Resources res) { - super(state, res); + private TransitionDrawable(TransitionState state, Resources res, Theme theme) { + super(state, res, theme); } private TransitionDrawable(TransitionState state, Drawable[] layers) { @@ -251,12 +252,17 @@ public class TransitionDrawable extends LayerDrawable implements Drawable.Callba @Override public Drawable newDrawable() { - return new TransitionDrawable(this, (Resources)null); + return new TransitionDrawable(this, null, null); } @Override public Drawable newDrawable(Resources res) { - return new TransitionDrawable(this, res); + return new TransitionDrawable(this, res, null); + } + + @Override + public Drawable newDrawable(Resources res, Theme theme) { + return new TransitionDrawable(this, res, theme); } @Override diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java index 977c2e7..a65f677 100644 --- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java +++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java @@ -3307,8 +3307,9 @@ public class PhoneWindow extends Window implements MenuBuilder.Callback { private Drawable loadImageURI(Uri uri) { try { - return Drawable.createFromStream( - getContext().getContentResolver().openInputStream(uri), null); + final Context context = getContext(); + return Drawable.createFromStreamThemed( + context.getContentResolver().openInputStream(uri), null, context.getTheme()); } catch (Exception e) { Log.w(TAG, "Unable to open content: " + uri); } |