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