content: /*
   * Copyright (C) 2008 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package android.content.res;
  
  import com.android.ide.common.rendering.api.ArrayResourceValue;
  import com.android.ide.common.rendering.api.AttrResourceValue;
  import com.android.ide.common.rendering.api.LayoutLog;
  import com.android.ide.common.rendering.api.RenderResources;
  import com.android.ide.common.rendering.api.ResourceNamespace;
  import com.android.ide.common.rendering.api.ResourceNamespace.Resolver;
  import com.android.ide.common.rendering.api.ResourceReference;
  import com.android.ide.common.rendering.api.ResourceValue;
  import com.android.ide.common.rendering.api.StyleResourceValue;
  import com.android.internal.util.XmlUtils;
  import com.android.layoutlib.bridge.Bridge;
  import com.android.layoutlib.bridge.android.BridgeContext;
  import com.android.layoutlib.bridge.android.UnresolvedResourceValue;
  import com.android.layoutlib.bridge.impl.ResourceHelper;
  import com.android.resources.ResourceType;
  import com.android.resources.ResourceUrl;
  
  import android.annotation.Nullable;
  import android.content.res.Resources.Theme;
  import android.graphics.Typeface;
  import android.graphics.Typeface_Accessor;
  import android.graphics.drawable.Drawable;
  import android.util.DisplayMetrics;
  import android.util.TypedValue;
  import android.view.LayoutInflater_Delegate;
  import android.view.ViewGroup.LayoutParams;
  
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Map;
  
  import static android.util.TypedValue.TYPE_ATTRIBUTE;
  import static android.util.TypedValue.TYPE_DIMENSION;
  import static android.util.TypedValue.TYPE_FLOAT;
  import static android.util.TypedValue.TYPE_INT_BOOLEAN;
  import static android.util.TypedValue.TYPE_INT_COLOR_ARGB4;
  import static android.util.TypedValue.TYPE_INT_COLOR_ARGB8;
  import static android.util.TypedValue.TYPE_INT_COLOR_RGB4;
  import static android.util.TypedValue.TYPE_INT_COLOR_RGB8;
  import static android.util.TypedValue.TYPE_INT_DEC;
  import static android.util.TypedValue.TYPE_INT_HEX;
  import static android.util.TypedValue.TYPE_NULL;
  import static android.util.TypedValue.TYPE_REFERENCE;
  import static android.util.TypedValue.TYPE_STRING;
  import static com.android.SdkConstants.PREFIX_RESOURCE_REF;
  import static com.android.SdkConstants.PREFIX_THEME_REF;
  import static com.android.ide.common.rendering.api.RenderResources.REFERENCE_EMPTY;
  import static com.android.ide.common.rendering.api.RenderResources.REFERENCE_NULL;
  import static com.android.ide.common.rendering.api.RenderResources.REFERENCE_UNDEFINED;
  
  /**
   * Custom implementation of TypedArray to handle non compiled resources.
   */
  public final class BridgeTypedArray extends TypedArray {
  
      private final Resources mBridgeResources;
      private final BridgeContext mContext;
  
      private final int[] mResourceId;
      private final ResourceValue[] mResourceData;
      private final String[] mNames;
      private final ResourceNamespace[] mNamespaces;
  
      // Contains ids that are @empty. We still store null in mResourceData for that index, since we
      // want to save on the check against empty, each time a resource value is requested.
      @Nullable
      private int[] mEmptyIds;
  
      public BridgeTypedArray(Resources resources, BridgeContext context, int len) {
          super(resources);
          mBridgeResources = resources;
          mContext = context;
          mResourceId = new int[len];
          mResourceData = new ResourceValue[len];
          mNames = new String[len];
          mNamespaces = new ResourceNamespace[len];
      }
  
      /**
       * A bridge-specific method that sets a value in the type array
       * @param index the index of the value in the TypedArray
       * @param name the name of the attribute
       * @param namespace namespace of the attribute
       * @param resourceId the reference id of this resource
       * @param value the value of the attribute
       */
      public void bridgeSetValue(int index, String name, ResourceNamespace namespace, int resourceId,
              ResourceValue value) {
          mResourceId[index] = resourceId;
          mResourceData[index] = value;
          mNames[index] = name;
          mNamespaces[index] = namespace;
      }
  
      /**
       * Seals the array after all calls to
       * {@link #bridgeSetValue(int, String, ResourceNamespace, int, ResourceValue)} have been done.
       * <p/>This allows to compute the list of non default values, permitting
       * {@link #getIndexCount()} to return the proper value.
       */
      public void sealArray() {
          // fills TypedArray.mIndices which is used to implement getIndexCount/getIndexAt
          // first count the array size
          int count = 0;
          ArrayList<Integer> emptyIds = null;
          for (int i = 0; i < mResourceData.length; i++) {
              ResourceValue data = mResourceData[i];
              if (data != null) {
                  String dataValue = data.getValue();
                  if (REFERENCE_NULL.equals(dataValue) || REFERENCE_UNDEFINED.equals(dataValue)) {
                      mResourceData[i] = null;
                  } else if (REFERENCE_EMPTY.equals(dataValue)) {
                      mResourceData[i] = null;
                      if (emptyIds == null) {
                          emptyIds = new ArrayList<>(4);
                      }
                      emptyIds.add(i);
                  } else {
                      count++;
                  }
              }
          }
  
          if (emptyIds != null) {
              mEmptyIds = new int[emptyIds.size()];
              for (int i = 0; i < emptyIds.size(); i++) {
                  mEmptyIds[i] = emptyIds.get(i);
              }
          }
  
          // allocate the table with an extra to store the size
          mIndices = new int[count+1];
          mIndices[0] = count;
  
          // fill the array with the indices.
          int index = 1;
          for (int i = 0 ; i < mResourceData.length ; i++) {
              if (mResourceData[i] != null) {
                  mIndices[index++] = i;
              }
          }
      }
  
      /**
       * Set the theme to be used for inflating drawables.
       */
      public void setTheme(Theme theme) {
          mTheme = theme;
      }
  
      /**
       * Return the number of values in this array.
       */
      @Override
      public int length() {
          return mResourceData.length;
      }
  
      /**
       * Return the Resources object this array was loaded from.
       */
      @Override
      public Resources getResources() {
          return mBridgeResources;
      }
  
      /**
       * Retrieve the styled string value for the attribute at <var>index</var>.
       *
       * @param index Index of attribute to retrieve.
       *
       * @return CharSequence holding string data.  May be styled.  Returns
       *         null if the attribute is not defined.
       */
      @Override
      public CharSequence getText(int index) {
          // FIXME: handle styled strings!
          return getString(index);
      }
  
      /**
       * Retrieve the string value for the attribute at <var>index</var>.
       *
       * @param index Index of attribute to retrieve.
       *
       * @return String holding string data.  Any styling information is
       * removed.  Returns null if the attribute is not defined.
       */
      @Override
      public String getString(int index) {
          if (!hasValue(index)) {
              return null;
          }
          // As unfortunate as it is, it's possible to use enums with all attribute formats,
          // not just integers/enums. So, we need to search the enums always. In case
          // enums are used, the returned value is an integer.
          Integer v = resolveEnumAttribute(index);
          return v == null ? mResourceData[index].getValue() : String.valueOf((int) v);
      }
  
      /**
       * Retrieve the boolean value for the attribute at <var>index</var>.
       *
       * @param index Index of attribute to retrieve.
       * @param defValue Value to return if the attribute is not defined.
       *
       * @return Attribute boolean value, or defValue if not defined.
       */
      @Override
      public boolean getBoolean(int index, boolean defValue) {
          String s = getString(index);
          return s == null ? defValue : XmlUtils.convertValueToBoolean(s, defValue);
  
      }
  
      /**
       * Retrieve the integer value for the attribute at <var>index</var>.
       *
       * @param index Index of attribute to retrieve.
       * @param defValue Value to return if the attribute is not defined.
       *
       * @return Attribute int value, or defValue if not defined.
       */
      @Override
      public int getInt(int index, int defValue) {
          String s = getString(index);
          try {
              return convertValueToInt(s, defValue);
          } catch (NumberFormatException e) {
              Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT,
                      String.format("\"%1$s\" in attribute \"%2$s\" is not a valid integer",
                              s, mNames[index]),
                      null);
          }
          return defValue;
      }
  
      /**
       * Retrieve the float value for the attribute at <var>index</var>.
       *
       * @param index Index of attribute to retrieve.
       *
       * @return Attribute float value, or defValue if not defined..
       */
      @Override
      public float getFloat(int index, float defValue) {
          String s = getString(index);
          try {
              if (s != null) {
                      return Float.parseFloat(s);
              }
          } catch (NumberFormatException e) {
              Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT,
                      String.format("\"%1$s\" in attribute \"%2$s\" cannot be converted to float.",
                              s, mNames[index]),
                      null);
          }
          return defValue;
      }
  
      /**
       * Retrieve the color value for the attribute at <var>index</var>.  If
       * the attribute references a color resource holding a complex
       * {@link android.content.res.ColorStateList}, then the default color from
       * the set is returned.
       *
       * @param index Index of attribute to retrieve.
       * @param defValue Value to return if the attribute is not defined or
       *                 not a resource.
       *
       * @return Attribute color value, or defValue if not defined.
       */
      @Override
      public int getColor(int index, int defValue) {
          if (index < 0 || index >= mResourceData.length) {
              return defValue;
          }
  
          if (mResourceData[index] == null) {
              return defValue;
          }
  
          ColorStateList colorStateList = ResourceHelper.getColorStateList(
                  mResourceData[index], mContext, mTheme);
          if (colorStateList != null) {
              return colorStateList.getDefaultColor();
          }
  
          return defValue;
      }
  
      @Override
      public ColorStateList getColorStateList(int index) {
          if (!hasValue(index)) {
              return null;
          }
  
          return ResourceHelper.getColorStateList(mResourceData[index], mContext, mTheme);
      }
  
      @Override
      public ComplexColor getComplexColor(int index) {
          if (!hasValue(index)) {
              return null;
          }
  
          return ResourceHelper.getComplexColor(mResourceData[index], mContext, mTheme);
      }
  
      /**
       * Retrieve the integer value for the attribute at <var>index</var>.
       *
       * @param index Index of attribute to retrieve.
       * @param defValue Value to return if the attribute is not defined or
       *                 not a resource.
       *
       * @return Attribute integer value, or defValue if not defined.
       */
      @Override
      public int getInteger(int index, int defValue) {
          return getInt(index, defValue);
      }
  
      /**
       * Retrieve a dimensional unit attribute at <var>index</var>.  Unit
       * conversions are based on the current {@link DisplayMetrics}
       * associated with the resources this {@link TypedArray} object
       * came from.
       *
       * @param index Index of attribute to retrieve.
       * @param defValue Value to return if the attribute is not defined or
       *                 not a resource.
       *
       * @return Attribute dimension value multiplied by the appropriate
       * metric, or defValue if not defined.
       *
       * @see #getDimensionPixelOffset
       * @see #getDimensionPixelSize
       */
      @Override
      public float getDimension(int index, float defValue) {
          String s = getString(index);
          if (s == null) {
              return defValue;
          }
          // Check if the value is a magic constant that doesn't require a unit.
          try {
              int i = Integer.parseInt(s);
              if (i == LayoutParams.MATCH_PARENT || i == LayoutParams.WRAP_CONTENT) {
                  return i;
              }
          } catch (NumberFormatException ignored) {
              // pass
          }
  
          if (ResourceHelper.parseFloatAttribute(mNames[index], s, mValue, true)) {
              return mValue.getDimension(mBridgeResources.getDisplayMetrics());
          }
  
          return defValue;
      }
  
      /**
       * Retrieve a dimensional unit attribute at <var>index</var> for use
       * as an offset in raw pixels.  This is the same as
       * {@link #getDimension}, except the returned value is converted to
       * integer pixels for you.  An offset conversion involves simply
       * truncating the base value to an integer.
       *
       * @param index Index of attribute to retrieve.
       * @param defValue Value to return if the attribute is not defined or
       *                 not a resource.
       *
       * @return Attribute dimension value multiplied by the appropriate
       * metric and truncated to integer pixels, or defValue if not defined.
       *
       * @see #getDimension
       * @see #getDimensionPixelSize
       */
      @Override
      public int getDimensionPixelOffset(int index, int defValue) {
          return (int) getDimension(index, defValue);
      }
  
      /**
       * Retrieve a dimensional unit attribute at <var>index</var> for use
       * as a size in raw pixels.  This is the same as
       * {@link #getDimension}, except the returned value is converted to
       * integer pixels for use as a size.  A size conversion involves
       * rounding the base value, and ensuring that a non-zero base value
       * is at least one pixel in size.
       *
       * @param index Index of attribute to retrieve.
       * @param defValue Value to return if the attribute is not defined or
       *                 not a resource.
       *
       * @return Attribute dimension value multiplied by the appropriate
       * metric and truncated to integer pixels, or defValue if not defined.
       *
       * @see #getDimension
       * @see #getDimensionPixelOffset
       */
      @Override
      public int getDimensionPixelSize(int index, int defValue) {
          try {
              return getDimension(index, null);
          } catch (RuntimeException e) {
              String s = getString(index);
  
              if (s != null) {
                  // looks like we were unable to resolve the dimension value
                  Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT,
                          String.format("\"%1$s\" in attribute \"%2$s\" is not a valid format.",
                                  s, mNames[index]), null);
              }
  
              return defValue;
          }
      }
  
      /**
       * Special version of {@link #getDimensionPixelSize} for retrieving
       * {@link android.view.ViewGroup}'s layout_width and layout_height
       * attributes.  This is only here for performance reasons; applications
       * should use {@link #getDimensionPixelSize}.
       *
       * @param index Index of the attribute to retrieve.
       * @param name Textual name of attribute for error reporting.
       *
       * @return Attribute dimension value multiplied by the appropriate
       * metric and truncated to integer pixels.
       */
      @Override
      public int getLayoutDimension(int index, String name) {
          try {
              // this will throw an exception if not found.
              return getDimension(index, name);
          } catch (RuntimeException e) {
  
              if (LayoutInflater_Delegate.sIsInInclude) {
                  throw new RuntimeException("Layout Dimension '" + name + "' not found.");
              }
  
              Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT,
                      "You must supply a " + name + " attribute.", null);
  
              return 0;
          }
      }
  
      @Override
      public int getLayoutDimension(int index, int defValue) {
          return getDimensionPixelSize(index, defValue);
      }
  
      /** @param name attribute name, used for error reporting. */
      private int getDimension(int index, @Nullable String name) {
          String s = getString(index);
          if (s == null) {
              if (name != null) {
                  throw new RuntimeException("Attribute '" + name + "' not found");
              }
              throw new RuntimeException();
          }
          // Check if the value is a magic constant that doesn't require a unit.
          try {
              int i = Integer.parseInt(s);
              if (i == LayoutParams.MATCH_PARENT || i == LayoutParams.WRAP_CONTENT) {
                  return i;
              }
          } catch (NumberFormatException ignored) {
              // pass
          }
          if (ResourceHelper.parseFloatAttribute(mNames[index], s, mValue, true)) {
              float f = mValue.getDimension(mBridgeResources.getDisplayMetrics());
  
              final int res = (int)(f+0.5f);
              if (res != 0) return res;
              if (f == 0) return 0;
              if (f > 0) return 1;
          }
  
          throw new RuntimeException();
      }
  
      /**
       * Retrieve a fractional unit attribute at <var>index</var>.
       *
       * @param index Index of attribute to retrieve.
       * @param base The base value of this fraction.  In other words, a
       *             standard fraction is multiplied by this value.
       * @param pbase The parent base value of this fraction.  In other
       *             words, a parent fraction (nn%p) is multiplied by this
       *             value.
       * @param defValue Value to return if the attribute is not defined or
       *                 not a resource.
       *
       * @return Attribute fractional value multiplied by the appropriate
       * base value, or defValue if not defined.
       */
      @Override
      public float getFraction(int index, int base, int pbase, float defValue) {
          String value = getString(index);
          if (value == null) {
              return defValue;
          }
  
          if (ResourceHelper.parseFloatAttribute(mNames[index], value, mValue, false)) {
              return mValue.getFraction(base, pbase);
          }
  
          // looks like we were unable to resolve the fraction value
          Bridge.getLog().warning(LayoutLog.TAG_RESOURCES_FORMAT,
                  String.format(
                          "\"%1$s\" in attribute \"%2$s\" cannot be converted to a fraction.",
                          value, mNames[index]), null);
  
          return defValue;
      }
  
      /**
       * Retrieve the resource identifier for the attribute at
       * <var>index</var>.  Note that attribute resource as resolved when
       * the overall {@link TypedArray} object is retrieved.  As a
       * result, this function will return the resource identifier of the
       * final resource value that was found, <em>not</em> necessarily the
       * original resource that was specified by the attribute.
       *
       * @param index Index of attribute to retrieve.
       * @param defValue Value to return if the attribute is not defined or
       *                 not a resource.
       *
       * @return Attribute resource identifier, or defValue if not defined.
       */
      @Override
      public int getResourceId(int index, int defValue) {
          if (index < 0 || index >= mResourceData.length) {
              return defValue;
          }
  
          // get the Resource for this index
          ResourceValue resValue = mResourceData[index];
  
          // no data, return the default value.
          if (resValue == null) {
              return defValue;
          }
  
          // check if this is a style resource
          if (resValue instanceof StyleResourceValue) {
              // get the id that will represent this style.
              return mContext.getDynamicIdByStyle((StyleResourceValue)resValue);
          }
  
          // If the attribute was a reference to a resource, and not a declaration of an id (@+id),
          // then the xml attribute value was "resolved" which leads us to a ResourceValue with a
          // valid type, name, namespace and a potentially null value.
          if (!(resValue instanceof UnresolvedResourceValue)) {
              return mContext.getResourceId(resValue.asReference(), defValue);
          }
  
          // else, try to get the value, and resolve it somehow.
          String value = resValue.getValue();
          if (value == null) {
              return defValue;
          }
          value = value.trim();
  
  
          // `resValue` failed to be resolved. We extract the interesting bits and get rid of this
          // broken object. The namespace and resolver come from where the XML attribute was defined.
          ResourceNamespace contextNamespace = resValue.getNamespace();
          Resolver namespaceResolver = resValue.getNamespaceResolver();
  
          if (value.startsWith("#")) {
              // this looks like a color, do not try to parse it
              return defValue;
          }
  
          if (Typeface_Accessor.isSystemFont(value)) {
              // A system font family value, do not try to parse
              return defValue;
          }
  
          // Handle the @id/<name>, @+id/<name> and @android:id/<name>
          // We need to return the exact value that was compiled (from the various R classes),
          // as these values can be reused internally with calls to findViewById().
          // There's a trick with platform layouts that not use "android:" but their IDs are in
          // fact in the android.R and com.android.internal.R classes.
          // The field mPlatformFile will indicate that all IDs are to be looked up in the android R
          // classes exclusively.
  
          // if this is a reference to an id, find it.
          ResourceUrl resourceUrl = ResourceUrl.parse(value);
          if (resourceUrl != null) {
              if (resourceUrl.type == ResourceType.ID) {
                  ResourceReference referencedId =
                          resourceUrl.resolve(contextNamespace, namespaceResolver);
  
                  // Look for the idName in project or android R class depending on isPlatform.
                  if (resourceUrl.isCreate()) {
                      int idValue;
                      if (referencedId.getNamespace() == ResourceNamespace.ANDROID) {
                          idValue = Bridge.getResourceId(ResourceType.ID, resourceUrl.name);
                      } else {
                          idValue = mContext.getLayoutlibCallback().getOrGenerateResourceId(referencedId);
                      }
                      return idValue;
                  }
                  // This calls the same method as in if(create), but doesn't create a dynamic id, if
                  // one is not found.
                  return mContext.getResourceId(referencedId, defValue);
              }
              else if (resourceUrl.type == ResourceType.AAPT) {
                  ResourceReference referencedId =
                          resourceUrl.resolve(contextNamespace, namespaceResolver);
                  return mContext.getLayoutlibCallback().getOrGenerateResourceId(referencedId);
              }
          }
          // not a direct id valid reference. First check if it's an enum (this is a corner case
          // for attributes that have a reference|enum type), then fallback to resolve
          // as an ID without prefix.
          Integer enumValue = resolveEnumAttribute(index);
          if (enumValue != null) {
              return enumValue;
          }
  
          return defValue;
      }
  
      @Override
      public int getThemeAttributeId(int index, int defValue) {
          // TODO: Get the right Theme Attribute ID to enable caching of the drawables.
          return defValue;
      }
  
      /**
       * Retrieve the Drawable for the attribute at <var>index</var>.  This
       * gets the resource ID of the selected attribute, and uses
       * {@link Resources#getDrawable Resources.getDrawable} of the owning
       * Resources object to retrieve its Drawable.
       *
       * @param index Index of attribute to retrieve.
       *
       * @return Drawable for the attribute, or null if not defined.
       */
      @Override
      @Nullable
      public Drawable getDrawable(int index) {
          if (!hasValue(index)) {
              return null;
          }
  
          ResourceValue value = mResourceData[index];
          return ResourceHelper.getDrawable(value, mContext, mTheme);
      }
  
      /**
       * Version of {@link #getDrawable(int)} that accepts an override density.
       * @hide
       */
      @Override
      @Nullable
      public Drawable getDrawableForDensity(int index, int density) {
          return getDrawable(index);
      }
  
      /**
       * Retrieve the Typeface for the attribute at <var>index</var>.
       * @param index Index of attribute to retrieve.
       *
       * @return Typeface for the attribute, or null if not defined.
       */
      @Override
      public Typeface getFont(int index) {
          if (!hasValue(index)) {
              return null;
          }
  
          ResourceValue value = mResourceData[index];
          return ResourceHelper.getFont(value, mContext, mTheme);
      }
  
      /**
       * Retrieve the CharSequence[] for the attribute at <var>index</var>.
       * This gets the resource ID of the selected attribute, and uses
       * {@link Resources#getTextArray Resources.getTextArray} of the owning
       * Resources object to retrieve its String[].
       *
       * @param index Index of attribute to retrieve.
       *
       * @return CharSequence[] for the attribute, or null if not defined.
       */
      @Override
      public CharSequence[] getTextArray(int index) {
          if (!hasValue(index)) {
              return null;
          }
          ResourceValue resVal = mResourceData[index];
          if (resVal instanceof ArrayResourceValue) {
              ArrayResourceValue array = (ArrayResourceValue) resVal;
              int count = array.getElementCount();
              return count >= 0 ?
                      Resources_Delegate.resolveValues(mBridgeResources, array) :
                      null;
          }
          int id = getResourceId(index, 0);
          String resIdMessage = id > 0 ? " (resource id 0x" + Integer.toHexString(id) + ')' : "";
          assert false :
                  String.format("%1$s in %2$s%3$s is not a valid array resource.", resVal.getValue(),
                          mNames[index], resIdMessage);
  
          return new CharSequence[0];
      }
  
      @Override
      public int[] extractThemeAttrs() {
          // The drawables are always inflated with a Theme and we don't care about caching. So,
          // just return.
          return null;
      }
  
      @Override
      public int getChangingConfigurations() {
          // We don't care about caching. Any change in configuration is a fresh render. So,
          // just return.
          return 0;
      }
  
      /**
       * Retrieve the raw TypedValue for the attribute at <var>index</var>.
       *
       * @param index Index of attribute to retrieve.
       * @param outValue TypedValue object in which to place the attribute's
       *                 data.
       *
       * @return Returns true if the value was retrieved, else false.
       */
      @Override
      public boolean getValue(int index, TypedValue outValue) {
          // TODO: more switch cases for other types.
          outValue.type = getType(index);
          switch (outValue.type) {
              case TYPE_NULL:
                  return false;
              case TYPE_STRING:
                  outValue.string = getString(index);
                  return true;
              case TYPE_REFERENCE:
                  outValue.resourceId = mResourceId[index];
                  return true;
              case TYPE_INT_COLOR_ARGB4:
              case TYPE_INT_COLOR_ARGB8:
              case TYPE_INT_COLOR_RGB4:
              case TYPE_INT_COLOR_RGB8:
                  ColorStateList colorStateList = getColorStateList(index);
                  if (colorStateList == null) {
                      return false;
                  }
                  outValue.data = colorStateList.getDefaultColor();
                  return true;
              default:
                  // For back-compatibility, parse as float.
                  String s = getString(index);
                  return s != null &&
                          ResourceHelper.parseFloatAttribute(mNames[index], s, outValue, false);
          }
      }
  
      @Override
      @SuppressWarnings("ResultOfMethodCallIgnored")
      public int getType(int index) {
          String value = getString(index);
          if (value == null) {
              return TYPE_NULL;
          }
          if (value.startsWith(PREFIX_RESOURCE_REF)) {
              return TYPE_REFERENCE;
          }
          if (value.startsWith(PREFIX_THEME_REF)) {
              return TYPE_ATTRIBUTE;
          }
          try {
              // Don't care about the value. Only called to check if an exception is thrown.
              convertValueToInt(value, 0);
              if (value.startsWith("0x") || value.startsWith("0X")) {
                  return TYPE_INT_HEX;
              }
              // is it a color?
              if (value.startsWith("#")) {
                  int length = value.length() - 1;
                  if (length == 3) {  // rgb
                      return TYPE_INT_COLOR_RGB4;
                  }
                  if (length == 4) {  // argb
                      return TYPE_INT_COLOR_ARGB4;
                  }
                  if (length == 6) {  // rrggbb
                      return TYPE_INT_COLOR_RGB8;
                  }
                  if (length == 8) {  // aarrggbb
                      return TYPE_INT_COLOR_ARGB8;
                  }
              }
              if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) {
                  return TYPE_INT_BOOLEAN;
              }
              return TYPE_INT_DEC;
          } catch (NumberFormatException ignored) {
              try {
                  Float.parseFloat(value);
                  return TYPE_FLOAT;
              } catch (NumberFormatException ignore) {
              }
              // Might be a dimension.
              if (ResourceHelper.parseFloatAttribute(null, value, new TypedValue(), false)) {
                  return TYPE_DIMENSION;
              }
          }
          // TODO: handle fractions.
          return TYPE_STRING;
      }
  
      /**
       * Determines whether there is an attribute at <var>index</var>.
       *
       * @param index Index of attribute to retrieve.
       *
       * @return True if the attribute has a value, false otherwise.
       */
      @Override
      public boolean hasValue(int index) {
          return index >= 0 && index < mResourceData.length && mResourceData[index] != null;
      }
  
      @Override
      public boolean hasValueOrEmpty(int index) {
          return hasValue(index) || index >= 0 && index < mResourceData.length &&
                  mEmptyIds != null && Arrays.binarySearch(mEmptyIds, index) >= 0;
      }
  
      /**
       * Retrieve the raw TypedValue for the attribute at <var>index</var>
       * and return a temporary object holding its data.  This object is only
       * valid until the next call on to {@link TypedArray}.
       *
       * @param index Index of attribute to retrieve.
       *
       * @return Returns a TypedValue object if the attribute is defined,
       *         containing its data; otherwise returns null.  (You will not
       *         receive a TypedValue whose type is TYPE_NULL.)
       */
      @Override
      public TypedValue peekValue(int index) {
          if (index < 0 || index >= mResourceData.length) {
              return null;
          }
  
          if (getValue(index, mValue)) {
              return mValue;
          }
  
          return null;
      }
  
      /**
       * Returns a message about the parser state suitable for printing error messages.
       */
      @Override
      public String getPositionDescription() {
          return "<internal -- stub if needed>";
      }
  
      /**
       * Give back a previously retrieved TypedArray, for later re-use.
       */
      @Override
      public void recycle() {
          // pass
      }
  
      @Override
      public String toString() {
          return Arrays.toString(mResourceData);
      }
  
      /**
       * Searches for the string in the attributes (flag or enums) and returns the integer.
       * If found, it will return an integer matching the value.
       *
       * @param index Index of attribute to retrieve.
       *
       * @return Attribute int value, or null if not defined.
       */
      private Integer resolveEnumAttribute(int index) {
          // Get the map of attribute-constant -> IntegerValue
          Map<String, Integer> map = null;
          if (mNamespaces[index] == ResourceNamespace.ANDROID) {
              map = Bridge.getEnumValues(mNames[index]);
          } else {
              // get the styleable matching the resolved name
              RenderResources res = mContext.getRenderResources();
              ResourceValue attr = res.getResolvedResource(
                      ResourceReference.attr(mNamespaces[index], mNames[index]));
              if (attr instanceof AttrResourceValue) {
                  map = ((AttrResourceValue) attr).getAttributeValues();
              }
          }
  
          if (map != null && !map.isEmpty()) {
              // Accumulator to store the value of the 1+ constants.
              int result = 0;
              boolean found = false;
  
              String value = mResourceData[index].getValue();
              if (!value.isEmpty()) {
                  // Check if the value string is already representing an integer and return it if so.
                  // Resources coming from res.apk in an AAR may have flags and enums in integer form.
                  char c = value.charAt(0);
                  if (Character.isDigit(c) || c == '-' || c == '+') {
                      try {
                          return convertValueToInt(value, 0);
                      } catch (NumberFormatException e) {
                          // Ignore and continue.
                      }
                  }
                  // Split the value in case it is a mix of several flags.
                  String[] keywords = value.split("\\|");
                  for (String keyword : keywords) {
                      Integer i = map.get(keyword.trim());
                      if (i != null) {
                          result |= i;
                          found = true;
                      }
                      // TODO: We should act smartly and log a warning for incorrect keywords. However,
                      // this method is currently called even if the resourceValue is not an enum.
                  }
                  if (found) {
                      return result;
                  }
              }
          }
  
          return null;
      }
  
      /**
       * Copied from {@link XmlUtils#convertValueToInt(CharSequence, int)}, but adapted to account
       * for aapt, and the fact that host Java VM's Integer.parseInt("XXXXXXXX", 16) cannot handle
       * "XXXXXXXX" > 80000000.
       */
      private static int convertValueToInt(@Nullable String charSeq, int defValue) {
          if (null == charSeq || charSeq.isEmpty())
              return defValue;
  
          int sign = 1;
          int index = 0;
          int len = charSeq.length();
          int base = 10;
  
          if ('-' == charSeq.charAt(0)) {
              sign = -1;
              index++;
          }
  
          if ('0' == charSeq.charAt(index)) {
              //  Quick check for a zero by itself
              if (index == (len - 1))
                  return 0;
  
              char c = charSeq.charAt(index + 1);
  
              if ('x' == c || 'X' == c) {
                  index += 2;
                  base = 16;
              } else {
                  index++;
                  // Leave the base as 10. aapt removes the preceding zero, and thus when framework
                  // sees the value, it only gets the decimal value.
              }
          } else if ('#' == charSeq.charAt(index)) {
              return ResourceHelper.getColor(charSeq) * sign;
          } else if ("true".equals(charSeq) || "TRUE".equals(charSeq)) {
              return -1;
          } else if ("false".equals(charSeq) || "FALSE".equals(charSeq)) {
              return 0;
          }
  
          // Use Long, since we want to handle hex ints > 80000000.
          return ((int)Long.parseLong(charSeq.substring(index), base)) * sign;
      }
  
      static TypedArray obtain(Resources res, int len) {
          return new BridgeTypedArray(res, null, len);
      }
  }
  