ResourcesCompat.java revision 543fd2946ded1593b28553879e74ca4393eddd2e
1/*
2 * Copyright (C) 2014 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
17package android.support.v4.content.res;
18
19import static android.os.Build.VERSION.SDK_INT;
20
21import android.content.Context;
22import android.content.res.ColorStateList;
23import android.content.res.Resources;
24import android.content.res.Resources.NotFoundException;
25import android.content.res.Resources.Theme;
26import android.content.res.XmlResourceParser;
27import android.graphics.Typeface;
28import android.graphics.drawable.Drawable;
29import android.support.annotation.ColorInt;
30import android.support.annotation.ColorRes;
31import android.support.annotation.DrawableRes;
32import android.support.annotation.FontRes;
33import android.support.annotation.NonNull;
34import android.support.annotation.Nullable;
35import android.support.v4.content.res.FontResourcesParserCompat.FamilyResourceEntry;
36import android.support.v4.graphics.TypefaceCompat;
37import android.support.v4.os.BuildCompat;
38import android.util.Log;
39import android.util.TypedValue;
40
41import org.xmlpull.v1.XmlPullParserException;
42
43import java.io.IOException;
44
45/**
46 * Helper for accessing features in {@link android.content.res.Resources}
47 * introduced after API level 4 in a backwards compatible fashion.
48 */
49public final class ResourcesCompat {
50    private static final String TAG = "ResourcesCompat";
51
52    /**
53     * Return a drawable object associated with a particular resource ID and
54     * styled for the specified theme. Various types of objects will be
55     * returned depending on the underlying resource -- for example, a solid
56     * color, PNG image, scalable image, etc.
57     * <p>
58     * Prior to API level 21, the theme will not be applied and this method
59     * simply calls through to {@link Resources#getDrawable(int)}.
60     *
61     * @param id The desired resource identifier, as generated by the aapt
62     *           tool. This integer encodes the package, type, and resource
63     *           entry. The value 0 is an invalid identifier.
64     * @param theme The theme used to style the drawable attributes, may be
65     *              {@code null}.
66     * @return Drawable An object that can be used to draw this resource.
67     * @throws NotFoundException Throws NotFoundException if the given ID does
68     *         not exist.
69     */
70    @Nullable
71    @SuppressWarnings("deprecation")
72    public static Drawable getDrawable(@NonNull Resources res, @DrawableRes int id,
73            @Nullable Theme theme) throws NotFoundException {
74        if (SDK_INT >= 21) {
75            return res.getDrawable(id, theme);
76        } else {
77            return res.getDrawable(id);
78        }
79    }
80
81
82    /**
83     * Return a drawable object associated with a particular resource ID for
84     * the given screen density in DPI and styled for the specified theme.
85     * <p>
86     * Prior to API level 15, the theme and density will not be applied and
87     * this method simply calls through to {@link Resources#getDrawable(int)}.
88     * <p>
89     * Prior to API level 21, the theme will not be applied and this method
90     * calls through to Resources#getDrawableForDensity(int, int).
91     *
92     * @param id The desired resource identifier, as generated by the aapt
93     *           tool. This integer encodes the package, type, and resource
94     *           entry. The value 0 is an invalid identifier.
95     * @param density The desired screen density indicated by the resource as
96     *                found in {@link android.util.DisplayMetrics}.
97     * @param theme The theme used to style the drawable attributes, may be
98     *              {@code null}.
99     * @return Drawable An object that can be used to draw this resource.
100     * @throws NotFoundException Throws NotFoundException if the given ID does
101     *         not exist.
102     */
103    @Nullable
104    @SuppressWarnings("deprecation")
105    public static Drawable getDrawableForDensity(@NonNull Resources res, @DrawableRes int id,
106            int density, @Nullable Theme theme) throws NotFoundException {
107        if (SDK_INT >= 21) {
108            return res.getDrawableForDensity(id, density, theme);
109        } else if (SDK_INT >= 15) {
110            return res.getDrawableForDensity(id, density);
111        } else {
112            return res.getDrawable(id);
113        }
114    }
115
116    /**
117     * Returns a themed color integer associated with a particular resource ID.
118     * If the resource holds a complex {@link ColorStateList}, then the default
119     * color from the set is returned.
120     * <p>
121     * Prior to API level 23, the theme will not be applied and this method
122     * calls through to {@link Resources#getColor(int)}.
123     *
124     * @param id The desired resource identifier, as generated by the aapt
125     *           tool. This integer encodes the package, type, and resource
126     *           entry. The value 0 is an invalid identifier.
127     * @param theme The theme used to style the color attributes, may be
128     *              {@code null}.
129     * @return A single color value in the form {@code 0xAARRGGBB}.
130     * @throws NotFoundException Throws NotFoundException if the given ID does
131     *         not exist.
132     */
133    @ColorInt
134    @SuppressWarnings("deprecation")
135    public static int getColor(@NonNull Resources res, @ColorRes int id, @Nullable Theme theme)
136            throws NotFoundException {
137        if (SDK_INT >= 23) {
138            return res.getColor(id, theme);
139        } else {
140            return res.getColor(id);
141        }
142    }
143
144    /**
145     * Returns a themed color state list associated with a particular resource
146     * ID. The resource may contain either a single raw color value or a
147     * complex {@link ColorStateList} holding multiple possible colors.
148     * <p>
149     * Prior to API level 23, the theme will not be applied and this method
150     * calls through to {@link Resources#getColorStateList(int)}.
151     *
152     * @param id The desired resource identifier of a {@link ColorStateList},
153     *           as generated by the aapt tool. This integer encodes the
154     *           package, type, and resource entry. The value 0 is an invalid
155     *           identifier.
156     * @param theme The theme used to style the color attributes, may be
157     *              {@code null}.
158     * @return A themed ColorStateList object containing either a single solid
159     *         color or multiple colors that can be selected based on a state.
160     * @throws NotFoundException Throws NotFoundException if the given ID does
161     *         not exist.
162     */
163    @Nullable
164    @SuppressWarnings("deprecation")
165    public static ColorStateList getColorStateList(@NonNull Resources res, @ColorRes int id,
166            @Nullable Theme theme) throws NotFoundException {
167        if (SDK_INT >= 23) {
168            return res.getColorStateList(id, theme);
169        } else {
170            return res.getColorStateList(id);
171        }
172    }
173
174    /**
175     * Returns a font Typeface associated with a particular resource ID.
176     * <p>
177     * Prior to API level 23, font resources with more than one font in a family will only load the
178     * first font in that family.
179     *
180     * @param context A context to retrieve the Resources from.
181     * @param id The desired resource identifier of a {@link Typeface},
182     *           as generated by the aapt tool. This integer encodes the
183     *           package, type, and resource entry. The value 0 is an invalid
184     *           identifier.
185     * @return A font Typeface object.
186     * @throws NotFoundException Throws NotFoundException if the given ID does
187     *         not exist.
188     */
189    @Nullable
190    public static Typeface getFont(@NonNull Context context, @FontRes int id)
191            throws NotFoundException {
192        if (context.isRestricted()) {
193            return null;
194        }
195        if (BuildCompat.isAtLeastO()) {
196            // Use framework support
197            return context.getResources().getFont(id);
198        } else {
199            return loadFont(context, id);
200        }
201    }
202
203    private static Typeface loadFont(@NonNull Context context, int id) {
204        final TypedValue value = new TypedValue();
205        final Resources resources = context.getResources();
206        resources.getValue(id, value, true);
207        Typeface typeface = loadFont(context, resources, value, id);
208        if (typeface != null) {
209            return typeface;
210        }
211        throw new NotFoundException("Font resource ID #0x"
212                + Integer.toHexString(id));
213    }
214
215    private static Typeface loadFont(@NonNull Context context, Resources wrapper, TypedValue value,
216            int id) {
217        if (value.string == null) {
218            throw new NotFoundException("Resource \"" + wrapper.getResourceName(id) + "\" ("
219                    + Integer.toHexString(id) + ") is not a Font: " + value);
220        }
221
222        final String file = value.string.toString();
223        Typeface cached = TypefaceCompat.findFromCache(wrapper, id, file);
224        if (cached != null) {
225            return cached;
226        }
227
228        try {
229            if (file.toLowerCase().endsWith(".xml")) {
230                final XmlResourceParser rp = wrapper.getXml(id);
231                final FamilyResourceEntry familyEntry =
232                        FontResourcesParserCompat.parse(rp, wrapper);
233                if (familyEntry == null) {
234                    Log.e(TAG, "Failed to find font-family tag");
235                    return null;
236                }
237                return TypefaceCompat.createFromResources(context, familyEntry, wrapper, id, file);
238            }
239            return TypefaceCompat.createFromResources(context, wrapper, id, file);
240        } catch (XmlPullParserException e) {
241            Log.e(TAG, "Failed to parse xml resource " + file, e);
242        } catch (IOException e) {
243            Log.e(TAG, "Failed to read xml resource " + file, e);
244        }
245        return null;
246    }
247
248    private ResourcesCompat() {}
249}
250