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