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