1/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.content.res;
18
19import android.annotation.ColorInt;
20import android.annotation.NonNull;
21import android.annotation.Nullable;
22import android.content.pm.ActivityInfo.Config;
23import android.content.res.Resources.Theme;
24import android.graphics.Color;
25
26import com.android.internal.R;
27import com.android.internal.util.ArrayUtils;
28import com.android.internal.util.GrowingArrayUtils;
29
30import org.xmlpull.v1.XmlPullParser;
31import org.xmlpull.v1.XmlPullParserException;
32
33import android.util.AttributeSet;
34import android.util.Log;
35import android.util.MathUtils;
36import android.util.SparseArray;
37import android.util.StateSet;
38import android.util.Xml;
39import android.os.Parcel;
40import android.os.Parcelable;
41
42import java.io.IOException;
43import java.lang.ref.WeakReference;
44import java.util.Arrays;
45
46/**
47 *
48 * Lets you map {@link android.view.View} state sets to colors.
49 * <p>
50 * {@link android.content.res.ColorStateList}s are created from XML resource files defined in the
51 * "color" subdirectory directory of an application's resource directory. The XML file contains
52 * a single "selector" element with a number of "item" elements inside. For example:
53 * <pre>
54 * &lt;selector xmlns:android="http://schemas.android.com/apk/res/android"&gt;
55 *   &lt;item android:state_focused="true"
56 *           android:color="@color/sample_focused" /&gt;
57 *   &lt;item android:state_pressed="true"
58 *           android:state_enabled="false"
59 *           android:color="@color/sample_disabled_pressed" /&gt;
60 *   &lt;item android:state_enabled="false"
61 *           android:color="@color/sample_disabled_not_pressed" /&gt;
62 *   &lt;item android:color="@color/sample_default" /&gt;
63 * &lt;/selector&gt;
64 * </pre>
65 *
66 * This defines a set of state spec / color pairs where each state spec specifies a set of
67 * states that a view must either be in or not be in and the color specifies the color associated
68 * with that spec.
69 *
70 * <a name="StateSpec"></a>
71 * <h3>State specs</h3>
72 * <p>
73 * Each item defines a set of state spec and color pairs, where the state spec is a series of
74 * attributes set to either {@code true} or {@code false} to represent inclusion or exclusion. If
75 * an attribute is not specified for an item, it may be any value.
76 * <p>
77 * For example, the following item will be matched whenever the focused state is set; any other
78 * states may be set or unset:
79 * <pre>
80 * &lt;item android:state_focused="true"
81 *         android:color="@color/sample_focused" /&gt;
82 * </pre>
83 * <p>
84 * Typically, a color state list will reference framework-defined state attributes such as
85 * {@link android.R.attr#state_focused android:state_focused} or
86 * {@link android.R.attr#state_enabled android:state_enabled}; however, app-defined attributes may
87 * also be used.
88 * <p>
89 * <strong>Note:</strong> The list of state specs will be matched against in the order that they
90 * appear in the XML file. For this reason, more-specific items should be placed earlier in the
91 * file. An item with no state spec is considered to match any set of states and is generally
92 * useful as a final item to be used as a default.
93 * <p>
94 * If an item with no state spec if placed before other items, those items
95 * will be ignored.
96 *
97 * <a name="ItemAttributes"></a>
98 * <h3>Item attributes</h3>
99 * <p>
100 * Each item must define an {@link android.R.attr#color android:color} attribute, which may be
101 * an HTML-style hex color, a reference to a color resource, or -- in API 23 and above -- a theme
102 * attribute that resolves to a color.
103 * <p>
104 * Starting with API 23, items may optionally define an {@link android.R.attr#alpha android:alpha}
105 * attribute to modify the base color's opacity. This attribute takes a either floating-point value
106 * between 0 and 1 or a theme attribute that resolves as such. The item's overall color is
107 * calculated by multiplying by the base color's alpha channel by the {@code alpha} value. For
108 * example, the following item represents the theme's accent color at 50% opacity:
109 * <pre>
110 * &lt;item android:state_enabled="false"
111 *         android:color="?android:attr/colorAccent"
112 *         android:alpha="0.5" /&gt;
113 * </pre>
114 *
115 * <a name="DeveloperGuide"></a>
116 * <h3>Developer guide</h3>
117 * <p>
118 * For more information, see the guide to
119 * <a href="{@docRoot}guide/topics/resources/color-list-resource.html">Color State
120 * List Resource</a>.
121 *
122 * @attr ref android.R.styleable#ColorStateListItem_alpha
123 * @attr ref android.R.styleable#ColorStateListItem_color
124 */
125public class ColorStateList extends ComplexColor implements Parcelable {
126    private static final String TAG = "ColorStateList";
127
128    private static final int DEFAULT_COLOR = Color.RED;
129    private static final int[][] EMPTY = new int[][] { new int[0] };
130
131    /** Thread-safe cache of single-color ColorStateLists. */
132    private static final SparseArray<WeakReference<ColorStateList>> sCache = new SparseArray<>();
133
134    /** Lazily-created factory for this color state list. */
135    private ColorStateListFactory mFactory;
136
137    private int[][] mThemeAttrs;
138    private @Config int mChangingConfigurations;
139
140    private int[][] mStateSpecs;
141    private int[] mColors;
142    private int mDefaultColor;
143    private boolean mIsOpaque;
144
145    private ColorStateList() {
146        // Not publicly instantiable.
147    }
148
149    /**
150     * Creates a ColorStateList that returns the specified mapping from
151     * states to colors.
152     */
153    public ColorStateList(int[][] states, @ColorInt int[] colors) {
154        mStateSpecs = states;
155        mColors = colors;
156
157        onColorsChanged();
158    }
159
160    /**
161     * @return A ColorStateList containing a single color.
162     */
163    @NonNull
164    public static ColorStateList valueOf(@ColorInt int color) {
165        synchronized (sCache) {
166            final int index = sCache.indexOfKey(color);
167            if (index >= 0) {
168                final ColorStateList cached = sCache.valueAt(index).get();
169                if (cached != null) {
170                    return cached;
171                }
172
173                // Prune missing entry.
174                sCache.removeAt(index);
175            }
176
177            // Prune the cache before adding new items.
178            final int N = sCache.size();
179            for (int i = N - 1; i >= 0; i--) {
180                if (sCache.valueAt(i).get() == null) {
181                    sCache.removeAt(i);
182                }
183            }
184
185            final ColorStateList csl = new ColorStateList(EMPTY, new int[] { color });
186            sCache.put(color, new WeakReference<>(csl));
187            return csl;
188        }
189    }
190
191    /**
192     * Creates a ColorStateList with the same properties as another
193     * ColorStateList.
194     * <p>
195     * The properties of the new ColorStateList can be modified without
196     * affecting the source ColorStateList.
197     *
198     * @param orig the source color state list
199     */
200    private ColorStateList(ColorStateList orig) {
201        if (orig != null) {
202            mChangingConfigurations = orig.mChangingConfigurations;
203            mStateSpecs = orig.mStateSpecs;
204            mDefaultColor = orig.mDefaultColor;
205            mIsOpaque = orig.mIsOpaque;
206
207            // Deep copy, these may change due to applyTheme().
208            mThemeAttrs = orig.mThemeAttrs.clone();
209            mColors = orig.mColors.clone();
210        }
211    }
212
213    /**
214     * Creates a ColorStateList from an XML document.
215     *
216     * @param r Resources against which the ColorStateList should be inflated.
217     * @param parser Parser for the XML document defining the ColorStateList.
218     * @return A new color state list.
219     *
220     * @deprecated Use #createFromXml(Resources, XmlPullParser parser, Theme)
221     */
222    @NonNull
223    @Deprecated
224    public static ColorStateList createFromXml(Resources r, XmlPullParser parser)
225            throws XmlPullParserException, IOException {
226        return createFromXml(r, parser, null);
227    }
228
229    /**
230     * Creates a ColorStateList from an XML document using given a set of
231     * {@link Resources} and a {@link Theme}.
232     *
233     * @param r Resources against which the ColorStateList should be inflated.
234     * @param parser Parser for the XML document defining the ColorStateList.
235     * @param theme Optional theme to apply to the color state list, may be
236     *              {@code null}.
237     * @return A new color state list.
238     */
239    @NonNull
240    public static ColorStateList createFromXml(@NonNull Resources r, @NonNull XmlPullParser parser,
241            @Nullable Theme theme) throws XmlPullParserException, IOException {
242        final AttributeSet attrs = Xml.asAttributeSet(parser);
243
244        int type;
245        while ((type = parser.next()) != XmlPullParser.START_TAG
246                   && type != XmlPullParser.END_DOCUMENT) {
247            // Seek parser to start tag.
248        }
249
250        if (type != XmlPullParser.START_TAG) {
251            throw new XmlPullParserException("No start tag found");
252        }
253
254        return createFromXmlInner(r, parser, attrs, theme);
255    }
256
257    /**
258     * Create from inside an XML document. Called on a parser positioned at a
259     * tag in an XML document, tries to create a ColorStateList from that tag.
260     *
261     * @throws XmlPullParserException if the current tag is not &lt;selector>
262     * @return A new color state list for the current tag.
263     */
264    @NonNull
265    static ColorStateList createFromXmlInner(@NonNull Resources r,
266            @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme)
267            throws XmlPullParserException, IOException {
268        final String name = parser.getName();
269        if (!name.equals("selector")) {
270            throw new XmlPullParserException(
271                    parser.getPositionDescription() + ": invalid color state list tag " + name);
272        }
273
274        final ColorStateList colorStateList = new ColorStateList();
275        colorStateList.inflate(r, parser, attrs, theme);
276        return colorStateList;
277    }
278
279    /**
280     * Creates a new ColorStateList that has the same states and colors as this
281     * one but where each color has the specified alpha value (0-255).
282     *
283     * @param alpha The new alpha channel value (0-255).
284     * @return A new color state list.
285     */
286    @NonNull
287    public ColorStateList withAlpha(int alpha) {
288        final int[] colors = new int[mColors.length];
289        final int len = colors.length;
290        for (int i = 0; i < len; i++) {
291            colors[i] = (mColors[i] & 0xFFFFFF) | (alpha << 24);
292        }
293
294        return new ColorStateList(mStateSpecs, colors);
295    }
296
297    /**
298     * Fill in this object based on the contents of an XML "selector" element.
299     */
300    private void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
301            @NonNull AttributeSet attrs, @Nullable Theme theme)
302            throws XmlPullParserException, IOException {
303        final int innerDepth = parser.getDepth()+1;
304        int depth;
305        int type;
306
307        @Config int changingConfigurations = 0;
308        int defaultColor = DEFAULT_COLOR;
309
310        boolean hasUnresolvedAttrs = false;
311
312        int[][] stateSpecList = ArrayUtils.newUnpaddedArray(int[].class, 20);
313        int[][] themeAttrsList = new int[stateSpecList.length][];
314        int[] colorList = new int[stateSpecList.length];
315        int listSize = 0;
316
317        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
318               && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
319            if (type != XmlPullParser.START_TAG || depth > innerDepth
320                    || !parser.getName().equals("item")) {
321                continue;
322            }
323
324            final TypedArray a = Resources.obtainAttributes(r, theme, attrs,
325                    R.styleable.ColorStateListItem);
326            final int[] themeAttrs = a.extractThemeAttrs();
327            final int baseColor = a.getColor(R.styleable.ColorStateListItem_color, Color.MAGENTA);
328            final float alphaMod = a.getFloat(R.styleable.ColorStateListItem_alpha, 1.0f);
329
330            changingConfigurations |= a.getChangingConfigurations();
331
332            a.recycle();
333
334            // Parse all unrecognized attributes as state specifiers.
335            int j = 0;
336            final int numAttrs = attrs.getAttributeCount();
337            int[] stateSpec = new int[numAttrs];
338            for (int i = 0; i < numAttrs; i++) {
339                final int stateResId = attrs.getAttributeNameResource(i);
340                switch (stateResId) {
341                    case R.attr.color:
342                    case R.attr.alpha:
343                        // Recognized attribute, ignore.
344                        break;
345                    default:
346                        stateSpec[j++] = attrs.getAttributeBooleanValue(i, false)
347                                ? stateResId : -stateResId;
348                }
349            }
350            stateSpec = StateSet.trimStateSet(stateSpec, j);
351
352            // Apply alpha modulation. If we couldn't resolve the color or
353            // alpha yet, the default values leave us enough information to
354            // modulate again during applyTheme().
355            final int color = modulateColorAlpha(baseColor, alphaMod);
356            if (listSize == 0 || stateSpec.length == 0) {
357                defaultColor = color;
358            }
359
360            if (themeAttrs != null) {
361                hasUnresolvedAttrs = true;
362            }
363
364            colorList = GrowingArrayUtils.append(colorList, listSize, color);
365            themeAttrsList = GrowingArrayUtils.append(themeAttrsList, listSize, themeAttrs);
366            stateSpecList = GrowingArrayUtils.append(stateSpecList, listSize, stateSpec);
367            listSize++;
368        }
369
370        mChangingConfigurations = changingConfigurations;
371        mDefaultColor = defaultColor;
372
373        if (hasUnresolvedAttrs) {
374            mThemeAttrs = new int[listSize][];
375            System.arraycopy(themeAttrsList, 0, mThemeAttrs, 0, listSize);
376        } else {
377            mThemeAttrs = null;
378        }
379
380        mColors = new int[listSize];
381        mStateSpecs = new int[listSize][];
382        System.arraycopy(colorList, 0, mColors, 0, listSize);
383        System.arraycopy(stateSpecList, 0, mStateSpecs, 0, listSize);
384
385        onColorsChanged();
386    }
387
388    /**
389     * Returns whether a theme can be applied to this color state list, which
390     * usually indicates that the color state list has unresolved theme
391     * attributes.
392     *
393     * @return whether a theme can be applied to this color state list
394     * @hide only for resource preloading
395     */
396    @Override
397    public boolean canApplyTheme() {
398        return mThemeAttrs != null;
399    }
400
401    /**
402     * Applies a theme to this color state list.
403     * <p>
404     * <strong>Note:</strong> Applying a theme may affect the changing
405     * configuration parameters of this color state list. After calling this
406     * method, any dependent configurations must be updated by obtaining the
407     * new configuration mask from {@link #getChangingConfigurations()}.
408     *
409     * @param t the theme to apply
410     */
411    private void applyTheme(Theme t) {
412        if (mThemeAttrs == null) {
413            return;
414        }
415
416        boolean hasUnresolvedAttrs = false;
417
418        final int[][] themeAttrsList = mThemeAttrs;
419        final int N = themeAttrsList.length;
420        for (int i = 0; i < N; i++) {
421            if (themeAttrsList[i] != null) {
422                final TypedArray a = t.resolveAttributes(themeAttrsList[i],
423                        R.styleable.ColorStateListItem);
424
425                final float defaultAlphaMod;
426                if (themeAttrsList[i][R.styleable.ColorStateListItem_color] != 0) {
427                    // If the base color hasn't been resolved yet, the current
428                    // color's alpha channel is either full-opacity (if we
429                    // haven't resolved the alpha modulation yet) or
430                    // pre-modulated. Either is okay as a default value.
431                    defaultAlphaMod = Color.alpha(mColors[i]) / 255.0f;
432                } else {
433                    // Otherwise, the only correct default value is 1. Even if
434                    // nothing is resolved during this call, we can apply this
435                    // multiple times without losing of information.
436                    defaultAlphaMod = 1.0f;
437                }
438
439                // Extract the theme attributes, if any, before attempting to
440                // read from the typed array. This prevents a crash if we have
441                // unresolved attrs.
442                themeAttrsList[i] = a.extractThemeAttrs(themeAttrsList[i]);
443                if (themeAttrsList[i] != null) {
444                    hasUnresolvedAttrs = true;
445                }
446
447                final int baseColor = a.getColor(
448                        R.styleable.ColorStateListItem_color, mColors[i]);
449                final float alphaMod = a.getFloat(
450                        R.styleable.ColorStateListItem_alpha, defaultAlphaMod);
451                mColors[i] = modulateColorAlpha(baseColor, alphaMod);
452
453                // Account for any configuration changes.
454                mChangingConfigurations |= a.getChangingConfigurations();
455
456                a.recycle();
457            }
458        }
459
460        if (!hasUnresolvedAttrs) {
461            mThemeAttrs = null;
462        }
463
464        onColorsChanged();
465    }
466
467    /**
468     * Returns an appropriately themed color state list.
469     *
470     * @param t the theme to apply
471     * @return a copy of the color state list with the theme applied, or the
472     *         color state list itself if there were no unresolved theme
473     *         attributes
474     * @hide only for resource preloading
475     */
476    @Override
477    public ColorStateList obtainForTheme(Theme t) {
478        if (t == null || !canApplyTheme()) {
479            return this;
480        }
481
482        final ColorStateList clone = new ColorStateList(this);
483        clone.applyTheme(t);
484        return clone;
485    }
486
487    /**
488     * Returns a mask of the configuration parameters for which this color
489     * state list may change, requiring that it be re-created.
490     *
491     * @return a mask of the changing configuration parameters, as defined by
492     *         {@link android.content.pm.ActivityInfo}
493     *
494     * @see android.content.pm.ActivityInfo
495     */
496    public @Config int getChangingConfigurations() {
497        return super.getChangingConfigurations() | mChangingConfigurations;
498    }
499
500    private int modulateColorAlpha(int baseColor, float alphaMod) {
501        if (alphaMod == 1.0f) {
502            return baseColor;
503        }
504
505        final int baseAlpha = Color.alpha(baseColor);
506        final int alpha = MathUtils.constrain((int) (baseAlpha * alphaMod + 0.5f), 0, 255);
507        return (baseColor & 0xFFFFFF) | (alpha << 24);
508    }
509
510    /**
511     * Indicates whether this color state list contains more than one state spec
512     * and will change color based on state.
513     *
514     * @return True if this color state list changes color based on state, false
515     *         otherwise.
516     * @see #getColorForState(int[], int)
517     */
518    @Override
519    public boolean isStateful() {
520        return mStateSpecs.length > 1;
521    }
522
523    /**
524     * Indicates whether this color state list is opaque, which means that every
525     * color returned from {@link #getColorForState(int[], int)} has an alpha
526     * value of 255.
527     *
528     * @return True if this color state list is opaque.
529     */
530    public boolean isOpaque() {
531        return mIsOpaque;
532    }
533
534    /**
535     * Return the color associated with the given set of
536     * {@link android.view.View} states.
537     *
538     * @param stateSet an array of {@link android.view.View} states
539     * @param defaultColor the color to return if there's no matching state
540     *                     spec in this {@link ColorStateList} that matches the
541     *                     stateSet.
542     *
543     * @return the color associated with that set of states in this {@link ColorStateList}.
544     */
545    public int getColorForState(@Nullable int[] stateSet, int defaultColor) {
546        final int setLength = mStateSpecs.length;
547        for (int i = 0; i < setLength; i++) {
548            final int[] stateSpec = mStateSpecs[i];
549            if (StateSet.stateSetMatches(stateSpec, stateSet)) {
550                return mColors[i];
551            }
552        }
553        return defaultColor;
554    }
555
556    /**
557     * Return the default color in this {@link ColorStateList}.
558     *
559     * @return the default color in this {@link ColorStateList}.
560     */
561    @ColorInt
562    public int getDefaultColor() {
563        return mDefaultColor;
564    }
565
566    /**
567     * Return the states in this {@link ColorStateList}. The returned array
568     * should not be modified.
569     *
570     * @return the states in this {@link ColorStateList}
571     * @hide
572     */
573    public int[][] getStates() {
574        return mStateSpecs;
575    }
576
577    /**
578     * Return the colors in this {@link ColorStateList}. The returned array
579     * should not be modified.
580     *
581     * @return the colors in this {@link ColorStateList}
582     * @hide
583     */
584    public int[] getColors() {
585        return mColors;
586    }
587
588    /**
589     * Returns whether the specified state is referenced in any of the state
590     * specs contained within this ColorStateList.
591     * <p>
592     * Any reference, either positive or negative {ex. ~R.attr.state_enabled},
593     * will cause this method to return {@code true}. Wildcards are not counted
594     * as references.
595     *
596     * @param state the state to search for
597     * @return {@code true} if the state if referenced, {@code false} otherwise
598     * @hide Use only as directed. For internal use only.
599     */
600    public boolean hasState(int state) {
601        final int[][] stateSpecs = mStateSpecs;
602        final int specCount = stateSpecs.length;
603        for (int specIndex = 0; specIndex < specCount; specIndex++) {
604            final int[] states = stateSpecs[specIndex];
605            final int stateCount = states.length;
606            for (int stateIndex = 0; stateIndex < stateCount; stateIndex++) {
607                if (states[stateIndex] == state || states[stateIndex] == ~state) {
608                    return true;
609                }
610            }
611        }
612        return false;
613    }
614
615    @Override
616    public String toString() {
617        return "ColorStateList{" +
618               "mThemeAttrs=" + Arrays.deepToString(mThemeAttrs) +
619               "mChangingConfigurations=" + mChangingConfigurations +
620               "mStateSpecs=" + Arrays.deepToString(mStateSpecs) +
621               "mColors=" + Arrays.toString(mColors) +
622               "mDefaultColor=" + mDefaultColor + '}';
623    }
624
625    /**
626     * Updates the default color and opacity.
627     */
628    private void onColorsChanged() {
629        int defaultColor = DEFAULT_COLOR;
630        boolean isOpaque = true;
631
632        final int[][] states = mStateSpecs;
633        final int[] colors = mColors;
634        final int N = states.length;
635        if (N > 0) {
636            defaultColor = colors[0];
637
638            for (int i = N - 1; i > 0; i--) {
639                if (states[i].length == 0) {
640                    defaultColor = colors[i];
641                    break;
642                }
643            }
644
645            for (int i = 0; i < N; i++) {
646                if (Color.alpha(colors[i]) != 0xFF) {
647                    isOpaque = false;
648                    break;
649                }
650            }
651        }
652
653        mDefaultColor = defaultColor;
654        mIsOpaque = isOpaque;
655    }
656
657    /**
658     * @return a factory that can create new instances of this ColorStateList
659     * @hide only for resource preloading
660     */
661    public ConstantState<ComplexColor> getConstantState() {
662        if (mFactory == null) {
663            mFactory = new ColorStateListFactory(this);
664        }
665        return mFactory;
666    }
667
668    private static class ColorStateListFactory extends ConstantState<ComplexColor> {
669        private final ColorStateList mSrc;
670
671        public ColorStateListFactory(ColorStateList src) {
672            mSrc = src;
673        }
674
675        @Override
676        public @Config int getChangingConfigurations() {
677            return mSrc.mChangingConfigurations;
678        }
679
680        @Override
681        public ColorStateList newInstance() {
682            return mSrc;
683        }
684
685        @Override
686        public ColorStateList newInstance(Resources res, Theme theme) {
687            return (ColorStateList) mSrc.obtainForTheme(theme);
688        }
689    }
690
691    @Override
692    public int describeContents() {
693        return 0;
694    }
695
696    @Override
697    public void writeToParcel(Parcel dest, int flags) {
698        if (canApplyTheme()) {
699            Log.w(TAG, "Wrote partially-resolved ColorStateList to parcel!");
700        }
701        final int N = mStateSpecs.length;
702        dest.writeInt(N);
703        for (int i = 0; i < N; i++) {
704            dest.writeIntArray(mStateSpecs[i]);
705        }
706        dest.writeIntArray(mColors);
707    }
708
709    public static final Parcelable.Creator<ColorStateList> CREATOR =
710            new Parcelable.Creator<ColorStateList>() {
711        @Override
712        public ColorStateList[] newArray(int size) {
713            return new ColorStateList[size];
714        }
715
716        @Override
717        public ColorStateList createFromParcel(Parcel source) {
718            final int N = source.readInt();
719            final int[][] stateSpecs = new int[N][];
720            for (int i = 0; i < N; i++) {
721                stateSpecs[i] = source.createIntArray();
722            }
723            final int[] colors = source.createIntArray();
724            return new ColorStateList(stateSpecs, colors);
725        }
726    };
727}
728