• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 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.drawable;
18 
19 import android.annotation.DrawableRes;
20 import android.annotation.NonNull;
21 import android.annotation.Nullable;
22 import android.compat.annotation.UnsupportedAppUsage;
23 import android.content.Context;
24 import android.content.res.Resources;
25 import android.content.res.Resources.Theme;
26 import android.os.Build;
27 import android.util.AttributeSet;
28 import android.view.InflateException;
29 
30 import org.xmlpull.v1.XmlPullParser;
31 import org.xmlpull.v1.XmlPullParserException;
32 
33 import java.io.IOException;
34 import java.lang.reflect.Constructor;
35 import java.util.HashMap;
36 
37 /**
38  * Instantiates a drawable XML file into its corresponding
39  * {@link android.graphics.drawable.Drawable} objects.
40  * <p>
41  * For performance reasons, inflation relies heavily on pre-processing of
42  * XML files that is done at build time. Therefore, it is not currently possible
43  * to use this inflater with an XmlPullParser over a plain XML file at runtime;
44  * it only works with an XmlPullParser returned from a compiled resource (R.
45  * <em>something</em> file.)
46  *
47  * @hide Pending API finalization.
48  */
49 public final class DrawableInflater {
50     private static final HashMap<String, Constructor<? extends Drawable>> CONSTRUCTOR_MAP =
51             new HashMap<>();
52 
53     private final Resources mRes;
54     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
55     private final ClassLoader mClassLoader;
56 
57     /**
58      * Loads the drawable resource with the specified identifier.
59      *
60      * @param context the context in which the drawable should be loaded
61      * @param id the identifier of the drawable resource
62      * @return a drawable, or {@code null} if the drawable failed to load
63      */
64     @Nullable
loadDrawable(@onNull Context context, @DrawableRes int id)65     public static Drawable loadDrawable(@NonNull Context context, @DrawableRes int id) {
66         return loadDrawable(context.getResources(), context.getTheme(), id);
67     }
68 
69     /**
70      * Loads the drawable resource with the specified identifier.
71      *
72      * @param resources the resources from which the drawable should be loaded
73      * @param theme the theme against which the drawable should be inflated
74      * @param id the identifier of the drawable resource
75      * @return a drawable, or {@code null} if the drawable failed to load
76      */
77     @Nullable
loadDrawable( @onNull Resources resources, @Nullable Theme theme, @DrawableRes int id)78     public static Drawable loadDrawable(
79             @NonNull Resources resources, @Nullable Theme theme, @DrawableRes int id) {
80         return resources.getDrawable(id, theme);
81     }
82 
83     /**
84      * Constructs a new drawable inflater using the specified resources and
85      * class loader.
86      *
87      * @param res the resources used to resolve resource identifiers
88      * @param classLoader the class loader used to load custom drawables
89      * @hide
90      */
DrawableInflater(@onNull Resources res, @NonNull ClassLoader classLoader)91     public DrawableInflater(@NonNull Resources res, @NonNull ClassLoader classLoader) {
92         mRes = res;
93         mClassLoader = classLoader;
94     }
95 
96     /**
97      * Inflates a drawable from inside an XML document using an optional
98      * {@link Theme}.
99      * <p>
100      * This method should be called on a parser positioned at a tag in an XML
101      * document defining a drawable resource. It will attempt to create a
102      * Drawable from the tag at the current position.
103      *
104      * @param name the name of the tag at the current position
105      * @param parser an XML parser positioned at the drawable tag
106      * @param attrs an attribute set that wraps the parser
107      * @param theme the theme against which the drawable should be inflated, or
108      *              {@code null} to not inflate against a theme
109      * @return a drawable
110      *
111      * @throws XmlPullParserException
112      * @throws IOException
113      */
114     @NonNull
inflateFromXml(@onNull String name, @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme)115     public Drawable inflateFromXml(@NonNull String name, @NonNull XmlPullParser parser,
116             @NonNull AttributeSet attrs, @Nullable Theme theme)
117             throws XmlPullParserException, IOException {
118         return inflateFromXmlForDensity(name, parser, attrs, 0, theme);
119     }
120 
121     /**
122      * Version of {@link #inflateFromXml(String, XmlPullParser, AttributeSet, Theme)} that accepts
123      * an override density.
124      */
125     @NonNull
inflateFromXmlForDensity(@onNull String name, @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, int density, @Nullable Theme theme)126     Drawable inflateFromXmlForDensity(@NonNull String name, @NonNull XmlPullParser parser,
127             @NonNull AttributeSet attrs, int density, @Nullable Theme theme)
128             throws XmlPullParserException, IOException {
129         // Inner classes must be referenced as Outer$Inner, but XML tag names
130         // can't contain $, so the <drawable> tag allows developers to specify
131         // the class in an attribute. We'll still run it through inflateFromTag
132         // to stay consistent with how LayoutInflater works.
133         if (name.equals("drawable")) {
134             name = attrs.getAttributeValue(null, "class");
135             if (name == null) {
136                 throw new InflateException("<drawable> tag must specify class attribute");
137             }
138         }
139 
140         Drawable drawable = inflateFromTag(name);
141         if (drawable == null) {
142             drawable = inflateFromClass(name);
143         }
144         drawable.setSrcDensityOverride(density);
145         drawable.inflate(mRes, parser, attrs, theme);
146         return drawable;
147     }
148 
149     @NonNull
150     @SuppressWarnings("deprecation")
inflateFromTag(@onNull String name)151     private Drawable inflateFromTag(@NonNull String name) {
152         switch (name) {
153             case "selector":
154                 return new StateListDrawable();
155             case "animated-selector":
156                 return new AnimatedStateListDrawable();
157             case "level-list":
158                 return new LevelListDrawable();
159             case "layer-list":
160                 return new LayerDrawable();
161             case "transition":
162                 return new TransitionDrawable();
163             case "ripple":
164                 return new RippleDrawable();
165             case "adaptive-icon":
166                 return new AdaptiveIconDrawable();
167             case "color":
168                 return new ColorDrawable();
169             case "shape":
170                 return new GradientDrawable();
171             case "vector":
172                 return new VectorDrawable();
173             case "animated-vector":
174                 return new AnimatedVectorDrawable();
175             case "scale":
176                 return new ScaleDrawable();
177             case "clip":
178                 return new ClipDrawable();
179             case "rotate":
180                 return new RotateDrawable();
181             case "animated-rotate":
182                 return new AnimatedRotateDrawable();
183             case "animation-list":
184                 return new AnimationDrawable();
185             case "inset":
186                 return new InsetDrawable();
187             case "bitmap":
188                 return new BitmapDrawable();
189             case "nine-patch":
190                 return new NinePatchDrawable();
191             case "animated-image":
192                 return new AnimatedImageDrawable();
193             default:
194                 return null;
195         }
196     }
197 
198     @NonNull
inflateFromClass(@onNull String className)199     private Drawable inflateFromClass(@NonNull String className) {
200         try {
201             Constructor<? extends Drawable> constructor;
202             synchronized (CONSTRUCTOR_MAP) {
203                 constructor = CONSTRUCTOR_MAP.get(className);
204                 if (constructor == null) {
205                     final Class<? extends Drawable> clazz =
206                             mClassLoader.loadClass(className).asSubclass(Drawable.class);
207                     constructor = clazz.getConstructor();
208                     CONSTRUCTOR_MAP.put(className, constructor);
209                 }
210             }
211             return constructor.newInstance();
212         } catch (NoSuchMethodException e) {
213             final InflateException ie = new InflateException(
214                     "Error inflating class " + className);
215             ie.initCause(e);
216             throw ie;
217         } catch (ClassCastException e) {
218             // If loaded class is not a Drawable subclass.
219             final InflateException ie = new InflateException(
220                     "Class is not a Drawable " + className);
221             ie.initCause(e);
222             throw ie;
223         } catch (ClassNotFoundException e) {
224             // If loadClass fails, we should propagate the exception.
225             final InflateException ie = new InflateException(
226                     "Class not found " + className);
227             ie.initCause(e);
228             throw ie;
229         } catch (Exception e) {
230             final InflateException ie = new InflateException(
231                     "Error inflating class " + className);
232             ie.initCause(e);
233             throw ie;
234         }
235     }
236 }
237