1 /* <lambda>null2 * Copyright (C) 2023 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.graphics.cts 18 19 import android.R 20 import android.app.UiModeManager 21 import android.content.Context 22 import android.graphics.Color 23 import android.provider.Settings 24 import android.util.Log 25 import android.util.Pair 26 import androidx.annotation.ColorInt 27 import androidx.core.graphics.ColorUtils 28 import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation 29 import com.android.compatibility.common.util.CddTest 30 import com.android.compatibility.common.util.FeatureUtil 31 import com.android.compatibility.common.util.SystemUtil.runShellCommand 32 import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity 33 import com.google.common.truth.Truth.assertWithMessage 34 import java.io.Serializable 35 import java.util.Arrays 36 import java.util.Locale 37 import org.junit.AfterClass 38 import org.junit.Assert 39 import org.junit.BeforeClass 40 import org.junit.FixMethodOrder 41 import org.junit.Test 42 import org.junit.runner.RunWith 43 import org.junit.runners.MethodSorters 44 import org.junit.runners.Parameterized 45 import org.xmlpull.v1.XmlPullParser 46 47 @RunWith(Parameterized::class) 48 @FixMethodOrder(MethodSorters.NAME_ASCENDING) 49 class SystemPaletteTest( 50 private val color: String, 51 private val style: String, 52 private val expectedPalette: IntArray 53 ) { 54 @Test 55 @CddTest(requirements = ["3.8.6/C-1-4,C-1-5,C-1-6"]) 56 fun a_testThemeStyles() { 57 // THEME_CUSTOMIZATION_OVERLAY_PACKAGES is not available in Wear OS 58 if (FeatureUtil.isWatch()) return 59 60 val newSetting = assurePaletteSetting() 61 62 assertWithMessage("Invalid tonal palettes for $color $style").that(newSetting).isTrue() 63 } 64 65 @Test 66 @CddTest(requirements = ["3.8.6/C-1-4,C-1-5,C-1-6"]) 67 fun b_testShades0and1000() { 68 val context = getInstrumentation().targetContext 69 70 assurePaletteSetting(context) 71 72 Log.d(TAG, "Color: $color, Style: $style") 73 74 val allPalettes = listOf( 75 getAllAccent1Colors(context), 76 getAllAccent2Colors(context), 77 getAllAccent3Colors(context), 78 getAllNeutral1Colors(context), 79 getAllNeutral2Colors(context) 80 ) 81 82 Log.d(TAG, "whiteColor: ${getAllAccent1Colors(context).joinToString()} ") 83 allPalettes.forEach { palette -> 84 assertColor(palette.first(), Color.WHITE) 85 assertColor(palette.last(), Color.BLACK) 86 } 87 } 88 89 @Test 90 @CddTest(requirements = ["3.8.6/C-1-4,C-1-5,C-1-6"]) 91 fun c_testColorsMatchExpectedLuminance() { 92 val context = getInstrumentation().targetContext 93 94 assurePaletteSetting(context) 95 96 val allPalettes = listOf( 97 getAllAccent1Colors(context), 98 getAllAccent2Colors(context), getAllAccent3Colors(context), 99 getAllNeutral1Colors(context), getAllNeutral2Colors(context) 100 ) 101 102 val labColor = doubleArrayOf(0.0, 0.0, 0.0) 103 val expectedL = doubleArrayOf( 104 100.0, 99.0, 95.0, 90.0, 80.0, 70.0, 60.0, 49.0, 40.0, 30.0, 20.0, 10.0, 0.0) 105 106 allPalettes.forEach { palette -> 107 palette.forEachIndexed { i, paletteColor -> 108 val expectedColor = expectedL[i] 109 ColorUtils.colorToLAB(paletteColor, labColor) 110 assertWithMessage( 111 "Color ${Integer.toHexString(paletteColor)} at index $i should " + 112 "have L $expectedColor in LAB space." 113 ).that(labColor[0]).isWithin(3.0).of(expectedColor) 114 } 115 } 116 } 117 118 @Test 119 @CddTest(requirements = ["3.8.6/C-1-4,C-1-5,C-1-6"]) 120 fun d_testContrastRatio() { 121 val context = getInstrumentation().targetContext 122 123 assurePaletteSetting(context) 124 125 val atLeast4dot5 = listOf( 126 Pair(0, 500), Pair(50, 600), Pair(100, 600), Pair(200, 700), 127 Pair(300, 800), Pair(400, 900), Pair(500, 1000)) 128 129 val atLeast3dot0 = listOf( 130 Pair(0, 400), Pair(50, 500), Pair(100, 500), Pair(200, 600), Pair(300, 700), 131 Pair(400, 800), Pair(500, 900), Pair(600, 1000)) 132 133 val allPalettes = listOf(getAllAccent1Colors(context), 134 getAllAccent2Colors(context), getAllAccent3Colors(context), 135 getAllNeutral1Colors(context), getAllNeutral2Colors(context)) 136 137 fun pairContrastCheck(palette: IntArray, shades: Pair<Int, Int>, contrastLevel: Double) { 138 val background = palette[shadeToArrayIndex(shades.first)] 139 val foreground = palette[shadeToArrayIndex(shades.second)] 140 val contrast = ColorUtils.calculateContrast(foreground, background) 141 142 assertWithMessage("Shade ${shades.first} (#${Integer.toHexString(background)}) " + 143 "should have at least $contrastLevel contrast ratio against " + 144 "${shades.second} (#${Integer.toHexString(foreground)}), but had $contrast" 145 ).that(contrast).isGreaterThan(contrastLevel) 146 } 147 148 allPalettes.forEach { palette -> 149 atLeast4dot5.forEach { shades -> pairContrastCheck(palette, shades, 4.5) } 150 atLeast3dot0.forEach { shades -> pairContrastCheck(palette, shades, 3.0) } 151 } 152 } 153 154 @Test 155 fun e_testDynamicColorContrast() { 156 val context = getInstrumentation().targetContext 157 158 // Ideally this should be 3.0, but there's colorspace conversion that causes rounding 159 // errors. 160 val foregroundContrast = 2.9f 161 assurePaletteSetting(context) 162 163 val bulkTest: BulkContrastTester = BulkContrastTester.of( 164 // Colors against Surface [DARK] 165 ContrastTester.ofBackgrounds(context, 166 R.color.system_surface_dark, 167 R.color.system_surface_dim_dark, 168 R.color.system_surface_bright_dark, 169 R.color.system_surface_container_dark, 170 R.color.system_surface_container_high_dark, 171 R.color.system_surface_container_highest_dark, 172 R.color.system_surface_container_low_dark, 173 R.color.system_surface_container_lowest_dark, 174 R.color.system_surface_variant_dark 175 ).andForegrounds(4.5f, 176 R.color.system_on_surface_dark, 177 R.color.system_on_surface_variant_dark, 178 R.color.system_primary_dark, 179 R.color.system_secondary_dark, 180 R.color.system_tertiary_dark, 181 R.color.system_error_dark 182 ).andForegrounds(foregroundContrast, 183 R.color.system_outline_dark 184 ), 185 186 // Colors against Surface [LIGHT] 187 ContrastTester.ofBackgrounds(context, 188 R.color.system_surface_light, 189 R.color.system_surface_dim_light, 190 R.color.system_surface_bright_light, 191 R.color.system_surface_container_light, 192 R.color.system_surface_container_high_light, 193 R.color.system_surface_container_highest_light, 194 R.color.system_surface_container_low_light, 195 R.color.system_surface_container_lowest_light, 196 R.color.system_surface_variant_light 197 ).andForegrounds(4.5f, 198 R.color.system_on_surface_light, 199 R.color.system_on_surface_variant_light, 200 R.color.system_primary_light, 201 R.color.system_secondary_light, 202 R.color.system_tertiary_light, 203 R.color.system_error_light 204 ).andForegrounds(foregroundContrast, 205 R.color.system_outline_light 206 ), 207 208 // Colors against accents [DARK] 209 ContrastTester.ofBackgrounds(context, 210 R.color.system_primary_dark).andForegrounds(4.5f, 211 R.color.system_on_primary_dark), 212 213 ContrastTester.ofBackgrounds(context, 214 R.color.system_primary_container_dark).andForegrounds(4.5f, 215 R.color.system_on_primary_container_dark), 216 217 ContrastTester.ofBackgrounds(context, 218 R.color.system_secondary_dark).andForegrounds(4.5f, 219 R.color.system_on_secondary_dark), 220 221 ContrastTester.ofBackgrounds(context, 222 R.color.system_secondary_container_dark).andForegrounds(4.5f, 223 R.color.system_on_secondary_container_dark), 224 225 ContrastTester.ofBackgrounds(context, 226 R.color.system_tertiary_dark).andForegrounds(4.5f, 227 R.color.system_on_tertiary_dark), 228 229 ContrastTester.ofBackgrounds(context, 230 R.color.system_tertiary_container_dark).andForegrounds(4.5f, 231 R.color.system_on_tertiary_container_dark), 232 233 // Colors against accents [LIGHT] 234 ContrastTester.ofBackgrounds(context, 235 R.color.system_primary_light).andForegrounds(4.5f, 236 R.color.system_on_primary_light), 237 238 ContrastTester.ofBackgrounds(context, 239 R.color.system_primary_container_light).andForegrounds(4.5f, 240 R.color.system_on_primary_container_light), 241 242 ContrastTester.ofBackgrounds(context, 243 R.color.system_secondary_light).andForegrounds(4.5f, 244 R.color.system_on_secondary_light), 245 246 ContrastTester.ofBackgrounds(context, 247 R.color.system_secondary_container_light).andForegrounds(4.5f, 248 R.color.system_on_secondary_container_light), 249 250 ContrastTester.ofBackgrounds(context, 251 R.color.system_tertiary_light).andForegrounds(4.5f, 252 R.color.system_on_tertiary_light), 253 254 ContrastTester.ofBackgrounds(context, 255 R.color.system_tertiary_container_light).andForegrounds(4.5f, 256 R.color.system_on_tertiary_container_light), 257 258 // Colors against accents [FIXED] 259 ContrastTester.ofBackgrounds(context, 260 R.color.system_primary_fixed, 261 R.color.system_primary_fixed_dim 262 ).andForegrounds(4.5f, 263 R.color.system_on_primary_fixed, 264 R.color.system_on_primary_fixed_variant 265 ), 266 267 ContrastTester.ofBackgrounds(context, 268 R.color.system_secondary_fixed, 269 R.color.system_secondary_fixed_dim 270 ).andForegrounds(4.5f, 271 R.color.system_on_secondary_fixed, 272 R.color.system_on_secondary_fixed_variant 273 ), 274 275 ContrastTester.ofBackgrounds(context, 276 R.color.system_tertiary_fixed, 277 R.color.system_tertiary_fixed_dim 278 ).andForegrounds(4.5f, 279 R.color.system_on_tertiary_fixed, 280 R.color.system_on_tertiary_fixed_variant 281 ), 282 283 // Auxiliary Colors [DARK] 284 ContrastTester.ofBackgrounds(context, 285 R.color.system_error_dark 286 ).andForegrounds(4.5f, 287 R.color.system_on_error_dark 288 ), 289 290 ContrastTester.ofBackgrounds(context, 291 R.color.system_error_container_dark 292 ).andForegrounds(4.5f, 293 R.color.system_on_error_container_dark 294 ), 295 296 // Auxiliary Colors [LIGHT] 297 ContrastTester.ofBackgrounds(context, 298 R.color.system_error_light 299 ).andForegrounds(4.5f, 300 R.color.system_on_error_light 301 ), 302 303 ContrastTester.ofBackgrounds(context, 304 R.color.system_error_container_light 305 ).andForegrounds(4.5f, 306 R.color.system_on_error_container_light 307 ) 308 ) 309 bulkTest.run() 310 assertWithMessage(bulkTest.allMessages).that(bulkTest.testPassed).isTrue() 311 } 312 313 private fun getAllAccent1Colors(context: Context): IntArray { 314 return getAllResourceColors( 315 context, 316 R.color.system_accent1_0, 317 R.color.system_accent1_10, 318 R.color.system_accent1_50, 319 R.color.system_accent1_100, 320 R.color.system_accent1_200, 321 R.color.system_accent1_300, 322 R.color.system_accent1_400, 323 R.color.system_accent1_500, 324 R.color.system_accent1_600, 325 R.color.system_accent1_700, 326 R.color.system_accent1_800, 327 R.color.system_accent1_900, 328 R.color.system_accent1_1000 329 ) 330 } 331 332 private fun getAllAccent2Colors(context: Context): IntArray { 333 return getAllResourceColors( 334 context, 335 R.color.system_accent2_0, 336 R.color.system_accent2_10, 337 R.color.system_accent2_50, 338 R.color.system_accent2_100, 339 R.color.system_accent2_200, 340 R.color.system_accent2_300, 341 R.color.system_accent2_400, 342 R.color.system_accent2_500, 343 R.color.system_accent2_600, 344 R.color.system_accent2_700, 345 R.color.system_accent2_800, 346 R.color.system_accent2_900, 347 R.color.system_accent2_1000 348 ) 349 } 350 351 private fun getAllAccent3Colors(context: Context): IntArray { 352 return getAllResourceColors( 353 context, 354 R.color.system_accent3_0, 355 R.color.system_accent3_10, 356 R.color.system_accent3_50, 357 R.color.system_accent3_100, 358 R.color.system_accent3_200, 359 R.color.system_accent3_300, 360 R.color.system_accent3_400, 361 R.color.system_accent3_500, 362 R.color.system_accent3_600, 363 R.color.system_accent3_700, 364 R.color.system_accent3_800, 365 R.color.system_accent3_900, 366 R.color.system_accent3_1000 367 ) 368 } 369 370 private fun getAllNeutral1Colors(context: Context): IntArray { 371 return getAllResourceColors( 372 context, 373 R.color.system_neutral1_0, 374 R.color.system_neutral1_10, 375 R.color.system_neutral1_50, 376 R.color.system_neutral1_100, 377 R.color.system_neutral1_200, 378 R.color.system_neutral1_300, 379 R.color.system_neutral1_400, 380 R.color.system_neutral1_500, 381 R.color.system_neutral1_600, 382 R.color.system_neutral1_700, 383 R.color.system_neutral1_800, 384 R.color.system_neutral1_900, 385 R.color.system_neutral1_1000 386 ) 387 } 388 389 private fun getAllNeutral2Colors(context: Context): IntArray { 390 return getAllResourceColors( 391 context, 392 R.color.system_neutral2_0, 393 R.color.system_neutral2_10, 394 R.color.system_neutral2_50, 395 R.color.system_neutral2_100, 396 R.color.system_neutral2_200, 397 R.color.system_neutral2_300, 398 R.color.system_neutral2_400, 399 R.color.system_neutral2_500, 400 R.color.system_neutral2_600, 401 R.color.system_neutral2_700, 402 R.color.system_neutral2_800, 403 R.color.system_neutral2_900, 404 R.color.system_neutral2_1000 405 ) 406 } 407 408 // Helper functions 409 410 private fun assurePaletteSetting( 411 context: Context = getInstrumentation().targetContext 412 ): Boolean { 413 if (checkExpectedPalette(context).size > 0) { 414 return setExpectedPalette(context) 415 } 416 417 return true 418 } 419 420 private fun setExpectedPalette(context: Context): Boolean { 421 422 // Update setting, so system colors will change 423 runWithShellPermissionIdentity { 424 Settings.Secure.putString( 425 context.contentResolver, 426 Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES, 427 "{\"android.theme.customization.system_palette\":\"${color}\"," + 428 "\"android.theme.customization.theme_style\":\"${style}\"}" 429 ) 430 } 431 432 return conditionTimeoutCheck({ 433 val mismatches = checkExpectedPalette(context) 434 val noMismatches = mismatches.size == 0 435 436 if (DEBUG) { 437 val setting = Settings.Secure.getString( 438 context.contentResolver, 439 Settings.Secure.THEME_CUSTOMIZATION_OVERLAY_PACKAGES) 440 441 Log.d(TAG, 442 if (noMismatches) 443 "Palette $setting is correctly set with colors: " + 444 intArrayToHexString(expectedPalette) 445 else """ 446 Setting: 447 $setting 448 Mismatches [index](color, expected): 449 ${ 450 mismatches.map { (i, current, expected) -> 451 val c = if (current != null) 452 Integer.toHexString(current) else "Null" 453 val e = if (expected != null) 454 Integer.toHexString(expected) else "Null" 455 456 return@map "[$i]($c, $e) " 457 }.joinToString(" ") 458 } 459 """.trimIndent() 460 ) 461 } 462 463 return@conditionTimeoutCheck noMismatches 464 }) 465 } 466 467 private fun checkExpectedPalette( 468 context: Context, 469 ): MutableList<Triple<Int, Int?, Int?>> { 470 val allColors = IntArray(65) 471 System.arraycopy(getAllAccent1Colors(context), 0, allColors, 0, 13) 472 System.arraycopy(getAllAccent2Colors(context), 0, allColors, 13, 13) 473 System.arraycopy(getAllAccent3Colors(context), 0, allColors, 26, 13) 474 System.arraycopy(getAllNeutral1Colors(context), 0, allColors, 39, 13) 475 System.arraycopy(getAllNeutral2Colors(context), 0, allColors, 52, 13) 476 477 return getArraysMismatch( allColors, expectedPalette ) 478 } 479 480 /** 481 * Convert the Material shade to an array position. 482 * 483 * @param shade Shade from 0 to 1000. 484 * @return index in array 485 * @see .getAllAccent1Colors 486 * @see .getAllNeutral1Colors 487 */ 488 private fun shadeToArrayIndex(shade: Int): Int { 489 return when (shade) { 490 0 -> 0 491 10 -> 1 492 50 -> 2 493 else -> { 494 shade / 100 + 2 495 } 496 } 497 } 498 499 private fun assertColor(@ColorInt observed: Int, @ColorInt expected: Int) { 500 Assert.assertEquals( 501 "Color = ${Integer.toHexString(observed)}, " + 502 "${Integer.toHexString(expected)} expected", 503 expected, 504 observed 505 ) 506 } 507 508 private fun getAllResourceColors(context: Context, vararg resources: Int): IntArray { 509 if (resources.size != 13) throw Exception("Color palettes must be 13 in size") 510 return resources.map { resId -> context.getColor(resId) }.toIntArray() 511 } 512 513 private fun intArrayToHexString(src: IntArray): String { 514 return src.joinToString { n -> Integer.toHexString(n) } 515 } 516 517 private fun conditionTimeoutCheck( 518 evalFunction: () -> Boolean, 519 totalTimeout: Int = 15000, 520 loopTime: Int = 1000 521 ): Boolean { 522 if (totalTimeout < loopTime) throw Exception("Loop time must be smaller than total time.") 523 524 var remainingTime = totalTimeout 525 526 while (remainingTime > 0) { 527 if (evalFunction()) return true 528 529 Thread.sleep(loopTime.coerceAtMost(remainingTime).toLong()) 530 remainingTime -= loopTime 531 } 532 533 return false 534 } 535 536 private fun getArraysMismatch(a: IntArray, b: IntArray): MutableList<Triple<Int, Int?, Int?>> { 537 val len = a.size.coerceAtLeast(b.size) 538 val mismatches: MutableList<Triple<Int, Int?, Int?>> = mutableListOf() 539 540 repeat(len) { i -> 541 val valueA = if (a.size >= i + 1) a[i] else null 542 val valueB = if (b.size >= i + 1) b[i] else null 543 544 if (valueA != valueB) mismatches.add(Triple(i, valueA, valueB)) 545 } 546 547 return mismatches 548 } 549 550 // Helper Classes 551 552 private class ContrastTester private constructor( 553 var mContext: Context, 554 vararg var mForegrounds: Int 555 ) { 556 var mBgGroups = ArrayList<Background>() 557 558 fun checkContrastLevels(): ArrayList<String> { 559 val newFailMessages = ArrayList<String>() 560 mBgGroups.forEach { background -> 561 newFailMessages.addAll(background.checkContrast(mForegrounds)) 562 } 563 564 return newFailMessages 565 } 566 567 fun andForegrounds(contrastLevel: Float, vararg res: Int): ContrastTester { 568 mBgGroups.add(Background(contrastLevel, *res)) 569 return this 570 } 571 572 private inner class Background internal constructor( 573 private val mContrasLevel: Float, 574 private vararg val mEntries: Int 575 ) { 576 fun checkContrast(foregrounds: IntArray): ArrayList<String> { 577 val newFailMessages = ArrayList<String>() 578 val res = mContext.resources 579 580 foregrounds.forEach { fgRes -> 581 mEntries.forEach { bgRes -> 582 if (!checkPair(mContext, mContrasLevel, fgRes, bgRes)) { 583 val background = mContext.getColor(bgRes) 584 val foreground = mContext.getColor(fgRes) 585 val contrast = ColorUtils.calculateContrast(foreground, background) 586 val msg = "Background Color '${res.getResourceName(bgRes)}'" + 587 "(#${Integer.toHexString(mContext.getColor(bgRes))}) " + 588 "should have at least $mContrasLevel " + 589 "contrast ratio against Foreground Color '" + 590 res.getResourceName(fgRes) + 591 "' (#${Integer.toHexString(mContext.getColor(fgRes))}) " + 592 " but had $contrast" 593 594 newFailMessages.add(msg) 595 } 596 } 597 } 598 599 return newFailMessages 600 } 601 } 602 603 companion object { 604 fun ofBackgrounds(context: Context, vararg res: Int): ContrastTester { 605 return ContrastTester(context, *res) 606 } 607 608 fun checkPair(context: Context, minContrast: Float, fgRes: Int, bgRes: Int): Boolean { 609 val background = context.getColor(bgRes) 610 val foreground = context.getColor(fgRes) 611 val contrast = ColorUtils.calculateContrast(foreground, background) 612 return contrast > minContrast 613 } 614 } 615 } 616 617 private class BulkContrastTester private constructor(vararg testsArgs: ContrastTester) { 618 private val tests = testsArgs 619 private val errorMessages: MutableList<String> = mutableListOf() 620 621 val testPassed: Boolean get() = errorMessages.isEmpty() 622 623 val allMessages: String 624 get() = 625 if (testPassed) "Test OK" else errorMessages.joinToString("\n") 626 627 fun run() { 628 errorMessages.clear() 629 tests.forEach { test -> errorMessages.addAll(test.checkContrastLevels()) } 630 } 631 632 companion object { 633 fun of(vararg testers: ContrastTester): BulkContrastTester { 634 return BulkContrastTester(*testers) 635 } 636 } 637 } 638 639 companion object { 640 private const val TAG = "SystemPaletteTest" 641 private const val DEBUG = true 642 643 private var initialContrast: Float = 0f 644 645 @BeforeClass 646 fun setUp() { 647 initialContrast = getInstrumentation() 648 .targetContext 649 .getSystemService(UiModeManager::class.java) 650 .contrast 651 putContrastInSettings(0f) 652 } 653 654 @AfterClass 655 fun tearDown() { 656 putContrastInSettings(initialContrast) 657 } 658 659 private fun putContrastInSettings(contrast: Float) { 660 runShellCommand("settings put secure contrast_level $contrast") 661 } 662 663 @Parameterized.Parameters(name = "Palette {1} with color {0}") 664 @JvmStatic 665 fun testData(): List<Array<Serializable>> { 666 val context: Context = getInstrumentation().targetContext 667 val parser: XmlPullParser = 668 context.resources.getXml(android.graphics.cts.R.xml.valid_themes) 669 670 val dataList: MutableList<Array<Serializable>> = mutableListOf() 671 672 try { 673 parser.next() 674 parser.next() 675 parser.require(XmlPullParser.START_TAG, null, "themes") 676 while (parser.next() != XmlPullParser.END_TAG) { 677 parser.require(XmlPullParser.START_TAG, null, "theme") 678 val color = parser.getAttributeValue(null, "color") 679 while (parser.next() != XmlPullParser.END_TAG) { 680 val styleName = parser.name 681 parser.next() 682 val colors = Arrays.stream( 683 parser.text.split(",".toRegex()).dropLastWhile { it.isEmpty() } 684 .toTypedArray()) 685 .mapToInt { s: String -> 686 Color.parseColor( 687 "#$s" 688 ) 689 } 690 .toArray() 691 parser.next() 692 parser.require(XmlPullParser.END_TAG, null, styleName) 693 dataList.add( 694 arrayOf( 695 color, 696 styleName.uppercase(Locale.getDefault()), 697 colors 698 ) 699 ) 700 } 701 } 702 } catch (e: Exception) { 703 throw RuntimeException("Error parsing xml", e) 704 } 705 706 return dataList 707 } 708 } 709 } 710