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