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