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