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