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