• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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