| /* |
| * 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 com.android.layoutlib.bridge; |
| |
| import com.android.internal.util.XmlUtils; |
| import com.android.layoutlib.api.IResourceValue; |
| import com.android.layoutlib.api.IStyleResourceValue; |
| |
| import org.kxml2.io.KXmlParser; |
| import org.xmlpull.v1.XmlPullParser; |
| |
| import android.content.res.ColorStateList; |
| import android.content.res.Resources; |
| import android.content.res.TypedArray; |
| import android.graphics.drawable.Drawable; |
| import android.util.DisplayMetrics; |
| import android.util.TypedValue; |
| import android.view.ViewGroup.LayoutParams; |
| |
| import java.io.File; |
| import java.io.FileReader; |
| import java.util.Map; |
| |
| /** |
| * TODO: describe. |
| */ |
| public final class BridgeTypedArray extends TypedArray { |
| |
| @SuppressWarnings("hiding") |
| private BridgeResources mResources; |
| private BridgeContext mContext; |
| @SuppressWarnings("hiding") |
| private IResourceValue[] mData; |
| private String[] mNames; |
| private final boolean mPlatformFile; |
| |
| public BridgeTypedArray(BridgeResources resources, BridgeContext context, int len, |
| boolean platformFile) { |
| super(null, null, null, 0); |
| mResources = resources; |
| mContext = context; |
| mPlatformFile = platformFile; |
| mData = new IResourceValue[len]; |
| mNames = new String[len]; |
| } |
| |
| /** A bridge-specific method that sets a value in the type array */ |
| public void bridgeSetValue(int index, String name, IResourceValue value) { |
| mData[index] = value; |
| mNames[index] = name; |
| } |
| |
| /** |
| * Seals the array after all calls to {@link #bridgeSetValue(int, String, IResourceValue)} 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; |
| for (IResourceValue data : mData) { |
| if (data != null) { |
| count++; |
| } |
| } |
| |
| // 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 < mData.length ; i++) { |
| if (mData[i] != null) { |
| mIndices[index++] = i; |
| } |
| } |
| } |
| |
| /** |
| * Return the number of values in this array. |
| */ |
| @Override |
| public int length() { |
| return mData.length; |
| } |
| |
| /** |
| * Return the Resources object this array was loaded from. |
| */ |
| @Override |
| public Resources getResources() { |
| return mResources; |
| } |
| |
| /** |
| * 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) { |
| if (mData[index] != null) { |
| // FIXME: handle styled strings! |
| return mData[index].getValue(); |
| } |
| |
| return null; |
| } |
| |
| /** |
| * 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 (mData[index] != null) { |
| return mData[index].getValue(); |
| } |
| |
| return null; |
| } |
| |
| /** |
| * 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) { |
| if (mData[index] == null) { |
| return defValue; |
| } |
| |
| String s = mData[index].getValue(); |
| if (s != null) { |
| return XmlUtils.convertValueToBoolean(s, defValue); |
| } |
| |
| return 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) { |
| if (mData[index] == null) { |
| return defValue; |
| } |
| |
| String s = mData[index].getValue(); |
| |
| try { |
| return (s == null) ? defValue : XmlUtils.convertValueToInt(s, defValue); |
| } catch (NumberFormatException e) { |
| // pass |
| } |
| |
| // Field is not null and is not an integer. |
| // Check for possible constants and try to find them. |
| // Get the map of attribute-constant -> IntegerValue |
| Map<String, Integer> map = Bridge.getEnumValues(mNames[index]); |
| |
| if (map != null) { |
| // accumulator to store the value of the 1+ constants. |
| int result = 0; |
| |
| // split the value in case this is a mix of several flags. |
| String[] keywords = s.split("\\|"); |
| for (String keyword : keywords) { |
| Integer i = map.get(keyword.trim()); |
| if (i != null) { |
| result |= i.intValue(); |
| } else { |
| mContext.getLogger().warning(String.format( |
| "Unknown constant \"%s\" in attribute \"%2$s\"", |
| keyword, mNames[index])); |
| } |
| } |
| return result; |
| } |
| |
| 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) { |
| if (mData[index] == null) { |
| return defValue; |
| } |
| |
| String s = mData[index].getValue(); |
| |
| if (s != null) { |
| try { |
| return Float.parseFloat(s); |
| } catch (NumberFormatException e) { |
| mContext.getLogger().warning(String.format( |
| "Unable to convert \"%s\" into a float in attribute \"%2$s\"", |
| s, mNames[index])); |
| |
| // we'll return the default value below. |
| } |
| } |
| 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 (mData[index] == null) { |
| return defValue; |
| } |
| |
| String s = mData[index].getValue(); |
| try { |
| return ResourceHelper.getColor(s); |
| } catch (NumberFormatException e) { |
| mContext.getLogger().warning(String.format( |
| "Unable to convert \"%s\" into a color in attribute \"%2$s\"", |
| s, mNames[index])); |
| |
| // we'll return the default value below. |
| } |
| |
| return defValue; |
| } |
| |
| /** |
| * Retrieve the ColorStateList for the attribute at <var>index</var>. |
| * The value may be either a single solid color or a reference to |
| * a color or complex {@link android.content.res.ColorStateList} description. |
| * |
| * @param index Index of attribute to retrieve. |
| * |
| * @return ColorStateList for the attribute, or null if not defined. |
| */ |
| @Override |
| public ColorStateList getColorStateList(int index) { |
| if (mData[index] == null) { |
| return null; |
| } |
| |
| String value = mData[index].getValue(); |
| |
| if (value == null) { |
| return null; |
| } |
| |
| try { |
| int color = ResourceHelper.getColor(value); |
| return ColorStateList.valueOf(color); |
| } catch (NumberFormatException e) { |
| // if it's not a color value, we'll attempt to read the xml based color below. |
| } |
| |
| // let the framework inflate the ColorStateList from the XML file. |
| try { |
| File f = new File(value); |
| if (f.isFile()) { |
| KXmlParser parser = new KXmlParser(); |
| parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true); |
| parser.setInput(new FileReader(f)); |
| |
| ColorStateList colorStateList = ColorStateList.createFromXml( |
| mContext.getResources(), |
| // FIXME: we need to know if this resource is platform or not |
| new BridgeXmlBlockParser(parser, mContext, false)); |
| return colorStateList; |
| } |
| } catch (Exception e) { |
| // this is an error and not warning since the file existence is checked before |
| // attempting to parse it. |
| mContext.getLogger().error(e); |
| |
| // return null below. |
| } |
| |
| // looks like were unable to resolve the color value. |
| mContext.getLogger().warning(String.format( |
| "Unable to resolve color value \"%1$s\" in attribute \"%2$s\"", |
| value, mNames[index])); |
| |
| return null; |
| } |
| |
| /** |
| * 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) { |
| if (mData[index] == null) { |
| return defValue; |
| } |
| |
| String s = mData[index].getValue(); |
| |
| if (s != null) { |
| try { |
| return Integer.parseInt(s); |
| } catch (NumberFormatException e) { |
| mContext.getLogger().warning(String.format( |
| "Unable to convert \"%s\" into a integer in attribute \"%2$s\"", |
| s, mNames[index])); |
| |
| // The default value is returned below. |
| } |
| } |
| |
| return 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) { |
| if (mData[index] == null) { |
| return defValue; |
| } |
| |
| String s = mData[index].getValue(); |
| |
| if (s == null) { |
| return defValue; |
| } else if (s.equals(BridgeConstants.FILL_PARENT)) { |
| return LayoutParams.FILL_PARENT; |
| } else if (s.equals(BridgeConstants.WRAP_CONTENT)) { |
| return LayoutParams.WRAP_CONTENT; |
| } |
| |
| if (ResourceHelper.stringToFloat(s, mValue)) { |
| return mValue.getDimension(mResources.mMetrics); |
| } |
| |
| // looks like we were unable to resolve the dimension value |
| mContext.getLogger().warning(String.format( |
| "Unable to resolve dimension value \"%1$s\" in attribute \"%2$s\"", |
| s, mNames[index])); |
| |
| 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) { |
| if (mData[index] == null) { |
| return defValue; |
| } |
| |
| String s = mData[index].getValue(); |
| |
| if (s == null) { |
| return defValue; |
| } else if (s.equals(BridgeConstants.FILL_PARENT)) { |
| return LayoutParams.FILL_PARENT; |
| } else if (s.equals(BridgeConstants.WRAP_CONTENT)) { |
| return LayoutParams.WRAP_CONTENT; |
| } |
| |
| // FIXME huh? |
| |
| float f = getDimension(index, defValue); |
| final int res = (int)(f+0.5f); |
| if (res != 0) return res; |
| if (f == 0) return 0; |
| if (f > 0) return 1; |
| |
| throw new UnsupportedOperationException("Can't convert to dimension: " + |
| Integer.toString(index)); |
| } |
| |
| /** |
| * 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) { |
| return getDimensionPixelSize(index, 0); |
| } |
| |
| @Override |
| public int getLayoutDimension(int index, int defValue) { |
| return getDimensionPixelSize(index, defValue); |
| } |
| |
| /** |
| * 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) { |
| if (mData[index] == null) { |
| return defValue; |
| } |
| |
| String value = mData[index].getValue(); |
| if (value == null) { |
| return defValue; |
| } |
| |
| if (ResourceHelper.stringToFloat(value, mValue)) { |
| return mValue.getFraction(base, pbase); |
| } |
| |
| // looks like we were unable to resolve the fraction value |
| mContext.getLogger().warning(String.format( |
| "Unable to resolve fraction value \"%1$s\" in attribute \"%2$s\"", |
| value, mNames[index])); |
| |
| 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) { |
| // get the IResource for this index |
| IResourceValue resValue = mData[index]; |
| |
| // no data, return the default value. |
| if (resValue == null) { |
| return defValue; |
| } |
| |
| // check if this is a style resource |
| if (resValue instanceof IStyleResourceValue) { |
| // get the id that will represent this style. |
| return mContext.getDynamicIdByStyle((IStyleResourceValue)resValue); |
| } |
| |
| // if the attribute was a reference to an id, and not a declaration of an id (@+id), then |
| // the xml attribute value was "resolved" which leads us to a IResourceValue with |
| // getType() returning "id" and getName() returning the id name |
| // (and getValue() returning null!). We need to handle this! |
| if (resValue.getType() != null && resValue.getType().equals(BridgeConstants.RES_ID)) { |
| // if this is a framework id |
| if (mPlatformFile || resValue.isFramework()) { |
| // look for idName in the android R classes |
| return mContext.getFrameworkIdValue(resValue.getName(), defValue); |
| } |
| |
| // look for idName in the project R class. |
| return mContext.getProjectIdValue(resValue.getName(), defValue); |
| } |
| |
| // else, try to get the value, and resolve it somehow. |
| String value = resValue.getValue(); |
| if (value == null) { |
| return defValue; |
| } |
| |
| // if the value is just an integer, return it. |
| try { |
| int i = Integer.parseInt(value); |
| if (Integer.toString(i).equals(value)) { |
| return i; |
| } |
| } catch (NumberFormatException e) { |
| // pass |
| } |
| |
| // 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. |
| if (value.startsWith("@id/") || value.startsWith("@+") || |
| value.startsWith("@android:id/")) { |
| |
| int pos = value.indexOf('/'); |
| String idName = value.substring(pos + 1); |
| |
| // if this is a framework id |
| if (mPlatformFile || value.startsWith("@android") || value.startsWith("@+android")) { |
| // look for idName in the android R classes |
| return mContext.getFrameworkIdValue(idName, defValue); |
| } |
| |
| // look for idName in the project R class. |
| return mContext.getProjectIdValue(idName, defValue); |
| } |
| |
| // not a direct id valid reference? resolve it |
| Integer idValue = null; |
| |
| if (resValue.isFramework()) { |
| idValue = Bridge.getResourceValue(resValue.getType(), resValue.getName()); |
| } else { |
| idValue = mContext.getProjectCallback().getResourceValue( |
| resValue.getType(), resValue.getName()); |
| } |
| |
| if (idValue != null) { |
| return idValue.intValue(); |
| } |
| |
| mContext.getLogger().warning(String.format( |
| "Unable to resolve id \"%1$s\" for attribute \"%2$s\"", value, mNames[index])); |
| 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 |
| public Drawable getDrawable(int index) { |
| if (mData[index] == null) { |
| return null; |
| } |
| |
| IResourceValue value = mData[index]; |
| String stringValue = value.getValue(); |
| if (stringValue == null || BridgeConstants.REFERENCE_NULL.equals(stringValue)) { |
| return null; |
| } |
| |
| Drawable d = ResourceHelper.getDrawable(value, mContext, mData[index].isFramework()); |
| |
| if (d != null) { |
| return d; |
| } |
| |
| // looks like we were unable to resolve the drawable |
| mContext.getLogger().warning(String.format( |
| "Unable to resolve drawable \"%1$s\" in attribute \"%2$s\"", value, mNames[index])); |
| |
| return null; |
| } |
| |
| |
| /** |
| * 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 (mData[index] == null) { |
| return null; |
| } |
| |
| String value = mData[index].getValue(); |
| if (value != null) { |
| return new CharSequence[] { value }; |
| } |
| |
| mContext.getLogger().warning(String.format( |
| String.format("Unknown value for getTextArray(%d) => %s", //DEBUG |
| index, mData[index].getName()))); |
| |
| return null; |
| } |
| |
| /** |
| * 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) { |
| if (mData[index] == null) { |
| return false; |
| } |
| |
| String s = mData[index].getValue(); |
| |
| return ResourceHelper.stringToFloat(s, outValue); |
| } |
| |
| /** |
| * 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 mData[index] != null; |
| } |
| |
| /** |
| * 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 (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 StyledAttributes, for later re-use. |
| */ |
| @Override |
| public void recycle() { |
| // pass |
| } |
| |
| @Override |
| public boolean getValueAt(int index, TypedValue outValue) { |
| // pass |
| return false; |
| } |
| |
| @Override |
| public String toString() { |
| return mData.toString(); |
| } |
| } |