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