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