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