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