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