diff options
author | Alan Viverette <alanv@google.com> | 2015-01-05 14:59:19 -0800 |
---|---|---|
committer | Alan Viverette <alanv@google.com> | 2015-01-05 14:59:19 -0800 |
commit | 45c4bbbbce6bbad50a033efcba7948a23f1f117a (patch) | |
tree | 9041d660953bcebaa302424abc27497867ccf954 /core/java/android/content/res | |
parent | 4ae0d9045f43f87354513e3d925fcea610c77fea (diff) | |
download | frameworks_base-45c4bbbbce6bbad50a033efcba7948a23f1f117a.zip frameworks_base-45c4bbbbce6bbad50a033efcba7948a23f1f117a.tar.gz frameworks_base-45c4bbbbce6bbad50a033efcba7948a23f1f117a.tar.bz2 |
Allow use of theme attributes in color state lists
BUG: 17384842
Change-Id: Ibdc413acbd00e37b908432abd55f6521c22b8fc9
Diffstat (limited to 'core/java/android/content/res')
-rw-r--r-- | core/java/android/content/res/ColorStateList.java | 400 | ||||
-rw-r--r-- | core/java/android/content/res/Resources.java | 261 | ||||
-rw-r--r-- | core/java/android/content/res/TypedArray.java | 33 |
3 files changed, 499 insertions, 195 deletions
diff --git a/core/java/android/content/res/ColorStateList.java b/core/java/android/content/res/ColorStateList.java index 68a39d3..b42d8bc 100644 --- a/core/java/android/content/res/ColorStateList.java +++ b/core/java/android/content/res/ColorStateList.java @@ -16,8 +16,12 @@ package android.content.res; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.content.res.Resources.Theme; import android.graphics.Color; +import com.android.internal.R; import com.android.internal.util.ArrayUtils; import com.android.internal.util.GrowingArrayUtils; @@ -25,6 +29,7 @@ import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import android.util.AttributeSet; +import android.util.Log; import android.util.MathUtils; import android.util.SparseArray; import android.util.StateSet; @@ -64,15 +69,23 @@ import java.util.Arrays; * List Resource</a>.</p> */ public class ColorStateList implements Parcelable { - private int[][] mStateSpecs; // must be parallel to mColors - private int[] mColors; // must be parallel to mStateSpecs - private int mDefaultColor = 0xffff0000; - + private static final String TAG = "ColorStateList"; + private static final int DEFAULT_COLOR = Color.RED; private static final int[][] EMPTY = new int[][] { new int[0] }; private static final SparseArray<WeakReference<ColorStateList>> sCache = new SparseArray<WeakReference<ColorStateList>>(); - private ColorStateList() { } + private int[][] mThemeAttrs; + private int mChangingConfigurations; + + private int[][] mStateSpecs; + private int[] mColors; + private int mDefaultColor; + private boolean mIsOpaque; + + private ColorStateList() { + // Not publicly instantiable. + } /** * Creates a ColorStateList that returns the specified mapping from @@ -82,53 +95,102 @@ public class ColorStateList implements Parcelable { mStateSpecs = states; mColors = colors; - if (states.length > 0) { - mDefaultColor = colors[0]; - - for (int i = 0; i < states.length; i++) { - if (states[i].length == 0) { - mDefaultColor = colors[i]; - } - } - } + onColorsChanged(); } /** - * Creates or retrieves a ColorStateList that always returns a single color. + * @return A ColorStateList containing a single color. */ + @NonNull public static ColorStateList valueOf(int color) { - // TODO: should we collect these eventually? synchronized (sCache) { - final WeakReference<ColorStateList> ref = sCache.get(color); + final int index = sCache.indexOfKey(color); + if (index >= 0) { + final ColorStateList cached = sCache.valueAt(index).get(); + if (cached != null) { + return cached; + } - ColorStateList csl = ref != null ? ref.get() : null; - if (csl != null) { - return csl; + // Prune missing entry. + sCache.removeAt(index); } - csl = new ColorStateList(EMPTY, new int[] { color }); + // Prune the cache before adding new items. + final int N = sCache.size(); + for (int i = N - 1; i >= 0; i--) { + if (sCache.valueAt(i).get() == null) { + sCache.removeAt(i); + } + } + + final ColorStateList csl = new ColorStateList(EMPTY, new int[] { color }); sCache.put(color, new WeakReference<ColorStateList>(csl)); return csl; } } /** - * Create a ColorStateList from an XML document, given a set of {@link Resources}. + * Creates a ColorStateList with the same properties as another + * ColorStateList. + * <p> + * The properties of the new ColorStateList can be modified without + * affecting the source ColorStateList. + * + * @param orig the source color state list */ + private ColorStateList(ColorStateList orig) { + if (orig != null) { + mStateSpecs = orig.mStateSpecs; + mDefaultColor = orig.mDefaultColor; + mIsOpaque = orig.mIsOpaque; + + // Deep copy, this may change due to theming. + mColors = orig.mColors.clone(); + } + } + + /** + * Creates a ColorStateList from an XML document. + * + * @param r Resources against which the ColorStateList should be inflated. + * @param parser Parser for the XML document defining the ColorStateList. + * @return A new color state list. + * + * @deprecated Use #createFromXml(Resources, XmlPullParser parser, Theme) + */ + @NonNull + @Deprecated public static ColorStateList createFromXml(Resources r, XmlPullParser parser) throws XmlPullParserException, IOException { + return createFromXml(r, parser, null); + } + + /** + * Creates a ColorStateList from an XML document using given a set of + * {@link Resources} and a {@link Theme}. + * + * @param r Resources against which the ColorStateList should be inflated. + * @param parser Parser for the XML document defining the ColorStateList. + * @param theme Optional theme to apply to the color state list, may be + * {@code null}. + * @return A new color state list. + */ + @NonNull + public static ColorStateList createFromXml(@NonNull Resources r, @NonNull XmlPullParser parser, + @Nullable Theme theme) throws XmlPullParserException, IOException { final AttributeSet attrs = Xml.asAttributeSet(parser); int type; - while ((type=parser.next()) != XmlPullParser.START_TAG + while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) { + // Seek parser to start tag. } if (type != XmlPullParser.START_TAG) { throw new XmlPullParserException("No start tag found"); } - return createFromXmlInner(r, parser, attrs); + return createFromXmlInner(r, parser, attrs, theme); } /** @@ -136,28 +198,31 @@ public class ColorStateList implements Parcelable { * tag in an XML document, tries to create a ColorStateList from that tag. * * @throws XmlPullParserException if the current tag is not <selector> - * @return A color state list for the current tag. + * @return A new color state list for the current tag. */ - private static ColorStateList createFromXmlInner(Resources r, XmlPullParser parser, - AttributeSet attrs) throws XmlPullParserException, IOException { - final ColorStateList colorStateList; + @NonNull + private static ColorStateList createFromXmlInner(@NonNull Resources r, + @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme) + throws XmlPullParserException, IOException { final String name = parser.getName(); - if (name.equals("selector")) { - colorStateList = new ColorStateList(); - } else { + if (!name.equals("selector")) { throw new XmlPullParserException( - parser.getPositionDescription() + ": invalid drawable tag " + name); + parser.getPositionDescription() + ": invalid color state list tag " + name); } - colorStateList.inflate(r, parser, attrs); + final ColorStateList colorStateList = new ColorStateList(); + colorStateList.inflate(r, parser, attrs, theme); return colorStateList; } /** - * Creates a new ColorStateList that has the same states and - * colors as this one but where each color has the specified alpha value - * (0-255). + * Creates a new ColorStateList that has the same states and colors as this + * one but where each color has the specified alpha value (0-255). + * + * @param alpha The new alpha channel value (0-255). + * @return A new color state list. */ + @NonNull public ColorStateList withAlpha(int alpha) { final int[] colors = new int[mColors.length]; final int len = colors.length; @@ -171,88 +236,154 @@ public class ColorStateList implements Parcelable { /** * Fill in this object based on the contents of an XML "selector" element. */ - private void inflate(Resources r, XmlPullParser parser, AttributeSet attrs) + private void inflate(@NonNull Resources r, @NonNull XmlPullParser parser, + @NonNull AttributeSet attrs, @Nullable Theme theme) throws XmlPullParserException, IOException { - int type; - final int innerDepth = parser.getDepth()+1; int depth; + int type; + + int changingConfigurations = 0; + int defaultColor = DEFAULT_COLOR; + + boolean hasUnresolvedAttrs = false; int[][] stateSpecList = ArrayUtils.newUnpaddedArray(int[].class, 20); + int[][] themeAttrsList = new int[stateSpecList.length][]; int[] colorList = new int[stateSpecList.length]; int listSize = 0; - while ((type=parser.next()) != XmlPullParser.END_DOCUMENT - && ((depth=parser.getDepth()) >= innerDepth - || type != XmlPullParser.END_TAG)) { - if (type != XmlPullParser.START_TAG) { + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) { + if (type != XmlPullParser.START_TAG || depth > innerDepth + || !parser.getName().equals("item")) { continue; } - if (depth > innerDepth || !parser.getName().equals("item")) { - continue; - } + final TypedArray a = Resources.obtainAttributes(r, theme, attrs, + R.styleable.ColorStateListItem); + final int[] themeAttrs = a.extractThemeAttrs(); + final int baseColor = a.getColor(R.styleable.ColorStateListItem_color, 0); + final float alphaMod = a.getFloat(R.styleable.ColorStateListItem_alpha, 1.0f); + + changingConfigurations |= a.getChangingConfigurations(); - int alphaRes = 0; - float alpha = 1.0f; - int colorRes = 0; - int color = 0xffff0000; - boolean haveColor = false; + a.recycle(); - int i; + // Parse all unrecognized attributes as state specifiers. int j = 0; final int numAttrs = attrs.getAttributeCount(); int[] stateSpec = new int[numAttrs]; - for (i = 0; i < numAttrs; i++) { + for (int i = 0; i < numAttrs; i++) { final int stateResId = attrs.getAttributeNameResource(i); - if (stateResId == 0) break; - if (stateResId == com.android.internal.R.attr.alpha) { - alphaRes = attrs.getAttributeResourceValue(i, 0); - if (alphaRes == 0) { - alpha = attrs.getAttributeFloatValue(i, 1.0f); - } - } else if (stateResId == com.android.internal.R.attr.color) { - colorRes = attrs.getAttributeResourceValue(i, 0); - if (colorRes == 0) { - color = attrs.getAttributeIntValue(i, color); - haveColor = true; - } - } else { - stateSpec[j++] = attrs.getAttributeBooleanValue(i, false) - ? stateResId : -stateResId; + switch (stateResId) { + case R.attr.color: + case R.attr.alpha: + // Recognized attribute, ignore. + break; + default: + stateSpec[j++] = attrs.getAttributeBooleanValue(i, false) + ? stateResId : -stateResId; } } stateSpec = StateSet.trimStateSet(stateSpec, j); - if (colorRes != 0) { - color = r.getColor(colorRes); - } else if (!haveColor) { - throw new XmlPullParserException( - parser.getPositionDescription() - + ": <item> tag requires a 'android:color' attribute."); - } - - if (alphaRes != 0) { - alpha = r.getFloat(alphaRes); - } - // Apply alpha modulation. - final int alphaMod = MathUtils.constrain((int) (Color.alpha(color) * alpha), 0, 255); - color = (color & 0xFFFFFF) | (alphaMod << 24); - + final int color = modulateColorAlpha(baseColor, alphaMod); if (listSize == 0 || stateSpec.length == 0) { - mDefaultColor = color; + defaultColor = color; + } + + if (themeAttrs != null) { + hasUnresolvedAttrs = true; } colorList = GrowingArrayUtils.append(colorList, listSize, color); + themeAttrsList = GrowingArrayUtils.append(themeAttrsList, listSize, themeAttrs); stateSpecList = GrowingArrayUtils.append(stateSpecList, listSize, stateSpec); listSize++; } + mChangingConfigurations = changingConfigurations; + mDefaultColor = defaultColor; + + if (hasUnresolvedAttrs) { + mThemeAttrs = new int[listSize][]; + System.arraycopy(themeAttrsList, 0, mThemeAttrs, 0, listSize); + } else { + mThemeAttrs = null; + } + mColors = new int[listSize]; mStateSpecs = new int[listSize][]; System.arraycopy(colorList, 0, mColors, 0, listSize); System.arraycopy(stateSpecList, 0, mStateSpecs, 0, listSize); + + onColorsChanged(); + } + + /** + * Returns whether a theme can be applied to this color state list, which + * usually indicates that the color state list has unresolved theme + * attributes. + * + * @return whether a theme can be applied to this color state list + */ + public boolean canApplyTheme() { + return mThemeAttrs != null; + } + + /** + * Applies a theme to this color state list. + * + * @param t the theme to apply + */ + public void applyTheme(Theme t) { + if (mThemeAttrs == null) { + return; + } + + boolean hasUnresolvedAttrs = false; + + final int[][] themeAttrsList = mThemeAttrs; + final int N = themeAttrsList.length; + for (int i = 0; i < N; i++) { + if (themeAttrsList[i] != null) { + final TypedArray a = t.resolveAttributes(themeAttrsList[i], + R.styleable.ColorStateListItem); + final int baseColor = a.getColor( + R.styleable.ColorStateListItem_color, mColors[i]); + final float alphaMod = a.getFloat( + R.styleable.ColorStateListItem_alpha, 1.0f); + + mColors[i] = modulateColorAlpha(baseColor, alphaMod); + mChangingConfigurations |= a.getChangingConfigurations(); + + themeAttrsList[i] = a.extractThemeAttrs(themeAttrsList[i]); + if (themeAttrsList[i] != null) { + hasUnresolvedAttrs = true; + } + + a.recycle(); + } + } + + if (!hasUnresolvedAttrs) { + mThemeAttrs = null; + } + + onColorsChanged(); + } + + private int modulateColorAlpha(int baseColor, float alphaMod) { + if (alphaMod == 1.0f) { + return baseColor; + } + + final int baseAlpha = Color.alpha(baseColor); + final int alpha = MathUtils.constrain((int) (baseAlpha * alphaMod + 0.5f), 0, 255); + final int color = (baseColor & 0xFFFFFF) | (alpha << 24); + return color; } /** @@ -275,28 +406,24 @@ public class ColorStateList implements Parcelable { * @return True if this color state list is opaque. */ public boolean isOpaque() { - final int n = mColors.length; - for (int i = 0; i < n; i++) { - if (Color.alpha(mColors[i]) != 0xFF) { - return false; - } - } - return true; + return mIsOpaque; } /** - * Return the color associated with the given set of {@link android.view.View} states. + * Return the color associated with the given set of + * {@link android.view.View} states. * * @param stateSet an array of {@link android.view.View} states - * @param defaultColor the color to return if there's not state spec in this - * {@link ColorStateList} that matches the stateSet. + * @param defaultColor the color to return if there's no matching state + * spec in this {@link ColorStateList} that matches the + * stateSet. * * @return the color associated with that set of states in this {@link ColorStateList}. */ - public int getColorForState(int[] stateSet, int defaultColor) { + public int getColorForState(@Nullable int[] stateSet, int defaultColor) { final int setLength = mStateSpecs.length; for (int i = 0; i < setLength; i++) { - int[] stateSpec = mStateSpecs[i]; + final int[] stateSpec = mStateSpecs[i]; if (StateSet.stateSetMatches(stateSpec, stateSet)) { return mColors[i]; } @@ -314,7 +441,9 @@ public class ColorStateList implements Parcelable { } /** - * Return the states in this {@link ColorStateList}. + * Return the states in this {@link ColorStateList}. The returned array + * should not be modified. + * * @return the states in this {@link ColorStateList} * @hide */ @@ -323,7 +452,9 @@ public class ColorStateList implements Parcelable { } /** - * Return the colors in this {@link ColorStateList}. + * Return the colors in this {@link ColorStateList}. The returned array + * should not be modified. + * * @return the colors in this {@link ColorStateList} * @hide */ @@ -374,11 +505,81 @@ public class ColorStateList implements Parcelable { @Override public String toString() { return "ColorStateList{" + + "mThemeAttrs=" + Arrays.deepToString(mThemeAttrs) + + "mChangingConfigurations=" + mChangingConfigurations + "mStateSpecs=" + Arrays.deepToString(mStateSpecs) + "mColors=" + Arrays.toString(mColors) + "mDefaultColor=" + mDefaultColor + '}'; } + /** + * Updates the default color and opacity. + */ + private void onColorsChanged() { + int defaultColor = DEFAULT_COLOR; + boolean isOpaque = true; + + final int[][] states = mStateSpecs; + final int[] colors = mColors; + final int N = states.length; + if (N > 0) { + defaultColor = colors[0]; + + for (int i = N - 1; i > 0; i--) { + if (states[i].length == 0) { + defaultColor = colors[i]; + break; + } + } + + for (int i = 0; i < N; i++) { + if (Color.alpha(colors[i]) != 0xFF) { + isOpaque = false; + break; + } + } + } + + mDefaultColor = defaultColor; + mIsOpaque = isOpaque; + } + + /** + * @return A factory that can create new instances of this ColorStateList. + */ + ColorStateListFactory getFactory() { + return new ColorStateListFactory(this); + } + + static class ColorStateListFactory extends ConstantState<ColorStateList> { + final ColorStateList mSrc; + + public ColorStateListFactory(ColorStateList src) { + mSrc = src; + } + + @Override + public int getChangingConfigurations() { + return mSrc.mChangingConfigurations; + } + + @Override + public ColorStateList newInstance() { + return mSrc; + } + + @Override + public ColorStateList newInstance(Resources res, Theme theme) { + if (theme == null || !mSrc.canApplyTheme()) { + return mSrc; + } + + final ColorStateList clone = new ColorStateList(mSrc); + clone.applyTheme(theme); + return clone; + } + } + @Override public int describeContents() { return 0; @@ -386,6 +587,9 @@ public class ColorStateList implements Parcelable { @Override public void writeToParcel(Parcel dest, int flags) { + if (canApplyTheme()) { + Log.w(TAG, "Wrote partially-resolved ColorStateList to parcel!"); + } final int N = mStateSpecs.length; dest.writeInt(N); for (int i = 0; i < N; i++) { diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index 73913b6..df949c1 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -16,21 +16,20 @@ package android.content.res; -import android.animation.Animator; -import android.animation.StateListAnimator; -import android.annotation.NonNull; -import android.util.Pools.SynchronizedPool; -import android.view.ViewDebug; import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; +import android.animation.Animator; +import android.animation.StateListAnimator; +import android.annotation.NonNull; import android.annotation.Nullable; import android.content.pm.ActivityInfo; +import android.content.res.ColorStateList.ColorStateListFactory; import android.graphics.Movie; -import android.graphics.drawable.Drawable; import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable.ConstantState; import android.os.Build; import android.os.Bundle; @@ -40,9 +39,11 @@ import android.util.ArrayMap; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; +import android.util.LongSparseArray; +import android.util.Pools.SynchronizedPool; import android.util.Slog; import android.util.TypedValue; -import android.util.LongSparseArray; +import android.view.ViewDebug; import java.io.IOException; import java.io.InputStream; @@ -97,8 +98,8 @@ public class Resources { private static final LongSparseArray<ConstantState>[] sPreloadedDrawables; private static final LongSparseArray<ConstantState> sPreloadedColorDrawables = new LongSparseArray<ConstantState>(); - private static final LongSparseArray<ColorStateList> sPreloadedColorStateLists - = new LongSparseArray<ColorStateList>(); + private static final LongSparseArray<ColorStateListFactory> sPreloadedColorStateLists + = new LongSparseArray<ColorStateListFactory>(); // Pool of TypedArrays targeted to this Resources object. final SynchronizedPool<TypedArray> mTypedArrayPool = new SynchronizedPool<TypedArray>(5); @@ -116,8 +117,8 @@ public class Resources { new ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>>(); private final ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> mColorDrawableCache = new ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>>(); - private final LongSparseArray<WeakReference<ColorStateList>> mColorStateListCache = - new LongSparseArray<WeakReference<ColorStateList>>(); + private final ConfigurationBoundResourceCache<ColorStateList> mColorStateListCache = + new ConfigurationBoundResourceCache<ColorStateList>(this); private final ConfigurationBoundResourceCache<Animator> mAnimatorCache = new ConfigurationBoundResourceCache<Animator>(this); private final ConfigurationBoundResourceCache<StateListAnimator> mStateListAnimatorCache = @@ -897,20 +898,41 @@ public class Resources { } /** - * Return a color integer associated with a particular resource ID. - * If the resource holds a complex - * {@link android.content.res.ColorStateList}, then the default color from - * the set is returned. + * Returns a color integer associated with a particular resource ID. If the + * resource holds a complex {@link ColorStateList}, then the default color + * from the set is returned. * * @param id The desired resource identifier, as generated by the aapt * tool. This integer encodes the package, type, and resource * entry. The value 0 is an invalid identifier. * - * @throws NotFoundException Throws NotFoundException if the given ID does not exist. + * @throws NotFoundException Throws NotFoundException if the given ID does + * not exist. * - * @return Returns a single color value in the form 0xAARRGGBB. + * @return A single color value in the form 0xAARRGGBB. + * @deprecated Use {@link #getColor(int, Theme)} instead. */ public int getColor(int id) throws NotFoundException { + return getColor(id, null); + } + + /** + * Returns a themed color integer associated with a particular resource ID. + * If the resource holds a complex {@link ColorStateList}, then the default + * color from the set is returned. + * + * @param id The desired resource identifier, as generated by the aapt + * tool. This integer encodes the package, type, and resource + * entry. The value 0 is an invalid identifier. + * @param theme The theme used to style the color attributes, may be + * {@code null}. + * + * @throws NotFoundException Throws NotFoundException if the given ID does + * not exist. + * + * @return A single color value in the form 0xAARRGGBB. + */ + public int getColor(int id, @Nullable Theme theme) throws NotFoundException { TypedValue value; synchronized (mAccessLock) { value = mTmpValue; @@ -919,40 +941,77 @@ public class Resources { } getValue(id, value, true); if (value.type >= TypedValue.TYPE_FIRST_INT - && value.type <= TypedValue.TYPE_LAST_INT) { + && value.type <= TypedValue.TYPE_LAST_INT) { mTmpValue = value; return value.data; } else if (value.type != TypedValue.TYPE_STRING) { throw new NotFoundException( - "Resource ID #0x" + Integer.toHexString(id) + " type #0x" - + Integer.toHexString(value.type) + " is not valid"); + "Resource ID #0x" + Integer.toHexString(id) + " type #0x" + + Integer.toHexString(value.type) + " is not valid"); } mTmpValue = null; } - ColorStateList csl = loadColorStateList(value, id); + + final ColorStateList csl = loadColorStateList(value, id, theme); synchronized (mAccessLock) { if (mTmpValue == null) { mTmpValue = value; } } + return csl.getDefaultColor(); } /** - * Return a color state list associated with a particular resource ID. The - * resource may contain either a single raw color value, or a complex - * {@link android.content.res.ColorStateList} holding multiple possible colors. + * Returns a color state list associated with a particular resource ID. The + * resource may contain either a single raw color value or a complex + * {@link ColorStateList} holding multiple possible colors. * * @param id The desired resource identifier of a {@link ColorStateList}, - * as generated by the aapt tool. This integer encodes the package, type, and resource - * entry. The value 0 is an invalid identifier. + * as generated by the aapt tool. This integer encodes the + * package, type, and resource entry. The value 0 is an invalid + * identifier. * - * @throws NotFoundException Throws NotFoundException if the given ID does not exist. + * @throws NotFoundException Throws NotFoundException if the given ID does + * not exist. * - * @return Returns a ColorStateList object containing either a single - * solid color or multiple colors that can be selected based on a state. + * @return A ColorStateList object containing either a single solid color + * or multiple colors that can be selected based on a state. + * @deprecated Use {@link #getColorStateList(int, Theme)} instead. */ + @Nullable public ColorStateList getColorStateList(int id) throws NotFoundException { + final ColorStateList csl = getColorStateList(id, null); + if (csl != null && csl.canApplyTheme()) { + Log.w(TAG, "ColorStateList " + getResourceName(id) + " has " + + "unresolved theme attributes! Consider using " + + "Resources.getColorStateList(int, Theme) or " + + "Context.getColorStateList(int).", new RuntimeException()); + } + return csl; + } + + /** + * Returns a themed color state list associated with a particular resource + * ID. The resource may contain either a single raw color value or a + * complex {@link ColorStateList} holding multiple possible colors. + * + * @param id The desired resource identifier of a {@link ColorStateList}, + * as generated by the aapt tool. This integer encodes the + * package, type, and resource entry. The value 0 is an invalid + * identifier. + * @param theme The theme used to style the color attributes, may be + * {@code null}. + * + * @throws NotFoundException Throws NotFoundException if the given ID does + * not exist. + * + * @return A themed ColorStateList object containing either a single solid + * color or multiple colors that can be selected based on a state. + */ + @Nullable + public ColorStateList getColorStateList(int id, @Nullable Theme theme) + throws NotFoundException { TypedValue value; synchronized (mAccessLock) { value = mTmpValue; @@ -963,15 +1022,19 @@ public class Resources { } getValue(id, value, true); } - ColorStateList res = loadColorStateList(value, id); + + final ColorStateList res = loadColorStateList(value, id, theme); synchronized (mAccessLock) { if (mTmpValue == null) { mTmpValue = value; } } + return res; } + + /** * Return a boolean associated with a particular resource ID. This can be * used with any integral resource value, and will return true if it is @@ -1843,11 +1906,10 @@ public class Resources { clearDrawableCachesLocked(mDrawableCache, configChanges); clearDrawableCachesLocked(mColorDrawableCache, configChanges); + mColorStateListCache.onConfigurationChange(configChanges); mAnimatorCache.onConfigurationChange(configChanges); mStateListAnimatorCache.onConfigurationChange(configChanges); - mColorStateListCache.clear(); - flushLayoutCache(); } synchronized (sSync) { @@ -2439,7 +2501,7 @@ public class Resources { private Drawable loadDrawableForCookie(TypedValue value, int id, Theme theme) { if (value.string == null) { throw new NotFoundException("Resource \"" + getResourceName(id) + "\" (" - + Integer.toHexString(id) + ") is not a Drawable (color or path): " + value); + + Integer.toHexString(id) + ") is not a Drawable (color or path): " + value); } final String file = value.string.toString(); @@ -2530,7 +2592,8 @@ public class Resources { return null; } - /*package*/ ColorStateList loadColorStateList(TypedValue value, int id) + @Nullable + ColorStateList loadColorStateList(TypedValue value, int id, Theme theme) throws NotFoundException { if (TRACE_FOR_PRELOAD) { // Log only framework resources @@ -2544,101 +2607,107 @@ public class Resources { ColorStateList csl; - if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT && - value.type <= TypedValue.TYPE_LAST_COLOR_INT) { - - csl = sPreloadedColorStateLists.get(key); - if (csl != null) { - return csl; + // Handle inline color definitions. + if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT + && value.type <= TypedValue.TYPE_LAST_COLOR_INT) { + final ColorStateListFactory factory = sPreloadedColorStateLists.get(key); + if (factory != null) { + return factory.newInstance(); } csl = ColorStateList.valueOf(value.data); + if (mPreloading) { if (verifyPreloadConfig(value.changingConfigurations, 0, value.resourceId, "color")) { - sPreloadedColorStateLists.put(key, csl); + sPreloadedColorStateLists.put(key, csl.getFactory()); } } return csl; } - csl = getCachedColorStateList(key); + final ConfigurationBoundResourceCache<ColorStateList> cache = mColorStateListCache; + + csl = cache.get(key, theme); if (csl != null) { return csl; } - csl = sPreloadedColorStateLists.get(key); + final ColorStateListFactory factory = sPreloadedColorStateLists.get(key); + if (factory != null) { + csl = factory.newInstance(this, theme); + } + + if (csl == null) { + csl = loadColorStateListForCookie(value, id, theme); + } + if (csl != null) { - return csl; + if (mPreloading) { + if (verifyPreloadConfig(value.changingConfigurations, 0, value.resourceId, + "color")) { + sPreloadedColorStateLists.put(key, csl.getFactory()); + } + } else { + cache.put(key, theme, csl.getFactory()); + } } + return csl; + } + + private ColorStateList loadColorStateListForCookie(TypedValue value, int id, Theme theme) { if (value.string == null) { - throw new NotFoundException( - "Resource is not a ColorStateList (color or path): " + value); + throw new NotFoundException("Resource \"" + getResourceName(id) + "\" (" + + Integer.toHexString(id) + ") is not a ColorStateList: " + 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 color state list #" + Integer.toHexString(id) + + ": " + name + " at " + file); + } + } + } + + if (DEBUG_LOAD) { + Log.v(TAG, "Loading color state list for cookie " + value.assetCookie + ": " + file); + } + + final ColorStateList csl; + + Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file); if (file.endsWith(".xml")) { - Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file); try { final XmlResourceParser rp = loadXmlResourceParser( - file, id, value.assetCookie, "colorstatelist"); - csl = ColorStateList.createFromXml(this, rp); + file, id, value.assetCookie, "colorstatelist"); + csl = ColorStateList.createFromXml(this, rp, theme); rp.close(); } catch (Exception e) { Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); - NotFoundException rnf = new NotFoundException( - "File " + file + " from color state list resource ID #0x" - + Integer.toHexString(id)); + final NotFoundException rnf = new NotFoundException( + "File " + file + " from color state list resource ID #0x" + + Integer.toHexString(id)); rnf.initCause(e); throw rnf; } - Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); } else { + Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); throw new NotFoundException( "File " + file + " from drawable resource ID #0x" - + Integer.toHexString(id) + ": .xml extension required"); - } - - if (csl != null) { - if (mPreloading) { - if (verifyPreloadConfig(value.changingConfigurations, 0, value.resourceId, - "color")) { - sPreloadedColorStateLists.put(key, csl); - } - } else { - synchronized (mAccessLock) { - //Log.i(TAG, "Saving cached color state list @ #" + - // Integer.toHexString(key.intValue()) - // + " in " + this + ": " + csl); - mColorStateListCache.put(key, new WeakReference<ColorStateList>(csl)); - } - } + + Integer.toHexString(id) + ": .xml extension required"); } + Trace.traceEnd(Trace.TRACE_TAG_RESOURCES); return csl; } - private ColorStateList getCachedColorStateList(long key) { - synchronized (mAccessLock) { - WeakReference<ColorStateList> wr = mColorStateListCache.get(key); - if (wr != null) { // we have the key - ColorStateList entry = wr.get(); - if (entry != null) { - //Log.i(TAG, "Returning cached color state list @ #" + - // Integer.toHexString(((Integer)key).intValue()) - // + " in " + this + ": " + entry); - return entry; - } else { // our entry has been purged - mColorStateListCache.delete(key); - } - } - } - return null; - } - /*package*/ XmlResourceParser loadXmlResourceParser(int id, String type) throws NotFoundException { synchronized (mAccessLock) { @@ -2715,6 +2784,20 @@ public class Resources { } } + /** + * Obtains styled attributes from the theme, if available, or unstyled + * resources if the theme is null. + * + * @hide + */ + public 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); + } + private Resources() { mAssets = AssetManager.getSystem(); // NOTE: Intentionally leaving this uninitialized (all values set diff --git a/core/java/android/content/res/TypedArray.java b/core/java/android/content/res/TypedArray.java index 02602fb..06c701a 100644 --- a/core/java/android/content/res/TypedArray.java +++ b/core/java/android/content/res/TypedArray.java @@ -392,8 +392,8 @@ public class TypedArray { } else if (type == TypedValue.TYPE_STRING) { final TypedValue value = mValue; if (getValueAt(index, value)) { - ColorStateList csl = mResources.loadColorStateList( - value, value.resourceId); + final ColorStateList csl = mResources.loadColorStateList( + value, value.resourceId, mTheme); return csl.getDefaultColor(); } return defValue; @@ -424,7 +424,7 @@ public class TypedArray { if (value.type == TypedValue.TYPE_ATTRIBUTE) { throw new RuntimeException("Failed to resolve attribute at index " + index); } - return mResources.loadColorStateList(value, value.resourceId); + return mResources.loadColorStateList(value, value.resourceId, mTheme); } return null; } @@ -905,12 +905,21 @@ public class TypedArray { * Removes the entries from the typed array so that subsequent calls to typed * getters will return the default value without crashing. * - * @return an array of length {@link #getIndexCount()} populated with theme - * attributes, or null if there are no theme attributes in the typed - * array + * @return An array of length {@link #getIndexCount()} populated with theme + * attributes, or {@code null} if there are no theme attributes in + * the typed array. * @hide */ + @Nullable public int[] extractThemeAttrs() { + return extractThemeAttrs(null); + } + + /** + * @hide + */ + @Nullable + public int[] extractThemeAttrs(@Nullable int[] scrap) { if (mRecycled) { throw new RuntimeException("Cannot make calls to a recycled instance!"); } @@ -922,6 +931,7 @@ public class TypedArray { for (int i = 0; i < N; i++) { final int index = i * AssetManager.STYLE_NUM_ENTRIES; if (data[index + AssetManager.STYLE_TYPE] != TypedValue.TYPE_ATTRIBUTE) { + // Not an attribute, ignore. continue; } @@ -930,13 +940,20 @@ public class TypedArray { final int attr = data[index + AssetManager.STYLE_DATA]; if (attr == 0) { - // This attribute is useless! + // Useless data, ignore. continue; } + // Ensure we have a usable attribute array. if (attrs == null) { - attrs = new int[N]; + if (scrap != null && scrap.length == N) { + attrs = scrap; + Arrays.fill(attrs, 0); + } else { + attrs = new int[N]; + } } + attrs[i] = attr; } |