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.res
18 
19 import android.content.res.Configuration
20 import android.content.res.Resources
21 import android.content.res.XmlResourceParser
22 import android.util.TypedValue
23 import android.util.Xml
24 import androidx.annotation.DrawableRes
25 import androidx.compose.runtime.Composable
26 import androidx.compose.runtime.remember
27 import androidx.compose.ui.graphics.vector.ImageVector
28 import androidx.compose.ui.graphics.vector.compat.AndroidVectorParser
29 import androidx.compose.ui.graphics.vector.compat.createVectorImageBuilder
30 import androidx.compose.ui.graphics.vector.compat.isAtEnd
31 import androidx.compose.ui.graphics.vector.compat.parseCurrentVectorNode
32 import androidx.compose.ui.graphics.vector.compat.seekToStartTag
33 import androidx.compose.ui.platform.LocalContext
34 import androidx.compose.ui.platform.LocalResources
35 import java.lang.ref.WeakReference
36 import org.xmlpull.v1.XmlPullParserException
37 
38 /**
39  * Load an ImageVector from a vector resource.
40  *
41  * This function is intended to be used for when low-level ImageVector-specific functionality is
42  * required. For simply displaying onscreen, the vector/bitmap-agnostic [painterResource] is
43  * recommended instead.
44  *
45  * @param id the resource identifier
46  * @return the vector data associated with the resource
47  */
48 @Composable
ImageVectornull49 fun ImageVector.Companion.vectorResource(@DrawableRes id: Int): ImageVector {
50     val context = LocalContext.current
51     val res = LocalResources.current
52     val theme = context.theme
53 
54     return remember(id, res, theme, res.configuration) { vectorResource(theme, res, id) }
55 }
56 
57 @Throws(XmlPullParserException::class)
ImageVectornull58 fun ImageVector.Companion.vectorResource(
59     theme: Resources.Theme? = null,
60     res: Resources,
61     resId: Int
62 ): ImageVector {
63     val value = TypedValue()
64     res.getValue(resId, value, true)
65 
66     return loadVectorResourceInner(
67             theme,
68             res,
69             res.getXml(resId).apply { seekToStartTag() },
70             value.changingConfigurations
71         )
72         .imageVector
73 }
74 
75 /**
76  * Helper method that parses a vector asset from the given [XmlResourceParser] position. This method
77  * assumes the parser is already been positioned to the start tag
78  */
79 @Throws(XmlPullParserException::class)
loadVectorResourceInnernull80 internal fun loadVectorResourceInner(
81     theme: Resources.Theme? = null,
82     res: Resources,
83     parser: XmlResourceParser,
84     changingConfigurations: Int
85 ): ImageVectorCache.ImageVectorEntry {
86     val attrs = Xml.asAttributeSet(parser)
87     val resourceParser = AndroidVectorParser(parser)
88     val builder = resourceParser.createVectorImageBuilder(res, theme, attrs)
89 
90     var nestedGroups = 0
91     while (!parser.isAtEnd()) {
92         nestedGroups =
93             resourceParser.parseCurrentVectorNode(res, attrs, theme, builder, nestedGroups)
94         parser.next()
95     }
96     return ImageVectorCache.ImageVectorEntry(builder.build(), changingConfigurations)
97 }
98 
99 /**
100  * Object responsible for caching [ImageVector] instances based on the given theme and drawable
101  * resource identifier
102  */
103 internal class ImageVectorCache {
104 
105     /** Key that binds the corresponding theme with the resource identifier for the vector asset */
106     data class Key(val theme: Resources.Theme, val id: Int)
107 
108     /**
109      * Tuple that contains the [ImageVector] as well as the corresponding configuration flags that
110      * the [ImageVector] depends on. That is if there is a configuration change that updates the
111      * parameters in the flag, this vector should be regenerated from the current configuration
112      */
113     data class ImageVectorEntry(val imageVector: ImageVector, val configFlags: Int)
114 
115     private val map = HashMap<Key, WeakReference<ImageVectorEntry>>()
116 
getnull117     operator fun get(key: Key): ImageVectorEntry? = map[key]?.get()
118 
119     fun prune(configChanges: Int) {
120         val it = map.entries.iterator()
121         while (it.hasNext()) {
122             val entry = it.next()
123             val imageVectorEntry = entry.value.get()
124             if (
125                 imageVectorEntry == null ||
126                     Configuration.needNewResources(configChanges, imageVectorEntry.configFlags)
127             ) {
128                 it.remove()
129             }
130         }
131     }
132 
setnull133     operator fun set(key: Key, imageVectorEntry: ImageVectorEntry) {
134         map[key] = WeakReference<ImageVectorEntry>(imageVectorEntry)
135     }
136 
clearnull137     fun clear() {
138         map.clear()
139     }
140 }
141