1 /*
2  * Copyright (C) 2008 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 
17 package android.content.res;
18 
19 import android.annotation.AnyRes;
20 import android.annotation.ColorInt;
21 import android.annotation.Nullable;
22 import android.annotation.StyleableRes;
23 import android.content.pm.ActivityInfo;
24 import android.content.pm.ActivityInfo.Config;
25 import android.graphics.drawable.Drawable;
26 import android.os.StrictMode;
27 import android.util.AttributeSet;
28 import android.util.DisplayMetrics;
29 import android.util.TypedValue;
30 
31 import com.android.internal.util.XmlUtils;
32 
33 import java.util.Arrays;
34 
35 /**
36  * Container for an array of values that were retrieved with
37  * {@link Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)}
38  * or {@link Resources#obtainAttributes}.  Be
39  * sure to call {@link #recycle} when done with them.
40  *
41  * The indices used to retrieve values from this structure correspond to
42  * the positions of the attributes given to obtainStyledAttributes.
43  */
44 public class TypedArray {
45 
obtain(Resources res, int len)46     static TypedArray obtain(Resources res, int len) {
47         final TypedArray attrs = res.mTypedArrayPool.acquire();
48         if (attrs != null) {
49             attrs.mLength = len;
50             attrs.mRecycled = false;
51 
52             // Reset the assets, which may have changed due to configuration changes
53             // or further resource loading.
54             attrs.mAssets = res.getAssets();
55 
56             final int fullLen = len * AssetManager.STYLE_NUM_ENTRIES;
57             if (attrs.mData.length >= fullLen) {
58                 return attrs;
59             }
60 
61             attrs.mData = new int[fullLen];
62             attrs.mIndices = new int[1 + len];
63             return attrs;
64         }
65 
66         return new TypedArray(res,
67                 new int[len*AssetManager.STYLE_NUM_ENTRIES],
68                 new int[1+len], len);
69     }
70 
71     private final Resources mResources;
72     private final DisplayMetrics mMetrics;
73     private AssetManager mAssets;
74 
75     private boolean mRecycled;
76 
77     /*package*/ XmlBlock.Parser mXml;
78     /*package*/ Resources.Theme mTheme;
79     /*package*/ int[] mData;
80     /*package*/ int[] mIndices;
81     /*package*/ int mLength;
82     /*package*/ TypedValue mValue = new TypedValue();
83 
84     /**
85      * Returns the number of values in this array.
86      *
87      * @throws RuntimeException if the TypedArray has already been recycled.
88      */
length()89     public int length() {
90         if (mRecycled) {
91             throw new RuntimeException("Cannot make calls to a recycled instance!");
92         }
93 
94         return mLength;
95     }
96 
97     /**
98      * Return the number of indices in the array that actually have data.
99      *
100      * @throws RuntimeException if the TypedArray has already been recycled.
101      */
getIndexCount()102     public int getIndexCount() {
103         if (mRecycled) {
104             throw new RuntimeException("Cannot make calls to a recycled instance!");
105         }
106 
107         return mIndices[0];
108     }
109 
110     /**
111      * Returns an index in the array that has data.
112      *
113      * @param at The index you would like to returned, ranging from 0 to
114      *           {@link #getIndexCount()}.
115      *
116      * @return The index at the given offset, which can be used with
117      *         {@link #getValue} and related APIs.
118      * @throws RuntimeException if the TypedArray has already been recycled.
119      */
getIndex(int at)120     public int getIndex(int at) {
121         if (mRecycled) {
122             throw new RuntimeException("Cannot make calls to a recycled instance!");
123         }
124 
125         return mIndices[1+at];
126     }
127 
128     /**
129      * Returns the Resources object this array was loaded from.
130      *
131      * @throws RuntimeException if the TypedArray has already been recycled.
132      */
getResources()133     public Resources getResources() {
134         if (mRecycled) {
135             throw new RuntimeException("Cannot make calls to a recycled instance!");
136         }
137 
138         return mResources;
139     }
140 
141     /**
142      * Retrieves the styled string value for the attribute at <var>index</var>.
143      * <p>
144      * If the attribute is not a string, this method will attempt to coerce
145      * it to a string.
146      *
147      * @param index Index of attribute to retrieve.
148      *
149      * @return CharSequence holding string data. May be styled. Returns
150      *         {@code null} if the attribute is not defined or could not be
151      *         coerced to a string.
152      * @throws RuntimeException if the TypedArray has already been recycled.
153      */
getText(@tyleableRes int index)154     public CharSequence getText(@StyleableRes int index) {
155         if (mRecycled) {
156             throw new RuntimeException("Cannot make calls to a recycled instance!");
157         }
158 
159         index *= AssetManager.STYLE_NUM_ENTRIES;
160         final int[] data = mData;
161         final int type = data[index+AssetManager.STYLE_TYPE];
162         if (type == TypedValue.TYPE_NULL) {
163             return null;
164         } else if (type == TypedValue.TYPE_STRING) {
165             return loadStringValueAt(index);
166         }
167 
168         final TypedValue v = mValue;
169         if (getValueAt(index, v)) {
170             return v.coerceToString();
171         }
172 
173         // We already checked for TYPE_NULL. This should never happen.
174         throw new RuntimeException("getText of bad type: 0x" + Integer.toHexString(type));
175     }
176 
177     /**
178      * Retrieves the string value for the attribute at <var>index</var>.
179      * <p>
180      * If the attribute is not a string, this method will attempt to coerce
181      * it to a string.
182      *
183      * @param index Index of attribute to retrieve.
184      *
185      * @return String holding string data. Any styling information is removed.
186      *         Returns {@code null} if the attribute is not defined or could
187      *         not be coerced to a string.
188      * @throws RuntimeException if the TypedArray has already been recycled.
189      */
190     @Nullable
getString(@tyleableRes int index)191     public String getString(@StyleableRes int index) {
192         if (mRecycled) {
193             throw new RuntimeException("Cannot make calls to a recycled instance!");
194         }
195 
196         index *= AssetManager.STYLE_NUM_ENTRIES;
197         final int[] data = mData;
198         final int type = data[index+AssetManager.STYLE_TYPE];
199         if (type == TypedValue.TYPE_NULL) {
200             return null;
201         } else if (type == TypedValue.TYPE_STRING) {
202             return loadStringValueAt(index).toString();
203         }
204 
205         final TypedValue v = mValue;
206         if (getValueAt(index, v)) {
207             final CharSequence cs = v.coerceToString();
208             return cs != null ? cs.toString() : null;
209         }
210 
211         // We already checked for TYPE_NULL. This should never happen.
212         throw new RuntimeException("getString of bad type: 0x" + Integer.toHexString(type));
213     }
214 
215     /**
216      * Retrieves the string value for the attribute at <var>index</var>, but
217      * only if that string comes from an immediate value in an XML file.  That
218      * is, this does not allow references to string resources, string
219      * attributes, or conversions from other types.  As such, this method
220      * will only return strings for TypedArray objects that come from
221      * attributes in an XML file.
222      *
223      * @param index Index of attribute to retrieve.
224      *
225      * @return String holding string data. Any styling information is removed.
226      *         Returns {@code null} if the attribute is not defined or is not
227      *         an immediate string value.
228      * @throws RuntimeException if the TypedArray has already been recycled.
229      */
getNonResourceString(@tyleableRes int index)230     public String getNonResourceString(@StyleableRes int index) {
231         if (mRecycled) {
232             throw new RuntimeException("Cannot make calls to a recycled instance!");
233         }
234 
235         index *= AssetManager.STYLE_NUM_ENTRIES;
236         final int[] data = mData;
237         final int type = data[index+AssetManager.STYLE_TYPE];
238         if (type == TypedValue.TYPE_STRING) {
239             final int cookie = data[index+AssetManager.STYLE_ASSET_COOKIE];
240             if (cookie < 0) {
241                 return mXml.getPooledString(
242                     data[index+AssetManager.STYLE_DATA]).toString();
243             }
244         }
245         return null;
246     }
247 
248     /**
249      * Retrieves the string value for the attribute at <var>index</var> that is
250      * not allowed to change with the given configurations.
251      *
252      * @param index Index of attribute to retrieve.
253      * @param allowedChangingConfigs Bit mask of configurations from
254      *        {@link Configuration}.NATIVE_CONFIG_* that are allowed to change.
255      *
256      * @return String holding string data. Any styling information is removed.
257      *         Returns {@code null} if the attribute is not defined.
258      * @throws RuntimeException if the TypedArray has already been recycled.
259      * @hide
260      */
getNonConfigurationString(@tyleableRes int index, @Config int allowedChangingConfigs)261     public String getNonConfigurationString(@StyleableRes int index,
262             @Config int allowedChangingConfigs) {
263         if (mRecycled) {
264             throw new RuntimeException("Cannot make calls to a recycled instance!");
265         }
266 
267         index *= AssetManager.STYLE_NUM_ENTRIES;
268         final int[] data = mData;
269         final int type = data[index+AssetManager.STYLE_TYPE];
270         final @Config int changingConfigs = ActivityInfo.activityInfoConfigNativeToJava(
271                 data[index + AssetManager.STYLE_CHANGING_CONFIGURATIONS]);
272         if ((changingConfigs & ~allowedChangingConfigs) != 0) {
273             return null;
274         }
275         if (type == TypedValue.TYPE_NULL) {
276             return null;
277         } else if (type == TypedValue.TYPE_STRING) {
278             return loadStringValueAt(index).toString();
279         }
280 
281         final TypedValue v = mValue;
282         if (getValueAt(index, v)) {
283             final CharSequence cs = v.coerceToString();
284             return cs != null ? cs.toString() : null;
285         }
286 
287         // We already checked for TYPE_NULL. This should never happen.
288         throw new RuntimeException("getNonConfigurationString of bad type: 0x"
289                 + Integer.toHexString(type));
290     }
291 
292     /**
293      * Retrieve the boolean value for the attribute at <var>index</var>.
294      * <p>
295      * If the attribute is an integer value, this method will return whether
296      * it is equal to zero. If the attribute is not a boolean or integer value,
297      * this method will attempt to coerce it to an integer using
298      * {@link Integer#decode(String)} and return whether it is equal to zero.
299      *
300      * @param index Index of attribute to retrieve.
301      * @param defValue Value to return if the attribute is not defined or
302      *                 cannot be coerced to an integer.
303      *
304      * @return Boolean value of the attribute, or defValue if the attribute was
305      *         not defined or could not be coerced to an integer.
306      * @throws RuntimeException if the TypedArray has already been recycled.
307      */
getBoolean(@tyleableRes int index, boolean defValue)308     public boolean getBoolean(@StyleableRes int index, boolean defValue) {
309         if (mRecycled) {
310             throw new RuntimeException("Cannot make calls to a recycled instance!");
311         }
312 
313         index *= AssetManager.STYLE_NUM_ENTRIES;
314         final int[] data = mData;
315         final int type = data[index+AssetManager.STYLE_TYPE];
316         if (type == TypedValue.TYPE_NULL) {
317             return defValue;
318         } else if (type >= TypedValue.TYPE_FIRST_INT
319                 && type <= TypedValue.TYPE_LAST_INT) {
320             return data[index+AssetManager.STYLE_DATA] != 0;
321         }
322 
323         final TypedValue v = mValue;
324         if (getValueAt(index, v)) {
325             StrictMode.noteResourceMismatch(v);
326             return XmlUtils.convertValueToBoolean(v.coerceToString(), defValue);
327         }
328 
329         // We already checked for TYPE_NULL. This should never happen.
330         throw new RuntimeException("getBoolean of bad type: 0x" + Integer.toHexString(type));
331     }
332 
333     /**
334      * Retrieve the integer value for the attribute at <var>index</var>.
335      * <p>
336      * If the attribute is not an integer, this method will attempt to coerce
337      * it to an integer using {@link Integer#decode(String)}.
338      *
339      * @param index Index of attribute to retrieve.
340      * @param defValue Value to return if the attribute is not defined or
341      *                 cannot be coerced to an integer.
342      *
343      * @return Integer value of the attribute, or defValue if the attribute was
344      *         not defined or could not be coerced to an integer.
345      * @throws RuntimeException if the TypedArray has already been recycled.
346      */
getInt(@tyleableRes int index, int defValue)347     public int getInt(@StyleableRes int index, int defValue) {
348         if (mRecycled) {
349             throw new RuntimeException("Cannot make calls to a recycled instance!");
350         }
351 
352         index *= AssetManager.STYLE_NUM_ENTRIES;
353         final int[] data = mData;
354         final int type = data[index+AssetManager.STYLE_TYPE];
355         if (type == TypedValue.TYPE_NULL) {
356             return defValue;
357         } else if (type >= TypedValue.TYPE_FIRST_INT
358                 && type <= TypedValue.TYPE_LAST_INT) {
359             return data[index+AssetManager.STYLE_DATA];
360         }
361 
362         final TypedValue v = mValue;
363         if (getValueAt(index, v)) {
364             StrictMode.noteResourceMismatch(v);
365             return XmlUtils.convertValueToInt(v.coerceToString(), defValue);
366         }
367 
368         // We already checked for TYPE_NULL. This should never happen.
369         throw new RuntimeException("getInt of bad type: 0x" + Integer.toHexString(type));
370     }
371 
372     /**
373      * Retrieve the float value for the attribute at <var>index</var>.
374      * <p>
375      * If the attribute is not a float or an integer, this method will attempt
376      * to coerce it to a float using {@link Float#parseFloat(String)}.
377      *
378      * @param index Index of attribute to retrieve.
379      *
380      * @return Attribute float value, or defValue if the attribute was
381      *         not defined or could not be coerced to a float.
382      * @throws RuntimeException if the TypedArray has already been recycled.
383      */
getFloat(@tyleableRes int index, float defValue)384     public float getFloat(@StyleableRes int index, float defValue) {
385         if (mRecycled) {
386             throw new RuntimeException("Cannot make calls to a recycled instance!");
387         }
388 
389         index *= AssetManager.STYLE_NUM_ENTRIES;
390         final int[] data = mData;
391         final int type = data[index+AssetManager.STYLE_TYPE];
392         if (type == TypedValue.TYPE_NULL) {
393             return defValue;
394         } else if (type == TypedValue.TYPE_FLOAT) {
395             return Float.intBitsToFloat(data[index+AssetManager.STYLE_DATA]);
396         } else if (type >= TypedValue.TYPE_FIRST_INT
397                 && type <= TypedValue.TYPE_LAST_INT) {
398             return data[index+AssetManager.STYLE_DATA];
399         }
400 
401         final TypedValue v = mValue;
402         if (getValueAt(index, v)) {
403             final CharSequence str = v.coerceToString();
404             if (str != null) {
405                 StrictMode.noteResourceMismatch(v);
406                 return Float.parseFloat(str.toString());
407             }
408         }
409 
410         // We already checked for TYPE_NULL. This should never happen.
411         throw new RuntimeException("getFloat of bad type: 0x" + Integer.toHexString(type));
412     }
413 
414     /**
415      * Retrieve the color value for the attribute at <var>index</var>.  If
416      * the attribute references a color resource holding a complex
417      * {@link android.content.res.ColorStateList}, then the default color from
418      * the set is returned.
419      * <p>
420      * This method will throw an exception if the attribute is defined but is
421      * not an integer color or color state list.
422      *
423      * @param index Index of attribute to retrieve.
424      * @param defValue Value to return if the attribute is not defined or
425      *                 not a resource.
426      *
427      * @return Attribute color value, or defValue if not defined.
428      * @throws RuntimeException if the TypedArray has already been recycled.
429      * @throws UnsupportedOperationException if the attribute is defined but is
430      *         not an integer color or color state list.
431      */
432     @ColorInt
getColor(@tyleableRes int index, @ColorInt int defValue)433     public int getColor(@StyleableRes int index, @ColorInt int defValue) {
434         if (mRecycled) {
435             throw new RuntimeException("Cannot make calls to a recycled instance!");
436         }
437 
438         final int attrIndex = index;
439         index *= AssetManager.STYLE_NUM_ENTRIES;
440 
441         final int[] data = mData;
442         final int type = data[index+AssetManager.STYLE_TYPE];
443         if (type == TypedValue.TYPE_NULL) {
444             return defValue;
445         } else if (type >= TypedValue.TYPE_FIRST_INT
446                 && type <= TypedValue.TYPE_LAST_INT) {
447             return data[index+AssetManager.STYLE_DATA];
448         } else if (type == TypedValue.TYPE_STRING) {
449             final TypedValue value = mValue;
450             if (getValueAt(index, value)) {
451                 final ColorStateList csl = mResources.loadColorStateList(
452                         value, value.resourceId, mTheme);
453                 return csl.getDefaultColor();
454             }
455             return defValue;
456         } else if (type == TypedValue.TYPE_ATTRIBUTE) {
457             final TypedValue value = mValue;
458             getValueAt(index, value);
459             throw new UnsupportedOperationException(
460                     "Failed to resolve attribute at index " + attrIndex + ": " + value);
461         }
462 
463         throw new UnsupportedOperationException("Can't convert value at index " + attrIndex
464                 + " to color: type=0x" + Integer.toHexString(type));
465     }
466 
467     /**
468      * Retrieve the ComplexColor for the attribute at <var>index</var>.
469      * The value may be either a {@link android.content.res.ColorStateList} which can wrap a simple
470      * color value or a {@link android.content.res.GradientColor}
471      * <p>
472      * This method will return {@code null} if the attribute is not defined or
473      * is not an integer color, color state list or GradientColor.
474      *
475      * @param index Index of attribute to retrieve.
476      *
477      * @return ComplexColor for the attribute, or {@code null} if not defined.
478      * @throws RuntimeException if the attribute if the TypedArray has already
479      *         been recycled.
480      * @throws UnsupportedOperationException if the attribute is defined but is
481      *         not an integer color, color state list or GradientColor.
482      * @hide
483      */
484     @Nullable
getComplexColor(@tyleableRes int index)485     public ComplexColor getComplexColor(@StyleableRes int index) {
486         if (mRecycled) {
487             throw new RuntimeException("Cannot make calls to a recycled instance!");
488         }
489 
490         final TypedValue value = mValue;
491         if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
492             if (value.type == TypedValue.TYPE_ATTRIBUTE) {
493                 throw new UnsupportedOperationException(
494                         "Failed to resolve attribute at index " + index + ": " + value);
495             }
496             return mResources.loadComplexColor(value, value.resourceId, mTheme);
497         }
498         return null;
499     }
500 
501     /**
502      * Retrieve the ColorStateList for the attribute at <var>index</var>.
503      * The value may be either a single solid color or a reference to
504      * a color or complex {@link android.content.res.ColorStateList}
505      * description.
506      * <p>
507      * This method will return {@code null} if the attribute is not defined or
508      * is not an integer color or color state list.
509      *
510      * @param index Index of attribute to retrieve.
511      *
512      * @return ColorStateList for the attribute, or {@code null} if not
513      *         defined.
514      * @throws RuntimeException if the attribute if the TypedArray has already
515      *         been recycled.
516      * @throws UnsupportedOperationException if the attribute is defined but is
517      *         not an integer color or color state list.
518      */
519     @Nullable
getColorStateList(@tyleableRes int index)520     public ColorStateList getColorStateList(@StyleableRes int index) {
521         if (mRecycled) {
522             throw new RuntimeException("Cannot make calls to a recycled instance!");
523         }
524 
525         final TypedValue value = mValue;
526         if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
527             if (value.type == TypedValue.TYPE_ATTRIBUTE) {
528                 throw new UnsupportedOperationException(
529                         "Failed to resolve attribute at index " + index + ": " + value);
530             }
531             return mResources.loadColorStateList(value, value.resourceId, mTheme);
532         }
533         return null;
534     }
535 
536     /**
537      * Retrieve the integer value for the attribute at <var>index</var>.
538      * <p>
539      * Unlike {@link #getInt(int, int)}, this method will throw an exception if
540      * the attribute is defined but is not an integer.
541      *
542      * @param index Index of attribute to retrieve.
543      * @param defValue Value to return if the attribute is not defined or
544      *                 not a resource.
545      *
546      * @return Attribute integer value, or defValue if not defined.
547      * @throws RuntimeException if the TypedArray has already been recycled.
548      * @throws UnsupportedOperationException if the attribute is defined but is
549      *         not an integer.
550      */
getInteger(@tyleableRes int index, int defValue)551     public int getInteger(@StyleableRes int index, int defValue) {
552         if (mRecycled) {
553             throw new RuntimeException("Cannot make calls to a recycled instance!");
554         }
555 
556         final int attrIndex = index;
557         index *= AssetManager.STYLE_NUM_ENTRIES;
558 
559         final int[] data = mData;
560         final int type = data[index+AssetManager.STYLE_TYPE];
561         if (type == TypedValue.TYPE_NULL) {
562             return defValue;
563         } else if (type >= TypedValue.TYPE_FIRST_INT
564                 && type <= TypedValue.TYPE_LAST_INT) {
565             return data[index+AssetManager.STYLE_DATA];
566         } else if (type == TypedValue.TYPE_ATTRIBUTE) {
567             final TypedValue value = mValue;
568             getValueAt(index, value);
569             throw new UnsupportedOperationException(
570                     "Failed to resolve attribute at index " + attrIndex + ": " + value);
571         }
572 
573         throw new UnsupportedOperationException("Can't convert value at index " + attrIndex
574                 + " to integer: type=0x" + Integer.toHexString(type));
575     }
576 
577     /**
578      * Retrieve a dimensional unit attribute at <var>index</var>. Unit
579      * conversions are based on the current {@link DisplayMetrics}
580      * associated with the resources this {@link TypedArray} object
581      * came from.
582      * <p>
583      * This method will throw an exception if the attribute is defined but is
584      * not a dimension.
585      *
586      * @param index Index of attribute to retrieve.
587      * @param defValue Value to return if the attribute is not defined or
588      *                 not a resource.
589      *
590      * @return Attribute dimension value multiplied by the appropriate
591      *         metric, or defValue if not defined.
592      * @throws RuntimeException if the TypedArray has already been recycled.
593      * @throws UnsupportedOperationException if the attribute is defined but is
594      *         not an integer.
595      *
596      * @see #getDimensionPixelOffset
597      * @see #getDimensionPixelSize
598      */
getDimension(@tyleableRes int index, float defValue)599     public float getDimension(@StyleableRes int index, float defValue) {
600         if (mRecycled) {
601             throw new RuntimeException("Cannot make calls to a recycled instance!");
602         }
603 
604         final int attrIndex = index;
605         index *= AssetManager.STYLE_NUM_ENTRIES;
606 
607         final int[] data = mData;
608         final int type = data[index+AssetManager.STYLE_TYPE];
609         if (type == TypedValue.TYPE_NULL) {
610             return defValue;
611         } else if (type == TypedValue.TYPE_DIMENSION) {
612             return TypedValue.complexToDimension(
613                     data[index + AssetManager.STYLE_DATA], mMetrics);
614         } else if (type == TypedValue.TYPE_ATTRIBUTE) {
615             final TypedValue value = mValue;
616             getValueAt(index, value);
617             throw new UnsupportedOperationException(
618                     "Failed to resolve attribute at index " + attrIndex + ": " + value);
619         }
620 
621         throw new UnsupportedOperationException("Can't convert value at index " + attrIndex
622                 + " to dimension: type=0x" + Integer.toHexString(type));
623     }
624 
625     /**
626      * Retrieve a dimensional unit attribute at <var>index</var> for use
627      * as an offset in raw pixels.  This is the same as
628      * {@link #getDimension}, except the returned value is converted to
629      * integer pixels for you.  An offset conversion involves simply
630      * truncating the base value to an integer.
631      * <p>
632      * This method will throw an exception if the attribute is defined but is
633      * not a dimension.
634      *
635      * @param index Index of attribute to retrieve.
636      * @param defValue Value to return if the attribute is not defined or
637      *                 not a resource.
638      *
639      * @return Attribute dimension value multiplied by the appropriate
640      *         metric and truncated to integer pixels, or defValue if not defined.
641      * @throws RuntimeException if the TypedArray has already been recycled.
642      * @throws UnsupportedOperationException if the attribute is defined but is
643      *         not an integer.
644      *
645      * @see #getDimension
646      * @see #getDimensionPixelSize
647      */
getDimensionPixelOffset(@tyleableRes int index, int defValue)648     public int getDimensionPixelOffset(@StyleableRes int index, int defValue) {
649         if (mRecycled) {
650             throw new RuntimeException("Cannot make calls to a recycled instance!");
651         }
652 
653         final int attrIndex = index;
654         index *= AssetManager.STYLE_NUM_ENTRIES;
655 
656         final int[] data = mData;
657         final int type = data[index+AssetManager.STYLE_TYPE];
658         if (type == TypedValue.TYPE_NULL) {
659             return defValue;
660         } else if (type == TypedValue.TYPE_DIMENSION) {
661             return TypedValue.complexToDimensionPixelOffset(
662                     data[index + AssetManager.STYLE_DATA], mMetrics);
663         } else if (type == TypedValue.TYPE_ATTRIBUTE) {
664             final TypedValue value = mValue;
665             getValueAt(index, value);
666             throw new UnsupportedOperationException(
667                     "Failed to resolve attribute at index " + attrIndex + ": " + value);
668         }
669 
670         throw new UnsupportedOperationException("Can't convert value at index " + attrIndex
671                 + " to dimension: type=0x" + Integer.toHexString(type));
672     }
673 
674     /**
675      * Retrieve a dimensional unit attribute at <var>index</var> for use
676      * as a size in raw pixels.  This is the same as
677      * {@link #getDimension}, except the returned value is converted to
678      * integer pixels for use as a size.  A size conversion involves
679      * rounding the base value, and ensuring that a non-zero base value
680      * is at least one pixel in size.
681      * <p>
682      * This method will throw an exception if the attribute is defined but is
683      * not a dimension.
684      *
685      * @param index Index of attribute to retrieve.
686      * @param defValue Value to return if the attribute is not defined or
687      *                 not a resource.
688      *
689      * @return Attribute dimension value multiplied by the appropriate
690      *         metric and truncated to integer pixels, or defValue if not defined.
691      * @throws RuntimeException if the TypedArray has already been recycled.
692      * @throws UnsupportedOperationException if the attribute is defined but is
693      *         not a dimension.
694      *
695      * @see #getDimension
696      * @see #getDimensionPixelOffset
697      */
getDimensionPixelSize(@tyleableRes int index, int defValue)698     public int getDimensionPixelSize(@StyleableRes int index, int defValue) {
699         if (mRecycled) {
700             throw new RuntimeException("Cannot make calls to a recycled instance!");
701         }
702 
703         final int attrIndex = index;
704         index *= AssetManager.STYLE_NUM_ENTRIES;
705 
706         final int[] data = mData;
707         final int type = data[index+AssetManager.STYLE_TYPE];
708         if (type == TypedValue.TYPE_NULL) {
709             return defValue;
710         } else if (type == TypedValue.TYPE_DIMENSION) {
711             return TypedValue.complexToDimensionPixelSize(
712                 data[index+AssetManager.STYLE_DATA], mMetrics);
713         } else if (type == TypedValue.TYPE_ATTRIBUTE) {
714             final TypedValue value = mValue;
715             getValueAt(index, value);
716             throw new UnsupportedOperationException(
717                     "Failed to resolve attribute at index " + attrIndex + ": " + value);
718         }
719 
720         throw new UnsupportedOperationException("Can't convert value at index " + attrIndex
721                 + " to dimension: type=0x" + Integer.toHexString(type));
722     }
723 
724     /**
725      * Special version of {@link #getDimensionPixelSize} for retrieving
726      * {@link android.view.ViewGroup}'s layout_width and layout_height
727      * attributes.  This is only here for performance reasons; applications
728      * should use {@link #getDimensionPixelSize}.
729      * <p>
730      * This method will throw an exception if the attribute is defined but is
731      * not a dimension or integer (enum).
732      *
733      * @param index Index of the attribute to retrieve.
734      * @param name Textual name of attribute for error reporting.
735      *
736      * @return Attribute dimension value multiplied by the appropriate
737      *         metric and truncated to integer pixels.
738      * @throws RuntimeException if the TypedArray has already been recycled.
739      * @throws UnsupportedOperationException if the attribute is defined but is
740      *         not a dimension or integer (enum).
741      */
getLayoutDimension(@tyleableRes int index, String name)742     public int getLayoutDimension(@StyleableRes int index, String name) {
743         if (mRecycled) {
744             throw new RuntimeException("Cannot make calls to a recycled instance!");
745         }
746 
747         final int attrIndex = index;
748         index *= AssetManager.STYLE_NUM_ENTRIES;
749 
750         final int[] data = mData;
751         final int type = data[index+AssetManager.STYLE_TYPE];
752         if (type >= TypedValue.TYPE_FIRST_INT
753                 && type <= TypedValue.TYPE_LAST_INT) {
754             return data[index+AssetManager.STYLE_DATA];
755         } else if (type == TypedValue.TYPE_DIMENSION) {
756             return TypedValue.complexToDimensionPixelSize(
757                 data[index+AssetManager.STYLE_DATA], mMetrics);
758         } else if (type == TypedValue.TYPE_ATTRIBUTE) {
759             final TypedValue value = mValue;
760             getValueAt(index, value);
761             throw new UnsupportedOperationException(
762                     "Failed to resolve attribute at index " + attrIndex + ": " + value);
763         }
764 
765         throw new UnsupportedOperationException(getPositionDescription()
766                 + ": You must supply a " + name + " attribute.");
767     }
768 
769     /**
770      * Special version of {@link #getDimensionPixelSize} for retrieving
771      * {@link android.view.ViewGroup}'s layout_width and layout_height
772      * attributes.  This is only here for performance reasons; applications
773      * should use {@link #getDimensionPixelSize}.
774      *
775      * @param index Index of the attribute to retrieve.
776      * @param defValue The default value to return if this attribute is not
777      *                 default or contains the wrong type of data.
778      *
779      * @return Attribute dimension value multiplied by the appropriate
780      *         metric and truncated to integer pixels.
781      * @throws RuntimeException if the TypedArray has already been recycled.
782      */
getLayoutDimension(@tyleableRes int index, int defValue)783     public int getLayoutDimension(@StyleableRes int index, int defValue) {
784         if (mRecycled) {
785             throw new RuntimeException("Cannot make calls to a recycled instance!");
786         }
787 
788         index *= AssetManager.STYLE_NUM_ENTRIES;
789         final int[] data = mData;
790         final int type = data[index+AssetManager.STYLE_TYPE];
791         if (type >= TypedValue.TYPE_FIRST_INT
792                 && type <= TypedValue.TYPE_LAST_INT) {
793             return data[index+AssetManager.STYLE_DATA];
794         } else if (type == TypedValue.TYPE_DIMENSION) {
795             return TypedValue.complexToDimensionPixelSize(
796                     data[index + AssetManager.STYLE_DATA], mMetrics);
797         }
798 
799         return defValue;
800     }
801 
802     /**
803      * Retrieves a fractional unit attribute at <var>index</var>.
804      *
805      * @param index Index of attribute to retrieve.
806      * @param base The base value of this fraction.  In other words, a
807      *             standard fraction is multiplied by this value.
808      * @param pbase The parent base value of this fraction.  In other
809      *             words, a parent fraction (nn%p) is multiplied by this
810      *             value.
811      * @param defValue Value to return if the attribute is not defined or
812      *                 not a resource.
813      *
814      * @return Attribute fractional value multiplied by the appropriate
815      *         base value, or defValue if not defined.
816      * @throws RuntimeException if the TypedArray has already been recycled.
817      * @throws UnsupportedOperationException if the attribute is defined but is
818      *         not a fraction.
819      */
getFraction(@tyleableRes int index, int base, int pbase, float defValue)820     public float getFraction(@StyleableRes int index, int base, int pbase, float defValue) {
821         if (mRecycled) {
822             throw new RuntimeException("Cannot make calls to a recycled instance!");
823         }
824 
825         final int attrIndex = index;
826         index *= AssetManager.STYLE_NUM_ENTRIES;
827 
828         final int[] data = mData;
829         final int type = data[index+AssetManager.STYLE_TYPE];
830         if (type == TypedValue.TYPE_NULL) {
831             return defValue;
832         } else if (type == TypedValue.TYPE_FRACTION) {
833             return TypedValue.complexToFraction(
834                 data[index+AssetManager.STYLE_DATA], base, pbase);
835         } else if (type == TypedValue.TYPE_ATTRIBUTE) {
836             final TypedValue value = mValue;
837             getValueAt(index, value);
838             throw new UnsupportedOperationException(
839                     "Failed to resolve attribute at index " + attrIndex + ": " + value);
840         }
841 
842         throw new UnsupportedOperationException("Can't convert value at index " + attrIndex
843                 + " to fraction: type=0x" + Integer.toHexString(type));
844     }
845 
846     /**
847      * Retrieves the resource identifier for the attribute at
848      * <var>index</var>.  Note that attribute resource as resolved when
849      * the overall {@link TypedArray} object is retrieved.  As a
850      * result, this function will return the resource identifier of the
851      * final resource value that was found, <em>not</em> necessarily the
852      * original resource that was specified by the attribute.
853      *
854      * @param index Index of attribute to retrieve.
855      * @param defValue Value to return if the attribute is not defined or
856      *                 not a resource.
857      *
858      * @return Attribute resource identifier, or defValue if not defined.
859      * @throws RuntimeException if the TypedArray has already been recycled.
860      */
861     @AnyRes
getResourceId(@tyleableRes int index, int defValue)862     public int getResourceId(@StyleableRes int index, int defValue) {
863         if (mRecycled) {
864             throw new RuntimeException("Cannot make calls to a recycled instance!");
865         }
866 
867         index *= AssetManager.STYLE_NUM_ENTRIES;
868         final int[] data = mData;
869         if (data[index+AssetManager.STYLE_TYPE] != TypedValue.TYPE_NULL) {
870             final int resid = data[index+AssetManager.STYLE_RESOURCE_ID];
871             if (resid != 0) {
872                 return resid;
873             }
874         }
875         return defValue;
876     }
877 
878     /**
879      * Retrieves the theme attribute resource identifier for the attribute at
880      * <var>index</var>.
881      *
882      * @param index Index of attribute to retrieve.
883      * @param defValue Value to return if the attribute is not defined or not a
884      *                 resource.
885      *
886      * @return Theme attribute resource identifier, or defValue if not defined.
887      * @throws RuntimeException if the TypedArray has already been recycled.
888      * @hide
889      */
getThemeAttributeId(@tyleableRes int index, int defValue)890     public int getThemeAttributeId(@StyleableRes int index, int defValue) {
891         if (mRecycled) {
892             throw new RuntimeException("Cannot make calls to a recycled instance!");
893         }
894 
895         index *= AssetManager.STYLE_NUM_ENTRIES;
896         final int[] data = mData;
897         if (data[index + AssetManager.STYLE_TYPE] == TypedValue.TYPE_ATTRIBUTE) {
898             return data[index + AssetManager.STYLE_DATA];
899         }
900         return defValue;
901     }
902 
903     /**
904      * Retrieve the Drawable for the attribute at <var>index</var>.
905      * <p>
906      * This method will throw an exception if the attribute is defined but is
907      * not a color or drawable resource.
908      *
909      * @param index Index of attribute to retrieve.
910      *
911      * @return Drawable for the attribute, or {@code null} if not defined.
912      * @throws RuntimeException if the TypedArray has already been recycled.
913      * @throws UnsupportedOperationException if the attribute is defined but is
914      *         not a color or drawable resource.
915      */
916     @Nullable
getDrawable(@tyleableRes int index)917     public Drawable getDrawable(@StyleableRes int index) {
918         if (mRecycled) {
919             throw new RuntimeException("Cannot make calls to a recycled instance!");
920         }
921 
922         final TypedValue value = mValue;
923         if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
924             if (value.type == TypedValue.TYPE_ATTRIBUTE) {
925                 throw new UnsupportedOperationException(
926                         "Failed to resolve attribute at index " + index + ": " + value);
927             }
928             return mResources.loadDrawable(value, value.resourceId, mTheme);
929         }
930         return null;
931     }
932 
933     /**
934      * Retrieve the CharSequence[] for the attribute at <var>index</var>.
935      * This gets the resource ID of the selected attribute, and uses
936      * {@link Resources#getTextArray Resources.getTextArray} of the owning
937      * Resources object to retrieve its String[].
938      * <p>
939      * This method will throw an exception if the attribute is defined but is
940      * not a text array resource.
941      *
942      * @param index Index of attribute to retrieve.
943      *
944      * @return CharSequence[] for the attribute, or {@code null} if not
945      *         defined.
946      * @throws RuntimeException if the TypedArray has already been recycled.
947      */
getTextArray(@tyleableRes int index)948     public CharSequence[] getTextArray(@StyleableRes int index) {
949         if (mRecycled) {
950             throw new RuntimeException("Cannot make calls to a recycled instance!");
951         }
952 
953         final TypedValue value = mValue;
954         if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
955             return mResources.getTextArray(value.resourceId);
956         }
957         return null;
958     }
959 
960     /**
961      * Retrieve the raw TypedValue for the attribute at <var>index</var>.
962      *
963      * @param index Index of attribute to retrieve.
964      * @param outValue TypedValue object in which to place the attribute's
965      *                 data.
966      *
967      * @return {@code true} if the value was retrieved, false otherwise.
968      * @throws RuntimeException if the TypedArray has already been recycled.
969      */
getValue(@tyleableRes int index, TypedValue outValue)970     public boolean getValue(@StyleableRes int index, TypedValue outValue) {
971         if (mRecycled) {
972             throw new RuntimeException("Cannot make calls to a recycled instance!");
973         }
974 
975         return getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, outValue);
976     }
977 
978     /**
979      * Returns the type of attribute at the specified index.
980      *
981      * @param index Index of attribute whose type to retrieve.
982      *
983      * @return Attribute type.
984      * @throws RuntimeException if the TypedArray has already been recycled.
985      */
getType(@tyleableRes int index)986     public int getType(@StyleableRes int index) {
987         if (mRecycled) {
988             throw new RuntimeException("Cannot make calls to a recycled instance!");
989         }
990 
991         index *= AssetManager.STYLE_NUM_ENTRIES;
992         return mData[index + AssetManager.STYLE_TYPE];
993     }
994 
995     /**
996      * Determines whether there is an attribute at <var>index</var>.
997      * <p>
998      * <strong>Note:</strong> If the attribute was set to {@code @empty} or
999      * {@code @undefined}, this method returns {@code false}.
1000      *
1001      * @param index Index of attribute to retrieve.
1002      *
1003      * @return True if the attribute has a value, false otherwise.
1004      * @throws RuntimeException if the TypedArray has already been recycled.
1005      */
hasValue(@tyleableRes int index)1006     public boolean hasValue(@StyleableRes int index) {
1007         if (mRecycled) {
1008             throw new RuntimeException("Cannot make calls to a recycled instance!");
1009         }
1010 
1011         index *= AssetManager.STYLE_NUM_ENTRIES;
1012         final int[] data = mData;
1013         final int type = data[index+AssetManager.STYLE_TYPE];
1014         return type != TypedValue.TYPE_NULL;
1015     }
1016 
1017     /**
1018      * Determines whether there is an attribute at <var>index</var>, returning
1019      * {@code true} if the attribute was explicitly set to {@code @empty} and
1020      * {@code false} only if the attribute was undefined.
1021      *
1022      * @param index Index of attribute to retrieve.
1023      *
1024      * @return True if the attribute has a value or is empty, false otherwise.
1025      * @throws RuntimeException if the TypedArray has already been recycled.
1026      */
hasValueOrEmpty(@tyleableRes int index)1027     public boolean hasValueOrEmpty(@StyleableRes int index) {
1028         if (mRecycled) {
1029             throw new RuntimeException("Cannot make calls to a recycled instance!");
1030         }
1031 
1032         index *= AssetManager.STYLE_NUM_ENTRIES;
1033         final int[] data = mData;
1034         final int type = data[index+AssetManager.STYLE_TYPE];
1035         return type != TypedValue.TYPE_NULL
1036                 || data[index+AssetManager.STYLE_DATA] == TypedValue.DATA_NULL_EMPTY;
1037     }
1038 
1039     /**
1040      * Retrieve the raw TypedValue for the attribute at <var>index</var>
1041      * and return a temporary object holding its data.  This object is only
1042      * valid until the next call on to {@link TypedArray}.
1043      *
1044      * @param index Index of attribute to retrieve.
1045      *
1046      * @return Returns a TypedValue object if the attribute is defined,
1047      *         containing its data; otherwise returns null.  (You will not
1048      *         receive a TypedValue whose type is TYPE_NULL.)
1049      * @throws RuntimeException if the TypedArray has already been recycled.
1050      */
peekValue(@tyleableRes int index)1051     public TypedValue peekValue(@StyleableRes int index) {
1052         if (mRecycled) {
1053             throw new RuntimeException("Cannot make calls to a recycled instance!");
1054         }
1055 
1056         final TypedValue value = mValue;
1057         if (getValueAt(index*AssetManager.STYLE_NUM_ENTRIES, value)) {
1058             return value;
1059         }
1060         return null;
1061     }
1062 
1063     /**
1064      * Returns a message about the parser state suitable for printing error messages.
1065      *
1066      * @return Human-readable description of current parser state.
1067      * @throws RuntimeException if the TypedArray has already been recycled.
1068      */
getPositionDescription()1069     public String getPositionDescription() {
1070         if (mRecycled) {
1071             throw new RuntimeException("Cannot make calls to a recycled instance!");
1072         }
1073 
1074         return mXml != null ? mXml.getPositionDescription() : "<internal>";
1075     }
1076 
1077     /**
1078      * Recycles the TypedArray, to be re-used by a later caller. After calling
1079      * this function you must not ever touch the typed array again.
1080      *
1081      * @throws RuntimeException if the TypedArray has already been recycled.
1082      */
recycle()1083     public void recycle() {
1084         if (mRecycled) {
1085             throw new RuntimeException(toString() + " recycled twice!");
1086         }
1087 
1088         mRecycled = true;
1089 
1090         // These may have been set by the client.
1091         mXml = null;
1092         mTheme = null;
1093         mAssets = null;
1094 
1095         mResources.mTypedArrayPool.release(this);
1096     }
1097 
1098     /**
1099      * Extracts theme attributes from a typed array for later resolution using
1100      * {@link android.content.res.Resources.Theme#resolveAttributes(int[], int[])}.
1101      * Removes the entries from the typed array so that subsequent calls to typed
1102      * getters will return the default value without crashing.
1103      *
1104      * @return an array of length {@link #getIndexCount()} populated with theme
1105      *         attributes, or null if there are no theme attributes in the typed
1106      *         array
1107      * @throws RuntimeException if the TypedArray has already been recycled.
1108      * @hide
1109      */
1110     @Nullable
extractThemeAttrs()1111     public int[] extractThemeAttrs() {
1112         return extractThemeAttrs(null);
1113     }
1114 
1115     /**
1116      * @hide
1117      */
1118     @Nullable
extractThemeAttrs(@ullable int[] scrap)1119     public int[] extractThemeAttrs(@Nullable int[] scrap) {
1120         if (mRecycled) {
1121             throw new RuntimeException("Cannot make calls to a recycled instance!");
1122         }
1123 
1124         int[] attrs = null;
1125 
1126         final int[] data = mData;
1127         final int N = length();
1128         for (int i = 0; i < N; i++) {
1129             final int index = i * AssetManager.STYLE_NUM_ENTRIES;
1130             if (data[index + AssetManager.STYLE_TYPE] != TypedValue.TYPE_ATTRIBUTE) {
1131                 // Not an attribute, ignore.
1132                 continue;
1133             }
1134 
1135             // Null the entry so that we can safely call getZzz().
1136             data[index + AssetManager.STYLE_TYPE] = TypedValue.TYPE_NULL;
1137 
1138             final int attr = data[index + AssetManager.STYLE_DATA];
1139             if (attr == 0) {
1140                 // Useless data, ignore.
1141                 continue;
1142             }
1143 
1144             // Ensure we have a usable attribute array.
1145             if (attrs == null) {
1146                 if (scrap != null && scrap.length == N) {
1147                     attrs = scrap;
1148                     Arrays.fill(attrs, 0);
1149                 } else {
1150                     attrs = new int[N];
1151                 }
1152             }
1153 
1154             attrs[i] = attr;
1155         }
1156 
1157         return attrs;
1158     }
1159 
1160     /**
1161      * Return a mask of the configuration parameters for which the values in
1162      * this typed array may change.
1163      *
1164      * @return Returns a mask of the changing configuration parameters, as
1165      *         defined by {@link android.content.pm.ActivityInfo}.
1166      * @throws RuntimeException if the TypedArray has already been recycled.
1167      * @see android.content.pm.ActivityInfo
1168      */
getChangingConfigurations()1169     public @Config int getChangingConfigurations() {
1170         if (mRecycled) {
1171             throw new RuntimeException("Cannot make calls to a recycled instance!");
1172         }
1173 
1174         @Config int changingConfig = 0;
1175 
1176         final int[] data = mData;
1177         final int N = length();
1178         for (int i = 0; i < N; i++) {
1179             final int index = i * AssetManager.STYLE_NUM_ENTRIES;
1180             final int type = data[index + AssetManager.STYLE_TYPE];
1181             if (type == TypedValue.TYPE_NULL) {
1182                 continue;
1183             }
1184             changingConfig |= ActivityInfo.activityInfoConfigNativeToJava(
1185                     data[index + AssetManager.STYLE_CHANGING_CONFIGURATIONS]);
1186         }
1187         return changingConfig;
1188     }
1189 
getValueAt(int index, TypedValue outValue)1190     private boolean getValueAt(int index, TypedValue outValue) {
1191         final int[] data = mData;
1192         final int type = data[index+AssetManager.STYLE_TYPE];
1193         if (type == TypedValue.TYPE_NULL) {
1194             return false;
1195         }
1196         outValue.type = type;
1197         outValue.data = data[index+AssetManager.STYLE_DATA];
1198         outValue.assetCookie = data[index+AssetManager.STYLE_ASSET_COOKIE];
1199         outValue.resourceId = data[index+AssetManager.STYLE_RESOURCE_ID];
1200         outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava(
1201                 data[index + AssetManager.STYLE_CHANGING_CONFIGURATIONS]);
1202         outValue.density = data[index+AssetManager.STYLE_DENSITY];
1203         outValue.string = (type == TypedValue.TYPE_STRING) ? loadStringValueAt(index) : null;
1204         return true;
1205     }
1206 
loadStringValueAt(int index)1207     private CharSequence loadStringValueAt(int index) {
1208         final int[] data = mData;
1209         final int cookie = data[index+AssetManager.STYLE_ASSET_COOKIE];
1210         if (cookie < 0) {
1211             if (mXml != null) {
1212                 return mXml.getPooledString(
1213                     data[index+AssetManager.STYLE_DATA]);
1214             }
1215             return null;
1216         }
1217         return mAssets.getPooledStringForCookie(cookie, data[index+AssetManager.STYLE_DATA]);
1218     }
1219 
TypedArray(Resources resources, int[] data, int[] indices, int len)1220     /*package*/ TypedArray(Resources resources, int[] data, int[] indices, int len) {
1221         mResources = resources;
1222         mMetrics = mResources.getDisplayMetrics();
1223         mAssets = mResources.getAssets();
1224         mData = data;
1225         mIndices = indices;
1226         mLength = len;
1227     }
1228 
1229     @Override
toString()1230     public String toString() {
1231         return Arrays.toString(mData);
1232     }
1233 }
1234