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