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