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