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 
17 package android.content.res;
18 
19 import com.android.ide.common.rendering.api.AttrResourceValue;
20 import com.android.ide.common.rendering.api.LayoutLog;
21 import com.android.ide.common.rendering.api.RenderResources;
22 import com.android.ide.common.rendering.api.ResourceValue;
23 import com.android.ide.common.rendering.api.StyleResourceValue;
24 import com.android.internal.util.XmlUtils;
25 import com.android.layoutlib.bridge.Bridge;
26 import com.android.layoutlib.bridge.android.BridgeContext;
27 import com.android.layoutlib.bridge.android.BridgeXmlBlockParser;
28 import com.android.layoutlib.bridge.impl.ParserFactory;
29 import com.android.layoutlib.bridge.impl.ResourceHelper;
30 import com.android.resources.ResourceType;
31 
32 import org.xmlpull.v1.XmlPullParser;
33 import org.xmlpull.v1.XmlPullParserException;
34 
35 import android.content.res.Resources.Theme;
36 import android.graphics.drawable.Drawable;
37 import android.util.DisplayMetrics;
38 import android.util.TypedValue;
39 import android.view.LayoutInflater_Delegate;
40 import android.view.ViewGroup.LayoutParams;
41 
42 import java.io.File;
43 import java.util.Arrays;
44 import java.util.Map;
45 
46 /**
47  * Custom implementation of TypedArray to handle non compiled resources.
48  */
49 public 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 
BridgeTypedArray(BridgeResources resources, BridgeContext context, int len, boolean platformFile)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      */
bridgeSetValue(int index, String name, boolean isFramework, ResourceValue value)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      */
sealArray()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      */
setTheme(Theme theme)122     public void setTheme(Theme theme) {
123         mTheme = theme;
124     }
125 
126     /**
127      * Return the number of values in this array.
128      */
129     @Override
length()130     public int length() {
131         return mResourceData.length;
132     }
133 
134     /**
135      * Return the Resources object this array was loaded from.
136      */
137     @Override
getResources()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
getText(int index)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
getString(int index)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
getBoolean(int index, boolean defValue)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
getInt(int index, int defValue)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
getFloat(int index, float defValue)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
getColor(int index, int defValue)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
getColorStateList(int index)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
getInteger(int index, int defValue)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
getDimension(int index, float defValue)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
getDimensionPixelOffset(int index, int defValue)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
getDimensionPixelSize(int index, int defValue)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
getLayoutDimension(int index, String name)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
getLayoutDimension(int index, int defValue)471     public int getLayoutDimension(int index, int defValue) {
472         return getDimensionPixelSize(index, defValue);
473     }
474 
getDimension(int index)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
getFraction(int index, int base, int pbase, float defValue)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
getResourceId(int index, int defValue)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
getThemeAttributeId(int index, int defValue)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
getDrawable(int index)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
getTextArray(int index)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
extractThemeAttrs()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
getChangingConfigurations()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
getValue(int index, TypedValue outValue)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
hasValue(int index)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
peekValue(int index)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
getPositionDescription()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
recycle()787     public void recycle() {
788         // pass
789     }
790 
791     @Override
toString()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      */
resolveEnumAttribute(int index)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 
obtain(Resources res, int len)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