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