1 /*
2  * Copyright 2019 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 androidx.compose.ui.graphics.vector.compat
18 
19 import android.content.res.ColorStateList
20 import android.content.res.Resources
21 import android.content.res.TypedArray
22 import android.util.AttributeSet
23 import android.util.TypedValue
24 import androidx.annotation.ColorInt
25 import androidx.annotation.StyleableRes
26 import androidx.compose.ui.graphics.BlendMode
27 import androidx.compose.ui.graphics.Brush
28 import androidx.compose.ui.graphics.Color
29 import androidx.compose.ui.graphics.PathFillType
30 import androidx.compose.ui.graphics.ShaderBrush
31 import androidx.compose.ui.graphics.SolidColor
32 import androidx.compose.ui.graphics.StrokeCap
33 import androidx.compose.ui.graphics.StrokeJoin
34 import androidx.compose.ui.graphics.vector.DefaultPivotX
35 import androidx.compose.ui.graphics.vector.DefaultPivotY
36 import androidx.compose.ui.graphics.vector.DefaultRotation
37 import androidx.compose.ui.graphics.vector.DefaultScaleX
38 import androidx.compose.ui.graphics.vector.DefaultScaleY
39 import androidx.compose.ui.graphics.vector.DefaultTranslationX
40 import androidx.compose.ui.graphics.vector.DefaultTranslationY
41 import androidx.compose.ui.graphics.vector.EmptyPath
42 import androidx.compose.ui.graphics.vector.ImageVector
43 import androidx.compose.ui.graphics.vector.PathNode
44 import androidx.compose.ui.graphics.vector.PathParser
45 import androidx.compose.ui.unit.dp
46 import androidx.core.content.res.ComplexColorCompat
47 import androidx.core.content.res.TypedArrayUtils
48 import org.xmlpull.v1.XmlPullParser
49 import org.xmlpull.v1.XmlPullParserException
50 
51 private const val LINECAP_BUTT = 0
52 private const val LINECAP_ROUND = 1
53 private const val LINECAP_SQUARE = 2
54 
55 private const val LINEJOIN_MITER = 0
56 private const val LINEJOIN_ROUND = 1
57 private const val LINEJOIN_BEVEL = 2
58 
59 private val FILL_TYPE_WINDING = 0
60 
61 private const val SHAPE_CLIP_PATH = "clip-path"
62 private const val SHAPE_GROUP = "group"
63 private const val SHAPE_PATH = "path"
64 
getStrokeLineCapnull65 private fun getStrokeLineCap(id: Int, defValue: StrokeCap = StrokeCap.Butt): StrokeCap =
66     when (id) {
67         LINECAP_BUTT -> StrokeCap.Butt
68         LINECAP_ROUND -> StrokeCap.Round
69         LINECAP_SQUARE -> StrokeCap.Square
70         else -> defValue
71     }
72 
getStrokeLineJoinnull73 private fun getStrokeLineJoin(id: Int, defValue: StrokeJoin = StrokeJoin.Miter): StrokeJoin =
74     when (id) {
75         LINEJOIN_MITER -> StrokeJoin.Miter
76         LINEJOIN_ROUND -> StrokeJoin.Round
77         LINEJOIN_BEVEL -> StrokeJoin.Bevel
78         else -> defValue
79     }
80 
isAtEndnull81 internal fun XmlPullParser.isAtEnd(): Boolean =
82     eventType == XmlPullParser.END_DOCUMENT || (depth < 1 && eventType == XmlPullParser.END_TAG)
83 
84 /**
85  * @param nestedGroups The number of additionally nested VectorGroups to represent clip paths.
86  * @return The number of nested VectorGroups that are not `<group>` in XML, but represented as
87  *   VectorGroup in the [builder]. These are also popped when this function sees `</group>`.
88  */
89 internal fun AndroidVectorParser.parseCurrentVectorNode(
90     res: Resources,
91     attrs: AttributeSet,
92     theme: Resources.Theme? = null,
93     builder: ImageVector.Builder,
94     nestedGroups: Int
95 ): Int {
96     when (xmlParser.eventType) {
97         XmlPullParser.START_TAG -> {
98             when (xmlParser.name) {
99                 SHAPE_PATH -> {
100                     parsePath(res, theme, attrs, builder)
101                 }
102                 SHAPE_CLIP_PATH -> {
103                     parseClipPath(res, theme, attrs, builder)
104                     return nestedGroups + 1
105                 }
106                 SHAPE_GROUP -> {
107                     parseGroup(res, theme, attrs, builder)
108                 }
109             }
110         }
111         XmlPullParser.END_TAG -> {
112             if (SHAPE_GROUP == xmlParser.name) {
113                 repeat(nestedGroups + 1) { builder.clearGroup() }
114                 return 0
115             }
116         }
117     }
118     return nestedGroups
119 }
120 
121 /** Helper method to seek to the first tag within the VectorDrawable xml asset */
122 @Throws(XmlPullParserException::class)
seekToStartTagnull123 internal fun XmlPullParser.seekToStartTag(): XmlPullParser {
124     var type = next()
125     while (type != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) {
126         // Empty loop
127         type = next()
128     }
129     if (type != XmlPullParser.START_TAG) {
130         throw XmlPullParserException("No start tag found")
131     }
132     return this
133 }
134 
createVectorImageBuildernull135 internal fun AndroidVectorParser.createVectorImageBuilder(
136     res: Resources,
137     theme: Resources.Theme?,
138     attrs: AttributeSet
139 ): ImageVector.Builder {
140     val vectorAttrs =
141         obtainAttributes(
142             res,
143             theme,
144             attrs,
145             AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_TYPE_ARRAY
146         )
147 
148     val autoMirror =
149         getNamedBoolean(
150             vectorAttrs,
151             "autoMirrored",
152             AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_AUTO_MIRRORED,
153             false
154         )
155 
156     val viewportWidth =
157         getNamedFloat(
158             vectorAttrs,
159             "viewportWidth",
160             AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_VIEWPORT_WIDTH,
161             0.0f
162         )
163 
164     val viewportHeight =
165         getNamedFloat(
166             vectorAttrs,
167             "viewportHeight",
168             AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_VIEWPORT_HEIGHT,
169             0.0f
170         )
171 
172     if (viewportWidth <= 0) {
173         throw XmlPullParserException(
174             vectorAttrs.positionDescription + "<VectorGraphic> tag requires viewportWidth > 0"
175         )
176     } else if (viewportHeight <= 0) {
177         throw XmlPullParserException(
178             vectorAttrs.positionDescription + "<VectorGraphic> tag requires viewportHeight > 0"
179         )
180     }
181 
182     val defaultWidth =
183         getDimension(vectorAttrs, AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_WIDTH, 0.0f)
184     val defaultHeight =
185         getDimension(vectorAttrs, AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_HEIGHT, 0.0f)
186 
187     val tintColor =
188         if (vectorAttrs.hasValue(AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_TINT)) {
189             val value = TypedValue()
190             vectorAttrs.getValue(AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_TINT, value)
191             // Unable to parse theme attributes outside of the framework here.
192             // This is a similar limitation to VectorDrawableCompat's parsing logic within
193             // updateStateFromTypedArray as TypedArray#extractThemeAttrs is not a public API
194             // ignore tint colors provided from the theme itself.
195             if (value.type == TypedValue.TYPE_ATTRIBUTE) {
196                 Color.Unspecified
197             } else {
198                 val tintColorStateList =
199                     getNamedColorStateList(
200                         vectorAttrs,
201                         theme,
202                         "tint",
203                         AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_TINT
204                     )
205                 if (tintColorStateList != null) {
206                     Color(tintColorStateList.defaultColor)
207                 } else {
208                     Color.Unspecified
209                 }
210             }
211         } else {
212             Color.Unspecified
213         }
214 
215     val blendModeValue =
216         getInt(vectorAttrs, AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_TINT_MODE, -1)
217     val tintBlendMode =
218         if (blendModeValue != -1) {
219             when (blendModeValue) {
220                 3 -> BlendMode.SrcOver
221                 5 -> BlendMode.SrcIn
222                 9 -> BlendMode.SrcAtop
223                 // b/73224934 PorterDuff Multiply maps to Skia Modulate so actually
224                 // return BlendMode.MODULATE here
225                 14 -> BlendMode.Modulate
226                 15 -> BlendMode.Screen
227                 16 -> BlendMode.Plus
228                 else -> BlendMode.SrcIn
229             }
230         } else {
231             BlendMode.SrcIn
232         }
233 
234     val defaultWidthDp = (defaultWidth / res.displayMetrics.density).dp
235     val defaultHeightDp = (defaultHeight / res.displayMetrics.density).dp
236 
237     vectorAttrs.recycle()
238 
239     return ImageVector.Builder(
240         defaultWidth = defaultWidthDp,
241         defaultHeight = defaultHeightDp,
242         viewportWidth = viewportWidth,
243         viewportHeight = viewportHeight,
244         tintColor = tintColor,
245         tintBlendMode = tintBlendMode,
246         autoMirror = autoMirror
247     )
248 }
249 
250 @Throws(IllegalArgumentException::class)
parsePathnull251 internal fun AndroidVectorParser.parsePath(
252     res: Resources,
253     theme: Resources.Theme?,
254     attrs: AttributeSet,
255     builder: ImageVector.Builder
256 ) {
257     val a =
258         obtainAttributes(res, theme, attrs, AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH)
259 
260     val hasPathData = TypedArrayUtils.hasAttribute(xmlParser, "pathData")
261     if (!hasPathData) {
262         // If there is no pathData in the VPath tag, then this is an empty VPath,
263         // nothing need to be drawn.
264         throw IllegalArgumentException("No path data available")
265     }
266 
267     val name: String =
268         getString(a, AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_NAME) ?: ""
269 
270     val pathStr = getString(a, AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_PATH_DATA)
271     val pathData: List<PathNode> =
272         if (pathStr == null) {
273             EmptyPath
274         } else {
275             pathParser.pathStringToNodes(pathStr)
276         }
277 
278     val fillColor =
279         getNamedComplexColor(
280             a,
281             theme,
282             "fillColor",
283             AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_FILL_COLOR,
284             0
285         )
286     val fillAlpha =
287         getNamedFloat(
288             a,
289             "fillAlpha",
290             AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_FILL_ALPHA,
291             1.0f
292         )
293     val lineCap =
294         getNamedInt(
295             a,
296             "strokeLineCap",
297             AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_LINE_CAP,
298             -1
299         )
300     val strokeLineCap = getStrokeLineCap(lineCap, StrokeCap.Butt)
301     val lineJoin =
302         getNamedInt(
303             a,
304             "strokeLineJoin",
305             AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_LINE_JOIN,
306             -1
307         )
308     val strokeLineJoin = getStrokeLineJoin(lineJoin, StrokeJoin.Bevel)
309     val strokeMiterLimit =
310         getNamedFloat(
311             a,
312             "strokeMiterLimit",
313             AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_MITER_LIMIT,
314             1.0f
315         )
316     val strokeColor =
317         getNamedComplexColor(
318             a,
319             theme,
320             "strokeColor",
321             AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_COLOR,
322             0
323         )
324     val strokeAlpha =
325         getNamedFloat(
326             a,
327             "strokeAlpha",
328             AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_ALPHA,
329             1.0f
330         )
331     val strokeLineWidth =
332         getNamedFloat(
333             a,
334             "strokeWidth",
335             AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_STROKE_WIDTH,
336             1.0f
337         )
338 
339     val trimPathEnd =
340         getNamedFloat(
341             a,
342             "trimPathEnd",
343             AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_TRIM_PATH_END,
344             1.0f
345         )
346     val trimPathOffset =
347         getNamedFloat(
348             a,
349             "trimPathOffset",
350             AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_TRIM_PATH_OFFSET,
351             0.0f
352         )
353     val trimPathStart =
354         getNamedFloat(
355             a,
356             "trimPathStart",
357             AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_TRIM_PATH_START,
358             0.0f
359         )
360 
361     val fillRule =
362         getNamedInt(
363             a,
364             "fillType",
365             AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_PATH_TRIM_PATH_FILLTYPE,
366             FILL_TYPE_WINDING
367         )
368 
369     a.recycle()
370 
371     val fillBrush = obtainBrushFromComplexColor(fillColor)
372     val strokeBrush = obtainBrushFromComplexColor(strokeColor)
373     val fillPathType = if (fillRule == 0) PathFillType.NonZero else PathFillType.EvenOdd
374 
375     builder.addPath(
376         pathData,
377         fillPathType,
378         name,
379         fillBrush,
380         fillAlpha,
381         strokeBrush,
382         strokeAlpha,
383         strokeLineWidth,
384         strokeLineCap,
385         strokeLineJoin,
386         strokeMiterLimit,
387         trimPathStart,
388         trimPathEnd,
389         trimPathOffset
390     )
391 }
392 
obtainBrushFromComplexColornull393 private fun obtainBrushFromComplexColor(complexColor: ComplexColorCompat): Brush? =
394     if (complexColor.willDraw()) {
395         val shader = complexColor.shader
396         if (shader != null) {
397             ShaderBrush(shader)
398         } else {
399             SolidColor(Color(complexColor.color))
400         }
401     } else {
402         null
403     }
404 
parseClipPathnull405 internal fun AndroidVectorParser.parseClipPath(
406     res: Resources,
407     theme: Resources.Theme?,
408     attrs: AttributeSet,
409     builder: ImageVector.Builder
410 ) {
411     val a =
412         obtainAttributes(
413             res,
414             theme,
415             attrs,
416             AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_CLIP_PATH,
417         )
418 
419     val name: String =
420         getString(a, AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_CLIP_PATH_NAME) ?: ""
421     val pathStr = getString(a, AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_CLIP_PATH_PATH_DATA)
422     val pathData = if (pathStr == null) EmptyPath else pathParser.pathStringToNodes(pathStr)
423     a.recycle()
424 
425     // <clip-path> is parsed out as an additional VectorGroup.
426     // This allows us to replicate the behavior of VectorDrawable where <clip-path> only affects
427     // <path> that comes after it in <group>.
428     builder.addGroup(name = name, clipPathData = pathData)
429 }
430 
parseGroupnull431 internal fun AndroidVectorParser.parseGroup(
432     res: Resources,
433     theme: Resources.Theme?,
434     attrs: AttributeSet,
435     builder: ImageVector.Builder
436 ) {
437     val a =
438         obtainAttributes(res, theme, attrs, AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP)
439 
440     // Account for any configuration changes.
441     // mChangingConfigurations |= Utils.getChangingConfigurations(a);
442 
443     // Extract the theme attributes, if any.
444     // mThemeAttrs = null // TODO TINT THEME Not supported yet a.extractThemeAttrs();
445 
446     // This is added in API 11
447     val rotate =
448         getNamedFloat(
449             a,
450             "rotation",
451             AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_ROTATION,
452             DefaultRotation
453         )
454 
455     val pivotX =
456         getFloat(a, AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_PIVOT_X, DefaultPivotX)
457     val pivotY =
458         getFloat(a, AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_PIVOT_Y, DefaultPivotY)
459 
460     // This is added in API 11
461     val scaleX =
462         getNamedFloat(
463             a,
464             "scaleX",
465             AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_SCALE_X,
466             DefaultScaleX
467         )
468 
469     // This is added in API 11
470     val scaleY =
471         getNamedFloat(
472             a,
473             "scaleY",
474             AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_SCALE_Y,
475             DefaultScaleY
476         )
477 
478     val translateX =
479         getNamedFloat(
480             a,
481             "translateX",
482             AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_TRANSLATE_X,
483             DefaultTranslationX
484         )
485     val translateY =
486         getNamedFloat(
487             a,
488             "translateY",
489             AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_TRANSLATE_Y,
490             DefaultTranslationY
491         )
492 
493     val name: String =
494         getString(a, AndroidVectorResources.STYLEABLE_VECTOR_DRAWABLE_GROUP_NAME) ?: ""
495 
496     a.recycle()
497 
498     builder.addGroup(
499         name,
500         rotate,
501         pivotX,
502         pivotY,
503         scaleX,
504         scaleY,
505         translateX,
506         translateY,
507         EmptyPath
508     )
509 }
510 
511 /**
512  * Class responsible for parsing vector graphics attributes and keeping track of which attributes
513  * depend on a configuration parameter. This is used to determine which cached vector graphics
514  * objects can be pruned during a configuration change as the vector graphic would need to be
515  * reloaded if a corresponding configuration parameter changed.
516  *
517  * For example, if the fill color for a path was dependent on the orientation of the device the
518  * config flag would include the value [android.content.pm.ActivityInfo.CONFIG_ORIENTATION]
519  */
520 internal data class AndroidVectorParser(val xmlParser: XmlPullParser, var config: Int = 0) {
521     @JvmField internal val pathParser = PathParser()
522 
updateConfignull523     private fun updateConfig(resConfig: Int) {
524         config = config or resConfig
525     }
526 
527     /**
528      * Helper method to parse the attributre set update the configuration flags this that these
529      * attributes may depend on
530      */
obtainAttributesnull531     fun obtainAttributes(
532         res: Resources,
533         theme: Resources.Theme?,
534         set: AttributeSet,
535         attrs: IntArray
536     ): TypedArray {
537         val typedArray = TypedArrayUtils.obtainAttributes(res, theme, set, attrs)
538         updateConfig(typedArray.changingConfigurations)
539         return typedArray
540     }
541 
542     /**
543      * Helper method to parse an int with the given resource identifier and attribute name as well
544      * as update the configuration flags this int may depend on.
545      */
getNamedIntnull546     fun getNamedInt(
547         typedArray: TypedArray,
548         attrName: String,
549         @StyleableRes resId: Int,
550         defaultValue: Int
551     ): Int {
552         with(typedArray) {
553             val result = TypedArrayUtils.getNamedInt(this, xmlParser, attrName, resId, defaultValue)
554             updateConfig(changingConfigurations)
555             return result
556         }
557     }
558 
559     /**
560      * Helper method to parse a float with the given resource identifier and attribute name as well
561      * as update the configuration flags this float may depend on.
562      */
getNamedFloatnull563     fun getNamedFloat(
564         typedArray: TypedArray,
565         attrName: String,
566         @StyleableRes resId: Int,
567         defaultValue: Float
568     ): Float {
569         with(typedArray) {
570             val result =
571                 TypedArrayUtils.getNamedFloat(this, xmlParser, attrName, resId, defaultValue)
572             updateConfig(changingConfigurations)
573             return result
574         }
575     }
576 
577     /**
578      * Helper method to parse a boolean with the given resource identifier and attribute name as
579      * well as update the configuration flags this float may depend on.
580      */
getNamedBooleannull581     fun getNamedBoolean(
582         typedArray: TypedArray,
583         attrName: String,
584         @StyleableRes resId: Int,
585         defaultValue: Boolean
586     ): Boolean {
587         with(typedArray) {
588             val result =
589                 TypedArrayUtils.getNamedBoolean(this, xmlParser, attrName, resId, defaultValue)
590             updateConfig(changingConfigurations)
591             return result
592         }
593     }
594 
595     /**
596      * Helper method to parse a float with the given resource identifier and update the
597      * configuration flags this float may depend on.
598      */
getFloatnull599     fun getFloat(typedArray: TypedArray, index: Int, defaultValue: Float): Float {
600         with(typedArray) {
601             val result = getFloat(index, defaultValue)
602             updateConfig(changingConfigurations)
603             return result
604         }
605     }
606 
607     /**
608      * Helper method to parse an int with the given resource identifier and update the configuration
609      * flags this int may depend on.
610      */
getIntnull611     fun getInt(typedArray: TypedArray, index: Int, defaultValue: Int): Int {
612         with(typedArray) {
613             val result = getInt(index, defaultValue)
614             updateConfig(changingConfigurations)
615             return result
616         }
617     }
618 
619     /**
620      * Helper method to parse a String with the given resource identifier and update the
621      * configuration flags this String may depend on.
622      */
getStringnull623     fun getString(typedArray: TypedArray, index: Int): String? {
624         with(typedArray) {
625             val result = getString(index)
626             updateConfig(changingConfigurations)
627             return result
628         }
629     }
630 
631     /**
632      * Helper method to parse a dimension with the given resource identifier and update the
633      * configuration flags this dimension may depend on.
634      */
getDimensionnull635     fun getDimension(typedArray: TypedArray, index: Int, defValue: Float): Float {
636         with(typedArray) {
637             val result = getDimension(index, defValue)
638             updateConfig(changingConfigurations)
639             return result
640         }
641     }
642 
643     /**
644      * Helper method to parse a ComplexColor with the given resource identifier and name as well as
645      * update the configuration flags this ComplexColor may depend on.
646      */
getNamedComplexColornull647     fun getNamedComplexColor(
648         typedArray: TypedArray,
649         theme: Resources.Theme?,
650         attrName: String,
651         @StyleableRes resId: Int,
652         @ColorInt defaultValue: Int
653     ): ComplexColorCompat {
654         with(typedArray) {
655             val result =
656                 TypedArrayUtils.getNamedComplexColor(
657                     this,
658                     xmlParser,
659                     theme,
660                     attrName,
661                     resId,
662                     defaultValue
663                 )
664             updateConfig(changingConfigurations)
665             return result
666         }
667     }
668 
669     /**
670      * Helper method to parse a ColorStateList with the given resource identifier and name as well
671      * as update the configuration flags this ColorStateList may depend on.
672      */
getNamedColorStateListnull673     fun getNamedColorStateList(
674         typedArray: TypedArray,
675         theme: Resources.Theme?,
676         attrName: String,
677         @StyleableRes resId: Int
678     ): ColorStateList? {
679         with(typedArray) {
680             val result =
681                 TypedArrayUtils.getNamedColorStateList(
682                     typedArray,
683                     xmlParser,
684                     theme,
685                     attrName,
686                     resId
687                 )
688             updateConfig(changingConfigurations)
689             return result
690         }
691     }
692 }
693