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