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