1/*
2 * Copyright (C) 2015 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 */
16package android.support.v4.content.res;
17
18import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP;
19
20import android.content.Context;
21import android.content.res.Resources;
22import android.content.res.TypedArray;
23import android.graphics.drawable.Drawable;
24import android.support.annotation.AnyRes;
25import android.support.annotation.ColorInt;
26import android.support.annotation.NonNull;
27import android.support.annotation.RestrictTo;
28import android.support.annotation.StyleableRes;
29import android.util.AttributeSet;
30import android.util.TypedValue;
31
32import org.xmlpull.v1.XmlPullParser;
33
34/**
35 * Compat methods for accessing TypedArray values.
36 *
37 * All the getNamed*() functions added the attribute name match, to take care of potential ID
38 * collision between the private attributes in older OS version (OEM) and the attributes existed in
39 * the newer OS version.
40 * For example, if an private attribute named "abcdefg" in Kitkat has the
41 * same id value as "android:pathData" in Lollipop, we need to match the attribute's namefirst.
42 *
43 * @hide
44 */
45@RestrictTo(LIBRARY_GROUP)
46public class TypedArrayUtils {
47
48    private static final String NAMESPACE = "http://schemas.android.com/apk/res/android";
49
50    /**
51     * @return Whether the current node ofthe  {@link XmlPullParser} has an attribute with the
52     * specified {@code attrName}.
53     */
54    public static boolean hasAttribute(@NonNull XmlPullParser parser, @NonNull String attrName) {
55        return parser.getAttributeValue(NAMESPACE, attrName) != null;
56    }
57
58    /**
59     * Retrieves a float attribute value. In addition to the styleable resource ID, we also make
60     * sure that the attribute name matches.
61     *
62     * @return a float value in the {@link TypedArray} with the specified {@code resId}, or
63     * {@code defaultValue} if it does not exist.
64     */
65    public static float getNamedFloat(@NonNull TypedArray a, @NonNull XmlPullParser parser,
66            @NonNull String attrName, @StyleableRes int resId, float defaultValue) {
67        final boolean hasAttr = hasAttribute(parser, attrName);
68        if (!hasAttr) {
69            return defaultValue;
70        } else {
71            return a.getFloat(resId, defaultValue);
72        }
73    }
74
75    /**
76     * Retrieves a boolean attribute value. In addition to the styleable resource ID, we also make
77     * sure that the attribute name matches.
78     *
79     * @return a boolean value in the {@link TypedArray} with the specified {@code resId}, or
80     * {@code defaultValue} if it does not exist.
81     */
82    public static boolean getNamedBoolean(@NonNull TypedArray a, @NonNull XmlPullParser parser,
83            String attrName, @StyleableRes int resId, boolean defaultValue) {
84        final boolean hasAttr = hasAttribute(parser, attrName);
85        if (!hasAttr) {
86            return defaultValue;
87        } else {
88            return a.getBoolean(resId, defaultValue);
89        }
90    }
91
92    /**
93     * Retrieves an int attribute value. In addition to the styleable resource ID, we also make
94     * sure that the attribute name matches.
95     *
96     * @return an int value in the {@link TypedArray} with the specified {@code resId}, or
97     * {@code defaultValue} if it does not exist.
98     */
99    public static int getNamedInt(@NonNull TypedArray a, @NonNull XmlPullParser parser,
100            String attrName, @StyleableRes int resId, int defaultValue) {
101        final boolean hasAttr = hasAttribute(parser, attrName);
102        if (!hasAttr) {
103            return defaultValue;
104        } else {
105            return a.getInt(resId, defaultValue);
106        }
107    }
108
109    /**
110     * Retrieves a color attribute value. In addition to the styleable resource ID, we also make
111     * sure that the attribute name matches.
112     *
113     * @return a color value in the {@link TypedArray} with the specified {@code resId}, or
114     * {@code defaultValue} if it does not exist.
115     */
116    @ColorInt
117    public static int getNamedColor(@NonNull TypedArray a, @NonNull XmlPullParser parser,
118            String attrName, @StyleableRes int resId, @ColorInt int defaultValue) {
119        final boolean hasAttr = hasAttribute(parser, attrName);
120        if (!hasAttr) {
121            return defaultValue;
122        } else {
123            return a.getColor(resId, defaultValue);
124        }
125    }
126
127    /**
128     * Retrieves a resource ID attribute value. In addition to the styleable resource ID, we also
129     * make sure that the attribute name matches.
130     *
131     * @return a resource ID value in the {@link TypedArray} with the specified {@code resId}, or
132     * {@code defaultValue} if it does not exist.
133     */
134    @AnyRes
135    public static int getNamedResourceId(@NonNull TypedArray a, @NonNull XmlPullParser parser,
136            String attrName, @StyleableRes int resId, @AnyRes int defaultValue) {
137        final boolean hasAttr = hasAttribute(parser, attrName);
138        if (!hasAttr) {
139            return defaultValue;
140        } else {
141            return a.getResourceId(resId, defaultValue);
142        }
143    }
144
145    /**
146     * Retrieves a string attribute value. In addition to the styleable resource ID, we also
147     * make sure that the attribute name matches.
148     *
149     * @return a string value in the {@link TypedArray} with the specified {@code resId}, or
150     * null if it does not exist.
151     */
152    public static String getNamedString(@NonNull TypedArray a, @NonNull XmlPullParser parser,
153            String attrName, @StyleableRes int resId) {
154        final boolean hasAttr = hasAttribute(parser, attrName);
155        if (!hasAttr) {
156            return null;
157        } else {
158            return a.getString(resId);
159        }
160    }
161
162    /**
163     * Retrieve the raw TypedValue for the attribute at <var>index</var>
164     * and return a temporary object holding its data.  This object is only
165     * valid until the next call on to {@link TypedArray}.
166     */
167    public static TypedValue peekNamedValue(TypedArray a, XmlPullParser parser, String attrName,
168            int resId) {
169        final boolean hasAttr = hasAttribute(parser, attrName);
170        if (!hasAttr) {
171            return null;
172        } else {
173            return a.peekValue(resId);
174        }
175    }
176
177    /**
178     * Obtains styled attributes from the theme, if available, or unstyled
179     * resources if the theme is null.
180     */
181    public static TypedArray obtainAttributes(
182            Resources res, Resources.Theme theme, AttributeSet set, int[] attrs) {
183        if (theme == null) {
184            return res.obtainAttributes(set, attrs);
185        }
186        return theme.obtainStyledAttributes(set, attrs, 0, 0);
187    }
188
189    /**
190     * @return a boolean value of {@code index}. If it does not exist, a boolean value of
191     * {@code fallbackIndex}. If it still does not exist, {@code defaultValue}.
192     */
193    public static boolean getBoolean(TypedArray a, @StyleableRes int index,
194            @StyleableRes int fallbackIndex, boolean defaultValue) {
195        boolean val = a.getBoolean(fallbackIndex, defaultValue);
196        return a.getBoolean(index, val);
197    }
198
199    /**
200     * @return a drawable value of {@code index}. If it does not exist, a drawable value of
201     * {@code fallbackIndex}. If it still does not exist, {@code null}.
202     */
203    public static Drawable getDrawable(TypedArray a, @StyleableRes int index,
204            @StyleableRes int fallbackIndex) {
205        Drawable val = a.getDrawable(index);
206        if (val == null) {
207            val = a.getDrawable(fallbackIndex);
208        }
209        return val;
210    }
211
212    /**
213     * @return an int value of {@code index}. If it does not exist, an int value of
214     * {@code fallbackIndex}. If it still does not exist, {@code defaultValue}.
215     */
216    public static int getInt(TypedArray a, @StyleableRes int index,
217            @StyleableRes int fallbackIndex, int defaultValue) {
218        int val = a.getInt(fallbackIndex, defaultValue);
219        return a.getInt(index, val);
220    }
221
222    /**
223     * @return a resource ID value of {@code index}. If it does not exist, a resource ID value of
224     * {@code fallbackIndex}. If it still does not exist, {@code defaultValue}.
225     */
226    @AnyRes
227    public static int getResourceId(TypedArray a, @StyleableRes int index,
228            @StyleableRes int fallbackIndex, @AnyRes int defaultValue) {
229        int val = a.getResourceId(fallbackIndex, defaultValue);
230        return a.getResourceId(index, val);
231    }
232
233    /**
234     * @return a string value of {@code index}. If it does not exist, a string value of
235     * {@code fallbackIndex}. If it still does not exist, {@code null}.
236     */
237    public static String getString(TypedArray a, @StyleableRes int index,
238            @StyleableRes int fallbackIndex) {
239        String val = a.getString(index);
240        if (val == null) {
241            val = a.getString(fallbackIndex);
242        }
243        return val;
244    }
245
246    /**
247     * Retrieves a text attribute value with the specified fallback ID.
248     *
249     * @return a text value of {@code index}. If it does not exist, a text value of
250     * {@code fallbackIndex}. If it still does not exist, {@code null}.
251     */
252    public static CharSequence getText(TypedArray a, @StyleableRes int index,
253            @StyleableRes int fallbackIndex) {
254        CharSequence val = a.getText(index);
255        if (val == null) {
256            val = a.getText(fallbackIndex);
257        }
258        return val;
259    }
260
261    /**
262     * Retrieves a string array attribute value with the specified fallback ID.
263     *
264     * @return a string array value of {@code index}. If it does not exist, a string array value
265     * of {@code fallbackIndex}. If it still does not exist, {@code null}.
266     */
267    public static CharSequence[] getTextArray(TypedArray a, @StyleableRes int index,
268            @StyleableRes int fallbackIndex) {
269        CharSequence[] val = a.getTextArray(index);
270        if (val == null) {
271            val = a.getTextArray(fallbackIndex);
272        }
273        return val;
274    }
275
276    /**
277     * @return The resource ID value in the {@code context} specified by {@code attr}. If it does
278     * not exist, {@code fallbackAttr}.
279     */
280    public static int getAttr(Context context, int attr, int fallbackAttr) {
281        TypedValue value = new TypedValue();
282        context.getTheme().resolveAttribute(attr, value, true);
283        if (value.resourceId != 0) {
284            return attr;
285        }
286        return fallbackAttr;
287    }
288}
289