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 * <selector xmlns:android="http://schemas.android.com/apk/res/android"> 58 * <item android:state_focused="true" 59 * android:color="@color/sample_focused" /> 60 * <item android:state_pressed="true" 61 * android:state_enabled="false" 62 * android:color="@color/sample_disabled_pressed" /> 63 * <item android:state_enabled="false" 64 * android:color="@color/sample_disabled_not_pressed" /> 65 * <item android:color="@color/sample_default" /> 66 * </selector> 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 * <item android:state_focused="true" 84 * android:color="@color/sample_focused" /> 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 * <item android:state_enabled="false" 114 * android:color="?android:attr/colorAccent" 115 * android:alpha="0.5" /> 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 * <item android:state_enabled="false" 126 * android:color="?android:attr/colorAccent" 127 * android:lStar="50" /> 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 <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