ResourcesCompat.java revision 645e5c8aa6b31961c5f73f3d30bb5261d5e04aeb
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.os.Build;
31import android.support.annotation.ColorInt;
32import android.support.annotation.ColorRes;
33import android.support.annotation.DrawableRes;
34import android.support.annotation.FontRes;
35import android.support.annotation.NonNull;
36import android.support.annotation.Nullable;
37import android.support.annotation.RestrictTo;
38import android.support.v4.content.res.FontResourcesParserCompat.FamilyResourceEntry;
39import android.support.v4.graphics.TypefaceCompat;
40import android.util.Log;
41import android.util.TypedValue;
42import android.widget.TextView;
43
44import org.xmlpull.v1.XmlPullParserException;
45
46import java.io.IOException;
47
48/**
49 * Helper for accessing features in {@link android.content.res.Resources}.
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 (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
198            // Use framework support.
199            return context.getResources().getFont(id);
200        }
201        return loadFont(context, id, new TypedValue(), Typeface.NORMAL, null);
202    }
203
204    /** @hide */
205    @RestrictTo(LIBRARY_GROUP)
206    public static Typeface getFont(@NonNull Context context, @FontRes int id, TypedValue value,
207            int style, @Nullable TextView targetView) throws NotFoundException {
208        if (context.isRestricted()) {
209            return null;
210        }
211        return loadFont(context, id, value, style, targetView);
212    }
213
214    private static Typeface loadFont(@NonNull Context context, int id, TypedValue value,
215            int style, @Nullable TextView targetView) {
216        final Resources resources = context.getResources();
217        resources.getValue(id, value, true);
218        Typeface typeface = loadFont(context, resources, value, id, style, targetView);
219        if (typeface != null) {
220            return typeface;
221        }
222        throw new NotFoundException("Font resource ID #0x"
223                + Integer.toHexString(id));
224    }
225
226    private static Typeface loadFont(
227            @NonNull Context context, Resources wrapper, TypedValue value, int id, int style,
228            @Nullable TextView targetView) {
229        if (value.string == null) {
230            throw new NotFoundException("Resource \"" + wrapper.getResourceName(id) + "\" ("
231                    + Integer.toHexString(id) + ") is not a Font: " + value);
232        }
233
234        final String file = value.string.toString();
235        if (!file.startsWith("res/")) {
236            // Early exit if the specified string is unlikely to the resource path.
237            return null;
238        }
239
240        Typeface cached = TypefaceCompat.findFromCache(wrapper, id, style);
241        if (cached != null) {
242            return cached;
243        }
244
245        try {
246            if (file.toLowerCase().endsWith(".xml")) {
247                final XmlResourceParser rp = wrapper.getXml(id);
248                final FamilyResourceEntry familyEntry =
249                        FontResourcesParserCompat.parse(rp, wrapper);
250                if (familyEntry == null) {
251                    Log.e(TAG, "Failed to find font-family tag");
252                    return null;
253                }
254                return TypefaceCompat.createFromResourcesFamilyXml(
255                        context, familyEntry, wrapper, id, style, targetView);
256            }
257            return TypefaceCompat.createFromResourcesFontFile(context, wrapper, id, style);
258        } catch (XmlPullParserException e) {
259            Log.e(TAG, "Failed to parse xml resource " + file, e);
260        } catch (IOException e) {
261            Log.e(TAG, "Failed to read xml resource " + file, e);
262        }
263        return null;
264    }
265
266    private ResourcesCompat() {}
267}
268