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