102fc5fef36357467eba22a0ee250a96734daf791Alan Viverette/*
202fc5fef36357467eba22a0ee250a96734daf791Alan Viverette * Copyright (C) 2015 The Android Open Source Project
302fc5fef36357467eba22a0ee250a96734daf791Alan Viverette *
402fc5fef36357467eba22a0ee250a96734daf791Alan Viverette * Licensed under the Apache License, Version 2.0 (the "License");
502fc5fef36357467eba22a0ee250a96734daf791Alan Viverette * you may not use this file except in compliance with the License.
602fc5fef36357467eba22a0ee250a96734daf791Alan Viverette * You may obtain a copy of the License at
702fc5fef36357467eba22a0ee250a96734daf791Alan Viverette *
802fc5fef36357467eba22a0ee250a96734daf791Alan Viverette *      http://www.apache.org/licenses/LICENSE-2.0
902fc5fef36357467eba22a0ee250a96734daf791Alan Viverette *
1002fc5fef36357467eba22a0ee250a96734daf791Alan Viverette * Unless required by applicable law or agreed to in writing, software
1102fc5fef36357467eba22a0ee250a96734daf791Alan Viverette * distributed under the License is distributed on an "AS IS" BASIS,
1202fc5fef36357467eba22a0ee250a96734daf791Alan Viverette * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1302fc5fef36357467eba22a0ee250a96734daf791Alan Viverette * See the License for the specific language governing permissions and
1402fc5fef36357467eba22a0ee250a96734daf791Alan Viverette * limitations under the License.
1502fc5fef36357467eba22a0ee250a96734daf791Alan Viverette */
1602fc5fef36357467eba22a0ee250a96734daf791Alan Viverette
1702fc5fef36357467eba22a0ee250a96734daf791Alan Viverettepackage android.graphics.drawable;
1802fc5fef36357467eba22a0ee250a96734daf791Alan Viverette
1902fc5fef36357467eba22a0ee250a96734daf791Alan Viveretteimport org.xmlpull.v1.XmlPullParser;
2002fc5fef36357467eba22a0ee250a96734daf791Alan Viveretteimport org.xmlpull.v1.XmlPullParserException;
2102fc5fef36357467eba22a0ee250a96734daf791Alan Viverette
2202fc5fef36357467eba22a0ee250a96734daf791Alan Viveretteimport android.annotation.DrawableRes;
2302fc5fef36357467eba22a0ee250a96734daf791Alan Viveretteimport android.annotation.NonNull;
2402fc5fef36357467eba22a0ee250a96734daf791Alan Viveretteimport android.annotation.Nullable;
2502fc5fef36357467eba22a0ee250a96734daf791Alan Viveretteimport android.content.Context;
2602fc5fef36357467eba22a0ee250a96734daf791Alan Viveretteimport android.content.res.Resources;
2702fc5fef36357467eba22a0ee250a96734daf791Alan Viveretteimport android.content.res.Resources.Theme;
2802fc5fef36357467eba22a0ee250a96734daf791Alan Viveretteimport android.util.AttributeSet;
2902fc5fef36357467eba22a0ee250a96734daf791Alan Viveretteimport android.view.InflateException;
3002fc5fef36357467eba22a0ee250a96734daf791Alan Viverette
3102fc5fef36357467eba22a0ee250a96734daf791Alan Viveretteimport java.io.IOException;
3202fc5fef36357467eba22a0ee250a96734daf791Alan Viveretteimport java.lang.reflect.Constructor;
3302fc5fef36357467eba22a0ee250a96734daf791Alan Viveretteimport java.util.HashMap;
3402fc5fef36357467eba22a0ee250a96734daf791Alan Viverette
3502fc5fef36357467eba22a0ee250a96734daf791Alan Viverette/**
3602fc5fef36357467eba22a0ee250a96734daf791Alan Viverette * Instantiates a drawable XML file into its corresponding
3702fc5fef36357467eba22a0ee250a96734daf791Alan Viverette * {@link android.graphics.drawable.Drawable} objects.
3802fc5fef36357467eba22a0ee250a96734daf791Alan Viverette * <p>
3902fc5fef36357467eba22a0ee250a96734daf791Alan Viverette * For performance reasons, inflation relies heavily on pre-processing of
4002fc5fef36357467eba22a0ee250a96734daf791Alan Viverette * XML files that is done at build time. Therefore, it is not currently possible
4102fc5fef36357467eba22a0ee250a96734daf791Alan Viverette * to use this inflater with an XmlPullParser over a plain XML file at runtime;
4202fc5fef36357467eba22a0ee250a96734daf791Alan Viverette * it only works with an XmlPullParser returned from a compiled resource (R.
4302fc5fef36357467eba22a0ee250a96734daf791Alan Viverette * <em>something</em> file.)
4402fc5fef36357467eba22a0ee250a96734daf791Alan Viverette *
4502fc5fef36357467eba22a0ee250a96734daf791Alan Viverette * @hide Pending API finalization.
4602fc5fef36357467eba22a0ee250a96734daf791Alan Viverette */
4702fc5fef36357467eba22a0ee250a96734daf791Alan Viverettepublic final class DrawableInflater {
4802fc5fef36357467eba22a0ee250a96734daf791Alan Viverette    private static final HashMap<String, Constructor<? extends Drawable>> CONSTRUCTOR_MAP =
4902fc5fef36357467eba22a0ee250a96734daf791Alan Viverette            new HashMap<>();
5002fc5fef36357467eba22a0ee250a96734daf791Alan Viverette
5102fc5fef36357467eba22a0ee250a96734daf791Alan Viverette    private final Resources mRes;
5202fc5fef36357467eba22a0ee250a96734daf791Alan Viverette    private final ClassLoader mClassLoader;
5302fc5fef36357467eba22a0ee250a96734daf791Alan Viverette
5402fc5fef36357467eba22a0ee250a96734daf791Alan Viverette    /**
5502fc5fef36357467eba22a0ee250a96734daf791Alan Viverette     * Loads the drawable resource with the specified identifier.
5602fc5fef36357467eba22a0ee250a96734daf791Alan Viverette     *
5702fc5fef36357467eba22a0ee250a96734daf791Alan Viverette     * @param context the context in which the drawable should be loaded
5802fc5fef36357467eba22a0ee250a96734daf791Alan Viverette     * @param id the identifier of the drawable resource
5902fc5fef36357467eba22a0ee250a96734daf791Alan Viverette     * @return a drawable, or {@code null} if the drawable failed to load
6002fc5fef36357467eba22a0ee250a96734daf791Alan Viverette     */
6102fc5fef36357467eba22a0ee250a96734daf791Alan Viverette    @Nullable
6202fc5fef36357467eba22a0ee250a96734daf791Alan Viverette    public static Drawable loadDrawable(@NonNull Context context, @DrawableRes int id) {
6302fc5fef36357467eba22a0ee250a96734daf791Alan Viverette        return loadDrawable(context.getResources(), context.getTheme(), id);
6402fc5fef36357467eba22a0ee250a96734daf791Alan Viverette    }
6502fc5fef36357467eba22a0ee250a96734daf791Alan Viverette
6602fc5fef36357467eba22a0ee250a96734daf791Alan Viverette    /**
6702fc5fef36357467eba22a0ee250a96734daf791Alan Viverette     * Loads the drawable resource with the specified identifier.
6802fc5fef36357467eba22a0ee250a96734daf791Alan Viverette     *
6902fc5fef36357467eba22a0ee250a96734daf791Alan Viverette     * @param resources the resources from which the drawable should be loaded
7002fc5fef36357467eba22a0ee250a96734daf791Alan Viverette     * @param theme the theme against which the drawable should be inflated
7102fc5fef36357467eba22a0ee250a96734daf791Alan Viverette     * @param id the identifier of the drawable resource
7202fc5fef36357467eba22a0ee250a96734daf791Alan Viverette     * @return a drawable, or {@code null} if the drawable failed to load
7302fc5fef36357467eba22a0ee250a96734daf791Alan Viverette     */
7402fc5fef36357467eba22a0ee250a96734daf791Alan Viverette    @Nullable
7502fc5fef36357467eba22a0ee250a96734daf791Alan Viverette    public static Drawable loadDrawable(
7602fc5fef36357467eba22a0ee250a96734daf791Alan Viverette            @NonNull Resources resources, @Nullable Theme theme, @DrawableRes int id) {
7702fc5fef36357467eba22a0ee250a96734daf791Alan Viverette        return resources.getDrawable(id, theme);
7802fc5fef36357467eba22a0ee250a96734daf791Alan Viverette    }
7902fc5fef36357467eba22a0ee250a96734daf791Alan Viverette
8002fc5fef36357467eba22a0ee250a96734daf791Alan Viverette    /**
8102fc5fef36357467eba22a0ee250a96734daf791Alan Viverette     * Constructs a new drawable inflater using the specified resources and
8202fc5fef36357467eba22a0ee250a96734daf791Alan Viverette     * class loader.
8302fc5fef36357467eba22a0ee250a96734daf791Alan Viverette     *
8402fc5fef36357467eba22a0ee250a96734daf791Alan Viverette     * @param res the resources used to resolve resource identifiers
8502fc5fef36357467eba22a0ee250a96734daf791Alan Viverette     * @param classLoader the class loader used to load custom drawables
8602fc5fef36357467eba22a0ee250a96734daf791Alan Viverette     * @hide
8702fc5fef36357467eba22a0ee250a96734daf791Alan Viverette     */
8802fc5fef36357467eba22a0ee250a96734daf791Alan Viverette    public DrawableInflater(@NonNull Resources res, @NonNull ClassLoader classLoader) {
8902fc5fef36357467eba22a0ee250a96734daf791Alan Viverette        mRes = res;
9002fc5fef36357467eba22a0ee250a96734daf791Alan Viverette        mClassLoader = classLoader;
9102fc5fef36357467eba22a0ee250a96734daf791Alan Viverette    }
9202fc5fef36357467eba22a0ee250a96734daf791Alan Viverette
9302fc5fef36357467eba22a0ee250a96734daf791Alan Viverette    /**
9402fc5fef36357467eba22a0ee250a96734daf791Alan Viverette     * Inflates a drawable from inside an XML document using an optional
9502fc5fef36357467eba22a0ee250a96734daf791Alan Viverette     * {@link Theme}.
9602fc5fef36357467eba22a0ee250a96734daf791Alan Viverette     * <p>
9702fc5fef36357467eba22a0ee250a96734daf791Alan Viverette     * This method should be called on a parser positioned at a tag in an XML
9802fc5fef36357467eba22a0ee250a96734daf791Alan Viverette     * document defining a drawable resource. It will attempt to create a
9902fc5fef36357467eba22a0ee250a96734daf791Alan Viverette     * Drawable from the tag at the current position.
10002fc5fef36357467eba22a0ee250a96734daf791Alan Viverette     *
10102fc5fef36357467eba22a0ee250a96734daf791Alan Viverette     * @param name the name of the tag at the current position
10202fc5fef36357467eba22a0ee250a96734daf791Alan Viverette     * @param parser an XML parser positioned at the drawable tag
10302fc5fef36357467eba22a0ee250a96734daf791Alan Viverette     * @param attrs an attribute set that wraps the parser
10402fc5fef36357467eba22a0ee250a96734daf791Alan Viverette     * @param theme the theme against which the drawable should be inflated, or
10502fc5fef36357467eba22a0ee250a96734daf791Alan Viverette     *              {@code null} to not inflate against a theme
10602fc5fef36357467eba22a0ee250a96734daf791Alan Viverette     * @return a drawable
10702fc5fef36357467eba22a0ee250a96734daf791Alan Viverette     *
10802fc5fef36357467eba22a0ee250a96734daf791Alan Viverette     * @throws XmlPullParserException
10902fc5fef36357467eba22a0ee250a96734daf791Alan Viverette     * @throws IOException
11002fc5fef36357467eba22a0ee250a96734daf791Alan Viverette     */
11102fc5fef36357467eba22a0ee250a96734daf791Alan Viverette    @NonNull
11202fc5fef36357467eba22a0ee250a96734daf791Alan Viverette    public Drawable inflateFromXml(@NonNull String name, @NonNull XmlPullParser parser,
11302fc5fef36357467eba22a0ee250a96734daf791Alan Viverette            @NonNull AttributeSet attrs, @Nullable Theme theme)
11402fc5fef36357467eba22a0ee250a96734daf791Alan Viverette            throws XmlPullParserException, IOException {
115caca720b176a0dab0a43a20496e676687e8d78f7Alan Viverette        // Inner classes must be referenced as Outer$Inner, but XML tag names
116caca720b176a0dab0a43a20496e676687e8d78f7Alan Viverette        // can't contain $, so the <drawable> tag allows developers to specify
117caca720b176a0dab0a43a20496e676687e8d78f7Alan Viverette        // the class in an attribute. We'll still run it through inflateFromTag
118caca720b176a0dab0a43a20496e676687e8d78f7Alan Viverette        // to stay consistent with how LayoutInflater works.
119caca720b176a0dab0a43a20496e676687e8d78f7Alan Viverette        if (name.equals("drawable")) {
120caca720b176a0dab0a43a20496e676687e8d78f7Alan Viverette            name = attrs.getAttributeValue(null, "class");
121caca720b176a0dab0a43a20496e676687e8d78f7Alan Viverette            if (name == null) {
122caca720b176a0dab0a43a20496e676687e8d78f7Alan Viverette                throw new InflateException("<drawable> tag must specify class attribute");
123caca720b176a0dab0a43a20496e676687e8d78f7Alan Viverette            }
124caca720b176a0dab0a43a20496e676687e8d78f7Alan Viverette        }
125caca720b176a0dab0a43a20496e676687e8d78f7Alan Viverette
12602fc5fef36357467eba22a0ee250a96734daf791Alan Viverette        Drawable drawable = inflateFromTag(name);
12702fc5fef36357467eba22a0ee250a96734daf791Alan Viverette        if (drawable == null) {
12802fc5fef36357467eba22a0ee250a96734daf791Alan Viverette            drawable = inflateFromClass(name);
12902fc5fef36357467eba22a0ee250a96734daf791Alan Viverette        }
13002fc5fef36357467eba22a0ee250a96734daf791Alan Viverette        drawable.inflate(mRes, parser, attrs, theme);
13102fc5fef36357467eba22a0ee250a96734daf791Alan Viverette        return drawable;
13202fc5fef36357467eba22a0ee250a96734daf791Alan Viverette    }
13302fc5fef36357467eba22a0ee250a96734daf791Alan Viverette
13402fc5fef36357467eba22a0ee250a96734daf791Alan Viverette    @NonNull
13502fc5fef36357467eba22a0ee250a96734daf791Alan Viverette    @SuppressWarnings("deprecation")
13602fc5fef36357467eba22a0ee250a96734daf791Alan Viverette    private Drawable inflateFromTag(@NonNull String name) {
13702fc5fef36357467eba22a0ee250a96734daf791Alan Viverette        switch (name) {
13802fc5fef36357467eba22a0ee250a96734daf791Alan Viverette            case "selector":
13902fc5fef36357467eba22a0ee250a96734daf791Alan Viverette                return new StateListDrawable();
14002fc5fef36357467eba22a0ee250a96734daf791Alan Viverette            case "animated-selector":
14102fc5fef36357467eba22a0ee250a96734daf791Alan Viverette                return new AnimatedStateListDrawable();
14202fc5fef36357467eba22a0ee250a96734daf791Alan Viverette            case "level-list":
14302fc5fef36357467eba22a0ee250a96734daf791Alan Viverette                return new LevelListDrawable();
14402fc5fef36357467eba22a0ee250a96734daf791Alan Viverette            case "layer-list":
14502fc5fef36357467eba22a0ee250a96734daf791Alan Viverette                return new LayerDrawable();
14602fc5fef36357467eba22a0ee250a96734daf791Alan Viverette            case "transition":
14702fc5fef36357467eba22a0ee250a96734daf791Alan Viverette                return new TransitionDrawable();
14802fc5fef36357467eba22a0ee250a96734daf791Alan Viverette            case "ripple":
14902fc5fef36357467eba22a0ee250a96734daf791Alan Viverette                return new RippleDrawable();
15002fc5fef36357467eba22a0ee250a96734daf791Alan Viverette            case "color":
15102fc5fef36357467eba22a0ee250a96734daf791Alan Viverette                return new ColorDrawable();
15202fc5fef36357467eba22a0ee250a96734daf791Alan Viverette            case "shape":
15302fc5fef36357467eba22a0ee250a96734daf791Alan Viverette                return new GradientDrawable();
15402fc5fef36357467eba22a0ee250a96734daf791Alan Viverette            case "vector":
15502fc5fef36357467eba22a0ee250a96734daf791Alan Viverette                return new VectorDrawable();
15602fc5fef36357467eba22a0ee250a96734daf791Alan Viverette            case "animated-vector":
15702fc5fef36357467eba22a0ee250a96734daf791Alan Viverette                return new AnimatedVectorDrawable();
15802fc5fef36357467eba22a0ee250a96734daf791Alan Viverette            case "scale":
15902fc5fef36357467eba22a0ee250a96734daf791Alan Viverette                return new ScaleDrawable();
16002fc5fef36357467eba22a0ee250a96734daf791Alan Viverette            case "clip":
16102fc5fef36357467eba22a0ee250a96734daf791Alan Viverette                return new ClipDrawable();
16202fc5fef36357467eba22a0ee250a96734daf791Alan Viverette            case "rotate":
16302fc5fef36357467eba22a0ee250a96734daf791Alan Viverette                return new RotateDrawable();
16402fc5fef36357467eba22a0ee250a96734daf791Alan Viverette            case "animated-rotate":
16502fc5fef36357467eba22a0ee250a96734daf791Alan Viverette                return new AnimatedRotateDrawable();
16602fc5fef36357467eba22a0ee250a96734daf791Alan Viverette            case "animation-list":
16702fc5fef36357467eba22a0ee250a96734daf791Alan Viverette                return new AnimationDrawable();
16802fc5fef36357467eba22a0ee250a96734daf791Alan Viverette            case "inset":
16902fc5fef36357467eba22a0ee250a96734daf791Alan Viverette                return new InsetDrawable();
17002fc5fef36357467eba22a0ee250a96734daf791Alan Viverette            case "bitmap":
17102fc5fef36357467eba22a0ee250a96734daf791Alan Viverette                return new BitmapDrawable();
17202fc5fef36357467eba22a0ee250a96734daf791Alan Viverette            case "nine-patch":
17302fc5fef36357467eba22a0ee250a96734daf791Alan Viverette                return new NinePatchDrawable();
17402fc5fef36357467eba22a0ee250a96734daf791Alan Viverette            default:
17502fc5fef36357467eba22a0ee250a96734daf791Alan Viverette                return null;
17602fc5fef36357467eba22a0ee250a96734daf791Alan Viverette        }
17702fc5fef36357467eba22a0ee250a96734daf791Alan Viverette    }
17802fc5fef36357467eba22a0ee250a96734daf791Alan Viverette
17902fc5fef36357467eba22a0ee250a96734daf791Alan Viverette    @NonNull
18002fc5fef36357467eba22a0ee250a96734daf791Alan Viverette    private Drawable inflateFromClass(@NonNull String className) {
18102fc5fef36357467eba22a0ee250a96734daf791Alan Viverette        try {
18202fc5fef36357467eba22a0ee250a96734daf791Alan Viverette            Constructor<? extends Drawable> constructor;
18302fc5fef36357467eba22a0ee250a96734daf791Alan Viverette            synchronized (CONSTRUCTOR_MAP) {
18402fc5fef36357467eba22a0ee250a96734daf791Alan Viverette                constructor = CONSTRUCTOR_MAP.get(className);
18502fc5fef36357467eba22a0ee250a96734daf791Alan Viverette                if (constructor == null) {
18602fc5fef36357467eba22a0ee250a96734daf791Alan Viverette                    final Class<? extends Drawable> clazz =
18702fc5fef36357467eba22a0ee250a96734daf791Alan Viverette                            mClassLoader.loadClass(className).asSubclass(Drawable.class);
18802fc5fef36357467eba22a0ee250a96734daf791Alan Viverette                    constructor = clazz.getConstructor();
18902fc5fef36357467eba22a0ee250a96734daf791Alan Viverette                    CONSTRUCTOR_MAP.put(className, constructor);
19002fc5fef36357467eba22a0ee250a96734daf791Alan Viverette                }
19102fc5fef36357467eba22a0ee250a96734daf791Alan Viverette            }
19202fc5fef36357467eba22a0ee250a96734daf791Alan Viverette            return constructor.newInstance();
19302fc5fef36357467eba22a0ee250a96734daf791Alan Viverette        } catch (NoSuchMethodException e) {
19402fc5fef36357467eba22a0ee250a96734daf791Alan Viverette            final InflateException ie = new InflateException(
19502fc5fef36357467eba22a0ee250a96734daf791Alan Viverette                    "Error inflating class " + className);
19602fc5fef36357467eba22a0ee250a96734daf791Alan Viverette            ie.initCause(e);
19702fc5fef36357467eba22a0ee250a96734daf791Alan Viverette            throw ie;
19802fc5fef36357467eba22a0ee250a96734daf791Alan Viverette        } catch (ClassCastException e) {
19902fc5fef36357467eba22a0ee250a96734daf791Alan Viverette            // If loaded class is not a Drawable subclass.
20002fc5fef36357467eba22a0ee250a96734daf791Alan Viverette            final InflateException ie = new InflateException(
20102fc5fef36357467eba22a0ee250a96734daf791Alan Viverette                    "Class is not a Drawable " + className);
20202fc5fef36357467eba22a0ee250a96734daf791Alan Viverette            ie.initCause(e);
20302fc5fef36357467eba22a0ee250a96734daf791Alan Viverette            throw ie;
20402fc5fef36357467eba22a0ee250a96734daf791Alan Viverette        } catch (ClassNotFoundException e) {
20502fc5fef36357467eba22a0ee250a96734daf791Alan Viverette            // If loadClass fails, we should propagate the exception.
206c725e8d5ea04ffb5821e00db1c6b45cfe2130e05Alan Viverette            final InflateException ie = new InflateException(
207c725e8d5ea04ffb5821e00db1c6b45cfe2130e05Alan Viverette                    "Class not found " + className);
208c725e8d5ea04ffb5821e00db1c6b45cfe2130e05Alan Viverette            ie.initCause(e);
209c725e8d5ea04ffb5821e00db1c6b45cfe2130e05Alan Viverette            throw ie;
21002fc5fef36357467eba22a0ee250a96734daf791Alan Viverette        } catch (Exception e) {
21102fc5fef36357467eba22a0ee250a96734daf791Alan Viverette            final InflateException ie = new InflateException(
21202fc5fef36357467eba22a0ee250a96734daf791Alan Viverette                    "Error inflating class " + className);
21302fc5fef36357467eba22a0ee250a96734daf791Alan Viverette            ie.initCause(e);
21402fc5fef36357467eba22a0ee250a96734daf791Alan Viverette            throw ie;
21502fc5fef36357467eba22a0ee250a96734daf791Alan Viverette        }
21602fc5fef36357467eba22a0ee250a96734daf791Alan Viverette    }
21702fc5fef36357467eba22a0ee250a96734daf791Alan Viverette}
218