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