[go: nahoru, domu]

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 com.android.ide.common.rendering.api.ArrayResourceValue;
20import com.android.ide.common.rendering.api.AttrResourceValue;
21import com.android.ide.common.rendering.api.LayoutLog;
22import com.android.ide.common.rendering.api.RenderResources;
23import com.android.ide.common.rendering.api.ResourceValue;
24import com.android.ide.common.rendering.api.StyleResourceValue;
25import com.android.internal.util.XmlUtils;
26import com.android.layoutlib.bridge.Bridge;
27import com.android.layoutlib.bridge.android.BridgeContext;
28import com.android.layoutlib.bridge.impl.ResourceHelper;
29import com.android.resources.ResourceType;
30
31import android.annotation.Nullable;
32import android.content.res.Resources.NotFoundException;
33import android.content.res.Resources.Theme;
34import android.graphics.drawable.Drawable;
35import android.util.DisplayMetrics;
36import android.util.TypedValue;
37import android.view.LayoutInflater_Delegate;
38import android.view.ViewGroup.LayoutParams;
39
40import java.util.ArrayList;
41import java.util.Arrays;
42import java.util.Map;
43
44import static android.util.TypedValue.TYPE_ATTRIBUTE;
45import static android.util.TypedValue.TYPE_DIMENSION;
46import static android.util.TypedValue.TYPE_FLOAT;
47import static android.util.TypedValue.TYPE_INT_BOOLEAN;
48import static android.util.TypedValue.TYPE_INT_COLOR_ARGB4;
49import static android.util.TypedValue.TYPE_INT_COLOR_ARGB8;
50import static android.util.TypedValue.TYPE_INT_COLOR_RGB4;
51import static android.util.TypedValue.TYPE_INT_COLOR_RGB8;
52import static android.util.TypedValue.TYPE_INT_DEC;
53import static android.util.TypedValue.TYPE_INT_HEX;
54import static android.util.TypedValue.TYPE_NULL;
55import static android.util.TypedValue.TYPE_REFERENCE;
56import static android.util.TypedValue.TYPE_STRING;
57import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
58import static com.android.SdkConstants.PREFIX_THEME_REF;
59import static com.android.ide.common.rendering.api.RenderResources.REFERENCE_EMPTY;
60import static com.android.ide.common.rendering.api.RenderResources.REFERENCE_NULL;
61import static com.android.ide.common.rendering.api.RenderResources.REFERENCE_UNDEFINED;
62
63/**
64 * Custom implementation of TypedArray to handle non compiled resources.
65 */
66public final class BridgeTypedArray extends TypedArray {
67
68    private final Resources mBridgeResources;
69    private final BridgeContext mContext;
70    private final boolean mPlatformFile;
71
72    private final ResourceValue[] mResourceData;
73    private final String[] mNames;
74    private final boolean[] mIsFramework;
75
76    // Contains ids that are @empty. We still store null in mResourceData for that index, since we
77    // want to save on the check against empty, each time a resource value is requested.
78    @Nullable
79    private int[] mEmptyIds;
80
81    public BridgeTypedArray(Resources resources, BridgeContext context, int len,
82            boolean platformFile) {
83        super(resources, null, null, 0);
84        mBridgeResources = resources;
85        mContext = context;
86        mPlatformFile = platformFile;
87        mResourceData = new ResourceValue[len];
88        mNames = new String[len];
89        mIsFramework = new boolean[len];
90    }
91
92    /**
93     * A bridge-specific method that sets a value in the type array
94     * @param index the index of the value in the TypedArray
95     * @param name the name of the attribute
96     * @param isFramework whether the attribute is in the android namespace.
97     * @param value the value of the attribute
98     */
99    public void bridgeSetValue(int index, String name, boolean isFramework, ResourceValue value) {
100        mResourceData[index] = value;
101        mNames[index] = name;
102        mIsFramework[index] = isFramework;
103    }
104
105    /**
106     * Seals the array after all calls to
107     * {@link #bridgeSetValue(int, String, boolean, ResourceValue)} have been done.
108     * <p/>This allows to compute the list of non default values, permitting
109     * {@link #getIndexCount()} to return the proper value.
110     */
111    public void sealArray() {
112        // fills TypedArray.mIndices which is used to implement getIndexCount/getIndexAt
113        // first count the array size
114        int count = 0;
115        ArrayList<Integer> emptyIds = null;
116        for (int i = 0; i < mResourceData.length; i++) {
117            ResourceValue data = mResourceData[i];
118            if (data != null) {
119                String dataValue = data.getValue();
120                if (REFERENCE_NULL.equals(dataValue) || REFERENCE_UNDEFINED.equals(dataValue)) {
121                    mResourceData[i] = null;
122                } else if (REFERENCE_EMPTY.equals(dataValue)) {
123                    mResourceData[i] = null;
124                    if (emptyIds == null) {
125                        emptyIds = new ArrayList<Integer>(4);
126                    }
127                    emptyIds.add(i);
128                } else {
129                    count++;
130                }
131            }
132        }
133
134        if (emptyIds != null) {
135            mEmptyIds = new int[emptyIds.size()];
136            for (int i = 0; i < emptyIds.size(); i++) {
137                mEmptyIds[i] = emptyIds.get(i);
138            }
139        }
140
141        // allocate the table with an extra to store the size
142        mIndices = new int[count+1];
143        mIndices[0] = count;
144
145        // fill the array with the indices.
146        int index = 1;
147        for (int i = 0 ; i < mResourceData.length ; i++) {
148            if (mResourceData[i] != null) {
149                mIndices[index++] = i;
150            }
151        }
152    }
153
154    /**
155     * Set the theme to be used for inflating drawables.
156     */
157    public void setTheme(Theme theme) {
158        mTheme = theme;
159    }
160
161    /**
162     * Return the number of values in this array.
163     */
164    @Override
165    public int length() {
166        return mResourceData.length;
167    }
168
169    /**
170     * Return the Resources object this array was loaded from.
171     */
172    @Override
173    public Resources getResources() {
174        return mBridgeResources;
175    }
176
177    /**
178     * Retrieve the styled string value for the attribute at <var>index</var>.
179     *
180     * @param index Index of attribute to retrieve.
181     *
182     * @return CharSequence holding string data.  May be styled.  Returns
183     *         null if the attribute is not defined.
184     */
185    @Override
186    public CharSequence getText(int index) {
187        // FIXME: handle styled strings!
188        return getString(index);
189    }
190
191    /**
192     * Retrieve the string value for the attribute at <var>index</var>.
193     *
194     * @param index Index of attribute to retrieve.
195     *
196     * @return String holding string data.  Any styling information is
197     * removed.  Returns null if the attribute is not defined.
198     */
199    @Override
200    public String getString(int index) {
201        if (!hasValue(index)) {
202            return null;
203        }
204        // As unfortunate as it is, it's possible to use enums with all attribute formats,
205        // not just integers/enums. So, we need to search the enums always. In case
206        // enums are used, the returned value is an integer.
207        Integer v = resolveEnumAttribute(index);
208        return v == null ? mResourceData[index].getValue() : String.valueOf((int) v);
209    }
210
211    /**
212     * Retrieve the boolean value for the attribute at <var>index</var>.
213     *
214     * @param index Index of attribute to retrieve.
215     * @param defValue Value to return if the attribute is not defined.
216     *
217     * @return Attribute boolean value, or defValue if not defined.
218     */
219    @Override
220    public boolean getBoolean(int index, boolean defValue) {
221        String s = getString(index);
222        return s == null ? defValue : XmlUtils.convertValueToBoolean(s, defValue);
223
224    }
225
226    /**
227     * Retrieve the integer value for the attribute at <var>index</var>.
228     *
229     * @param index Index of attribute to retrieve.
230     * @param defValue Value to return if the attribute is not defined.
231     *
232     * @return Attribute int value, or defValue if not defined.
233     */
234    @Override
235    public int getInt(int index, int defValue) {
236        String s = getString(index);
237        try {
238            return convertValueToInt(s, defValue);
239        } catch (NumberFormatException e) {
240            Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT,
241                    String.format("\"%1$s\" in attribute \"%2$s\" is not a valid integer",
242                            s, mNames[index]),
243                    null);
244        }
245        return defValue;
246    }
247
248    /**
249     * Retrieve the float value for the attribute at <var>index</var>.
250     *
251     * @param index Index of attribute to retrieve.
252     *
253     * @return Attribute float value, or defValue if not defined..
254     */
255    @Override
256    public float getFloat(int index, float defValue) {
257        String s = getString(index);
258        try {
259            if (s != null) {
260                    return Float.parseFloat(s);
261            }
262        } catch (NumberFormatException e) {
263            Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT,
264                    String.format("\"%1$s\" in attribute \"%2$s\" cannot be converted to float.",
265                            s, mNames[index]),
266                    null);
267        }
268        return defValue;
269    }
270
271    /**
272     * Retrieve the color value for the attribute at <var>index</var>.  If
273     * the attribute references a color resource holding a complex
274     * {@link android.content.res.ColorStateList}, then the default color from
275     * the set is returned.
276     *
277     * @param index Index of attribute to retrieve.
278     * @param defValue Value to return if the attribute is not defined or
279     *                 not a resource.
280     *
281     * @return Attribute color value, or defValue if not defined.
282     */
283    @Override
284    public int getColor(int index, int defValue) {
285        if (index < 0 || index >= mResourceData.length) {
286            return defValue;
287        }
288
289        if (mResourceData[index] == null) {
290            return defValue;
291        }
292
293        ColorStateList colorStateList = ResourceHelper.getColorStateList(
294                mResourceData[index], mContext);
295        if (colorStateList != null) {
296            return colorStateList.getDefaultColor();
297        }
298
299        return defValue;
300    }
301
302    @Override
303    public ColorStateList getColorStateList(int index) {
304        if (!hasValue(index)) {
305            return null;
306        }
307
308        return ResourceHelper.getColorStateList(mResourceData[index], mContext);
309    }
310
311    @Override
312    public ComplexColor getComplexColor(int index) {
313        if (!hasValue(index)) {
314            return null;
315        }
316
317        return ResourceHelper.getComplexColor(mResourceData[index], mContext);
318    }
319
320    /**
321     * Retrieve the integer value for the attribute at <var>index</var>.
322     *
323     * @param index Index of attribute to retrieve.
324     * @param defValue Value to return if the attribute is not defined or
325     *                 not a resource.
326     *
327     * @return Attribute integer value, or defValue if not defined.
328     */
329    @Override
330    public int getInteger(int index, int defValue) {
331        return getInt(index, defValue);
332    }
333
334    /**
335     * Retrieve a dimensional unit attribute at <var>index</var>.  Unit
336     * conversions are based on the current {@link DisplayMetrics}
337     * associated with the resources this {@link TypedArray} object
338     * came from.
339     *
340     * @param index Index of attribute to retrieve.
341     * @param defValue Value to return if the attribute is not defined or
342     *                 not a resource.
343     *
344     * @return Attribute dimension value multiplied by the appropriate
345     * metric, or defValue if not defined.
346     *
347     * @see #getDimensionPixelOffset
348     * @see #getDimensionPixelSize
349     */
350    @Override
351    public float getDimension(int index, float defValue) {
352        String s = getString(index);
353        if (s == null) {
354            return defValue;
355        }
356        // Check if the value is a magic constant that doesn't require a unit.
357        try {
358            int i = Integer.parseInt(s);
359            if (i == LayoutParams.MATCH_PARENT || i == LayoutParams.WRAP_CONTENT) {
360                return i;
361            }
362        } catch (NumberFormatException ignored) {
363            // pass
364        }
365
366        if (ResourceHelper.parseFloatAttribute(mNames[index], s, mValue, true)) {
367            return mValue.getDimension(mBridgeResources.getDisplayMetrics());
368        }
369
370        return defValue;
371    }
372
373    /**
374     * Retrieve a dimensional unit attribute at <var>index</var> for use
375     * as an offset in raw pixels.  This is the same as
376     * {@link #getDimension}, except the returned value is converted to
377     * integer pixels for you.  An offset conversion involves simply
378     * truncating the base value to an integer.
379     *
380     * @param index Index of attribute to retrieve.
381     * @param defValue Value to return if the attribute is not defined or
382     *                 not a resource.
383     *
384     * @return Attribute dimension value multiplied by the appropriate
385     * metric and truncated to integer pixels, or defValue if not defined.
386     *
387     * @see #getDimension
388     * @see #getDimensionPixelSize
389     */
390    @Override
391    public int getDimensionPixelOffset(int index, int defValue) {
392        return (int) getDimension(index, defValue);
393    }
394
395    /**
396     * Retrieve a dimensional unit attribute at <var>index</var> for use
397     * as a size in raw pixels.  This is the same as
398     * {@link #getDimension}, except the returned value is converted to
399     * integer pixels for use as a size.  A size conversion involves
400     * rounding the base value, and ensuring that a non-zero base value
401     * is at least one pixel in size.
402     *
403     * @param index Index of attribute to retrieve.
404     * @param defValue Value to return if the attribute is not defined or
405     *                 not a resource.
406     *
407     * @return Attribute dimension value multiplied by the appropriate
408     * metric and truncated to integer pixels, or defValue if not defined.
409     *
410     * @see #getDimension
411     * @see #getDimensionPixelOffset
412     */
413    @Override
414    public int getDimensionPixelSize(int index, int defValue) {
415        try {
416            return getDimension(index, null);
417        } catch (RuntimeException e) {
418            String s = getString(index);
419
420            if (s != null) {
421                // looks like we were unable to resolve the dimension value
422                Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT,
423                        String.format("\"%1$s\" in attribute \"%2$s\" is not a valid format.",
424                                s, mNames[index]), null);
425            }
426
427            return defValue;
428        }
429    }
430
431    /**
432     * Special version of {@link #getDimensionPixelSize} for retrieving
433     * {@link android.view.ViewGroup}'s layout_width and layout_height
434     * attributes.  This is only here for performance reasons; applications
435     * should use {@link #getDimensionPixelSize}.
436     *
437     * @param index Index of the attribute to retrieve.
438     * @param name Textual name of attribute for error reporting.
439     *
440     * @return Attribute dimension value multiplied by the appropriate
441     * metric and truncated to integer pixels.
442     */
443    @Override
444    public int getLayoutDimension(int index, String name) {
445        try {
446            // this will throw an exception if not found.
447            return getDimension(index, name);
448        } catch (RuntimeException e) {
449
450            if (LayoutInflater_Delegate.sIsInInclude) {
451                throw new RuntimeException("Layout Dimension '" + name + "' not found.");
452            }
453
454            Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT,
455                    "You must supply a " + name + " attribute.", null);
456
457            return 0;
458        }
459    }
460
461    @Override
462    public int getLayoutDimension(int index, int defValue) {
463        return getDimensionPixelSize(index, defValue);
464    }
465
466    /** @param name attribute name, used for error reporting. */
467    private int getDimension(int index, @Nullable String name) {
468        String s = getString(index);
469        if (s == null) {
470            if (name != null) {
471                throw new RuntimeException("Attribute '" + name + "' not found");
472            }
473            throw new RuntimeException();
474        }
475        // Check if the value is a magic constant that doesn't require a unit.
476        try {
477            int i = Integer.parseInt(s);
478            if (i == LayoutParams.MATCH_PARENT || i == LayoutParams.WRAP_CONTENT) {
479                return i;
480            }
481        } catch (NumberFormatException ignored) {
482            // pass
483        }
484        if (ResourceHelper.parseFloatAttribute(mNames[index], s, mValue, true)) {
485            float f = mValue.getDimension(mBridgeResources.getDisplayMetrics());
486
487            final int res = (int)(f+0.5f);
488            if (res != 0) return res;
489            if (f == 0) return 0;
490            if (f > 0) return 1;
491        }
492
493        throw new RuntimeException();
494    }
495
496    /**
497     * Retrieve a fractional unit attribute at <var>index</var>.
498     *
499     * @param index Index of attribute to retrieve.
500     * @param base The base value of this fraction.  In other words, a
501     *             standard fraction is multiplied by this value.
502     * @param pbase The parent base value of this fraction.  In other
503     *             words, a parent fraction (nn%p) is multiplied by this
504     *             value.
505     * @param defValue Value to return if the attribute is not defined or
506     *                 not a resource.
507     *
508     * @return Attribute fractional value multiplied by the appropriate
509     * base value, or defValue if not defined.
510     */
511    @Override
512    public float getFraction(int index, int base, int pbase, float defValue) {
513        String value = getString(index);
514        if (value == null) {
515            return defValue;
516        }
517
518        if (ResourceHelper.parseFloatAttribute(mNames[index], value, mValue, false)) {
519            return mValue.getFraction(base, pbase);
520        }
521
522        // looks like we were unable to resolve the fraction value
523        Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT,
524                String.format(
525                        "\"%1$s\" in attribute \"%2$s\" cannot be converted to a fraction.",
526                        value, mNames[index]), null);
527
528        return defValue;
529    }
530
531    /**
532     * Retrieve the resource identifier for the attribute at
533     * <var>index</var>.  Note that attribute resource as resolved when
534     * the overall {@link TypedArray} object is retrieved.  As a
535     * result, this function will return the resource identifier of the
536     * final resource value that was found, <em>not</em> necessarily the
537     * original resource that was specified by the attribute.
538     *
539     * @param index Index of attribute to retrieve.
540     * @param defValue Value to return if the attribute is not defined or
541     *                 not a resource.
542     *
543     * @return Attribute resource identifier, or defValue if not defined.
544     */
545    @Override
546    public int getResourceId(int index, int defValue) {
547        if (index < 0 || index >= mResourceData.length) {
548            return defValue;
549        }
550
551        // get the Resource for this index
552        ResourceValue resValue = mResourceData[index];
553
554        // no data, return the default value.
555        if (resValue == null) {
556            return defValue;
557        }
558
559        // check if this is a style resource
560        if (resValue instanceof StyleResourceValue) {
561            // get the id that will represent this style.
562            return mContext.getDynamicIdByStyle((StyleResourceValue)resValue);
563        }
564
565        // if the attribute was a reference to a resource, and not a declaration of an id (@+id),
566        // then the xml attribute value was "resolved" which leads us to a ResourceValue with a
567        // valid getType() and getName() returning a resource name.
568        // (and getValue() returning null!). We need to handle this!
569        if (resValue.getResourceType() != null) {
570            // if this is a framework id
571            if (mPlatformFile || resValue.isFramework()) {
572                // look for idName in the android R classes
573                return mContext.getFrameworkResourceValue(
574                        resValue.getResourceType(), resValue.getName(), defValue);
575            }
576
577            // look for idName in the project R class.
578            return mContext.getProjectResourceValue(
579                    resValue.getResourceType(), resValue.getName(), defValue);
580        }
581
582        // else, try to get the value, and resolve it somehow.
583        String value = resValue.getValue();
584        if (value == null) {
585            return defValue;
586        }
587
588        // if the value is just an integer, return it.
589        try {
590            int i = Integer.parseInt(value);
591            if (Integer.toString(i).equals(value)) {
592                return i;
593            }
594        } catch (NumberFormatException e) {
595            // pass
596        }
597
598        // Handle the @id/<name>, @+id/<name> and @android:id/<name>
599        // We need to return the exact value that was compiled (from the various R classes),
600        // as these values can be reused internally with calls to findViewById().
601        // There's a trick with platform layouts that not use "android:" but their IDs are in
602        // fact in the android.R and com.android.internal.R classes.
603        // The field mPlatformFile will indicate that all IDs are to be looked up in the android R
604        // classes exclusively.
605
606        // if this is a reference to an id, find it.
607        if (value.startsWith("@id/") || value.startsWith("@+") ||
608                value.startsWith("@android:id/")) {
609
610            int pos = value.indexOf('/');
611            String idName = value.substring(pos + 1);
612            boolean create = value.startsWith("@+");
613            boolean isFrameworkId =
614                    mPlatformFile || value.startsWith("@android") || value.startsWith("@+android");
615
616            // Look for the idName in project or android R class depending on isPlatform.
617            if (create) {
618                Integer idValue;
619                if (isFrameworkId) {
620                    idValue = Bridge.getResourceId(ResourceType.ID, idName);
621                } else {
622                    idValue = mContext.getLayoutlibCallback().getResourceId(ResourceType.ID, idName);
623                }
624                return idValue == null ? defValue : idValue;
625            }
626            // This calls the same method as in if(create), but doesn't create a dynamic id, if
627            // one is not found.
628            if (isFrameworkId) {
629                return mContext.getFrameworkResourceValue(ResourceType.ID, idName, defValue);
630            } else {
631                return mContext.getProjectResourceValue(ResourceType.ID, idName, defValue);
632            }
633        }
634
635        // not a direct id valid reference? resolve it
636        Integer idValue;
637
638        if (resValue.isFramework()) {
639            idValue = Bridge.getResourceId(resValue.getResourceType(),
640                    resValue.getName());
641        } else {
642            idValue = mContext.getLayoutlibCallback().getResourceId(
643                    resValue.getResourceType(), resValue.getName());
644        }
645
646        if (idValue != null) {
647            return idValue;
648        }
649
650        Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_RESOLVE,
651                String.format(
652                    "Unable to resolve id \"%1$s\" for attribute \"%2$s\"", value, mNames[index]),
653                    resValue);
654
655        return defValue;
656    }
657
658    @Override
659    public int getThemeAttributeId(int index, int defValue) {
660        // TODO: Get the right Theme Attribute ID to enable caching of the drawables.
661        return defValue;
662    }
663
664    /**
665     * Retrieve the Drawable for the attribute at <var>index</var>.  This
666     * gets the resource ID of the selected attribute, and uses
667     * {@link Resources#getDrawable Resources.getDrawable} of the owning
668     * Resources object to retrieve its Drawable.
669     *
670     * @param index Index of attribute to retrieve.
671     *
672     * @return Drawable for the attribute, or null if not defined.
673     */
674    @Override
675    public Drawable getDrawable(int index) {
676        if (!hasValue(index)) {
677            return null;
678        }
679
680        ResourceValue value = mResourceData[index];
681        return ResourceHelper.getDrawable(value, mContext, mTheme);
682    }
683
684
685    /**
686     * Retrieve the CharSequence[] for the attribute at <var>index</var>.
687     * This gets the resource ID of the selected attribute, and uses
688     * {@link Resources#getTextArray Resources.getTextArray} of the owning
689     * Resources object to retrieve its String[].
690     *
691     * @param index Index of attribute to retrieve.
692     *
693     * @return CharSequence[] for the attribute, or null if not defined.
694     */
695    @Override
696    public CharSequence[] getTextArray(int index) {
697        if (!hasValue(index)) {
698            return null;
699        }
700        ResourceValue resVal = mResourceData[index];
701        if (resVal instanceof ArrayResourceValue) {
702            ArrayResourceValue array = (ArrayResourceValue) resVal;
703            int count = array.getElementCount();
704            return count >= 0 ? Resources_Delegate.fillValues(mBridgeResources, array, new CharSequence[count]) :
705                    null;
706        }
707        int id = getResourceId(index, 0);
708        String resIdMessage = id > 0 ? " (resource id 0x" + Integer.toHexString(id) + ')' : "";
709        throw new NotFoundException(
710                String.format("%1$s in %2$s%3$s is not a valid array resource.",
711                        resVal.getValue(), mNames[index], resIdMessage));
712    }
713
714    @Override
715    public int[] extractThemeAttrs() {
716        // The drawables are always inflated with a Theme and we don't care about caching. So,
717        // just return.
718        return null;
719    }
720
721    @Override
722    public int getChangingConfigurations() {
723        // We don't care about caching. Any change in configuration is a fresh render. So,
724        // just return.
725        return 0;
726    }
727
728    /**
729     * Retrieve the raw TypedValue for the attribute at <var>index</var>.
730     *
731     * @param index Index of attribute to retrieve.
732     * @param outValue TypedValue object in which to place the attribute's
733     *                 data.
734     *
735     * @return Returns true if the value was retrieved, else false.
736     */
737    @Override
738    public boolean getValue(int index, TypedValue outValue) {
739        String s = getString(index);
740        return s != null && ResourceHelper.parseFloatAttribute(mNames[index], s, outValue, false);
741    }
742
743    @Override
744    @SuppressWarnings("ResultOfMethodCallIgnored")
745    public int getType(int index) {
746        String value = getString(index);
747        if (value == null) {
748            return TYPE_NULL;
749        }
750        if (value.startsWith(PREFIX_RESOURCE_REF)) {
751            return TYPE_REFERENCE;
752        }
753        if (value.startsWith(PREFIX_THEME_REF)) {
754            return TYPE_ATTRIBUTE;
755        }
756        try {
757            // Don't care about the value. Only called to check if an exception is thrown.
758            convertValueToInt(value, 0);
759            if (value.startsWith("0x") || value.startsWith("0X")) {
760                return TYPE_INT_HEX;
761            }
762            // is it a color?
763            if (value.startsWith("#")) {
764                int length = value.length() - 1;
765                if (length == 3) {  // rgb
766                    return TYPE_INT_COLOR_RGB4;
767                }
768                if (length == 4) {  // argb
769                    return TYPE_INT_COLOR_ARGB4;
770                }
771                if (length == 6) {  // rrggbb
772                    return TYPE_INT_COLOR_RGB8;
773                }
774                if (length == 8) {  // aarrggbb
775                    return TYPE_INT_COLOR_ARGB8;
776                }
777            }
778            if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) {
779                return TYPE_INT_BOOLEAN;
780            }
781            return TYPE_INT_DEC;
782        } catch (NumberFormatException ignored) {
783            try {
784                Float.parseFloat(value);
785                return TYPE_FLOAT;
786            } catch (NumberFormatException ignore) {
787            }
788            // Might be a dimension.
789            if (ResourceHelper.parseFloatAttribute(null, value, new TypedValue(), false)) {
790                return TYPE_DIMENSION;
791            }
792        }
793        // TODO: handle fractions.
794        return TYPE_STRING;
795    }
796
797    /**
798     * Determines whether there is an attribute at <var>index</var>.
799     *
800     * @param index Index of attribute to retrieve.
801     *
802     * @return True if the attribute has a value, false otherwise.
803     */
804    @Override
805    public boolean hasValue(int index) {
806        return index >= 0 && index < mResourceData.length && mResourceData[index] != null;
807    }
808
809    @Override
810    public boolean hasValueOrEmpty(int index) {
811        return hasValue(index) || index >= 0 && index < mResourceData.length &&
812                mEmptyIds != null && Arrays.binarySearch(mEmptyIds, index) >= 0;
813    }
814
815    /**
816     * Retrieve the raw TypedValue for the attribute at <var>index</var>
817     * and return a temporary object holding its data.  This object is only
818     * valid until the next call on to {@link TypedArray}.
819     *
820     * @param index Index of attribute to retrieve.
821     *
822     * @return Returns a TypedValue object if the attribute is defined,
823     *         containing its data; otherwise returns null.  (You will not
824     *         receive a TypedValue whose type is TYPE_NULL.)
825     */
826    @Override
827    public TypedValue peekValue(int index) {
828        if (index < 0 || index >= mResourceData.length) {
829            return null;
830        }
831
832        if (getValue(index, mValue)) {
833            return mValue;
834        }
835
836        return null;
837    }
838
839    /**
840     * Returns a message about the parser state suitable for printing error messages.
841     */
842    @Override
843    public String getPositionDescription() {
844        return "<internal -- stub if needed>";
845    }
846
847    /**
848     * Give back a previously retrieved TypedArray, for later re-use.
849     */
850    @Override
851    public void recycle() {
852        // pass
853    }
854
855    @Override
856    public String toString() {
857        return Arrays.toString(mResourceData);
858    }
859
860    /**
861     * Searches for the string in the attributes (flag or enums) and returns the integer.
862     * If found, it will return an integer matching the value.
863     *
864     * @param index Index of attribute to retrieve.
865     *
866     * @return Attribute int value, or null if not defined.
867     */
868    private Integer resolveEnumAttribute(int index) {
869        // Get the map of attribute-constant -> IntegerValue
870        Map<String, Integer> map = null;
871        if (mIsFramework[index]) {
872            map = Bridge.getEnumValues(mNames[index]);
873        } else {
874            // get the styleable matching the resolved name
875            RenderResources res = mContext.getRenderResources();
876            ResourceValue attr = res.getProjectResource(ResourceType.ATTR, mNames[index]);
877            if (attr instanceof AttrResourceValue) {
878                map = ((AttrResourceValue) attr).getAttributeValues();
879            }
880        }
881
882        if (map != null) {
883            // accumulator to store the value of the 1+ constants.
884            int result = 0;
885            boolean found = false;
886
887            // split the value in case this is a mix of several flags.
888            String[] keywords = mResourceData[index].getValue().split("\\|");
889            for (String keyword : keywords) {
890                Integer i = map.get(keyword.trim());
891                if (i != null) {
892                    result |= i;
893                    found = true;
894                }
895                // TODO: We should act smartly and log a warning for incorrect keywords. However,
896                // this method is currently called even if the resourceValue is not an enum.
897            }
898            if (found) {
899                return result;
900            }
901        }
902
903        return null;
904    }
905
906    /**
907     * Copied from {@link XmlUtils#convertValueToInt(CharSequence, int)}, but adapted to account
908     * for aapt, and the fact that host Java VM's Integer.parseInt("XXXXXXXX", 16) cannot handle
909     * "XXXXXXXX" > 80000000.
910     */
911    private static int convertValueToInt(@Nullable String charSeq, int defValue) {
912        if (null == charSeq || charSeq.isEmpty())
913            return defValue;
914
915        int sign = 1;
916        int index = 0;
917        int len = charSeq.length();
918        int base = 10;
919
920        if ('-' == charSeq.charAt(0)) {
921            sign = -1;
922            index++;
923        }
924
925        if ('0' == charSeq.charAt(index)) {
926            //  Quick check for a zero by itself
927            if (index == (len - 1))
928                return 0;
929
930            char c = charSeq.charAt(index + 1);
931
932            if ('x' == c || 'X' == c) {
933                index += 2;
934                base = 16;
935            } else {
936                index++;
937                // Leave the base as 10. aapt removes the preceding zero, and thus when framework
938                // sees the value, it only gets the decimal value.
939            }
940        } else if ('#' == charSeq.charAt(index)) {
941            return ResourceHelper.getColor(charSeq) * sign;
942        } else if ("true".equals(charSeq) || "TRUE".equals(charSeq)) {
943            return -1;
944        } else if ("false".equals(charSeq) || "FALSE".equals(charSeq)) {
945            return 0;
946        }
947
948        // Use Long, since we want to handle hex ints > 80000000.
949        return ((int)Long.parseLong(charSeq.substring(index), base)) * sign;
950    }
951
952    static TypedArray obtain(Resources res, int len) {
953        return new BridgeTypedArray(res, null, len, true);
954    }
955}
956