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