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