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