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