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