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