diff options
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); } |