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