summaryrefslogtreecommitdiffstats
path: root/core/java/android/content/res
diff options
context:
space:
mode:
authorAlan Viverette <alanv@google.com>2015-01-05 14:59:19 -0800
committerAlan Viverette <alanv@google.com>2015-01-05 14:59:19 -0800
commit45c4bbbbce6bbad50a033efcba7948a23f1f117a (patch)
tree9041d660953bcebaa302424abc27497867ccf954 /core/java/android/content/res
parent4ae0d9045f43f87354513e3d925fcea610c77fea (diff)
downloadframeworks_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.java400
-rw-r--r--core/java/android/content/res/Resources.java261
-rw-r--r--core/java/android/content/res/TypedArray.java33
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 &lt;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;
}