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