blob: b79cf6566987729e55c46c4154d181fc8d4b3558 [file] [log] [blame]
/*
* 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 android.annotation.AnyRes;
import android.annotation.ColorInt;
import android.annotation.Nullable;
import android.annotation.StyleableRes;
import android.annotation.UnsupportedAppUsage;
import android.content.pm.ActivityInfo;
import android.content.pm.ActivityInfo.Config;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.os.StrictMode;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import com.android.internal.util.XmlUtils;
import dalvik.system.VMRuntime;
import java.util.Arrays;
/**
* Container for an array of values that were retrieved with
* {@link Resources.Theme#obtainStyledAttributes(AttributeSet, int[], int, int)}
* or {@link Resources#obtainAttributes}. Be
* sure to call {@link #recycle} when done with them.
*
* The indices used to retrieve values from this structure correspond to
* the positions of the attributes given to obtainStyledAttributes.
*/
public class TypedArray {
static TypedArray obtain(Resources res, int len) {
TypedArray attrs = res.mTypedArrayPool.acquire();
if (attrs == null) {
attrs = new TypedArray(res);
}
attrs.mRecycled = false;
// Reset the assets, which may have changed due to configuration changes
// or further resource loading.
attrs.mAssets = res.getAssets();
attrs.mMetrics = res.getDisplayMetrics();
attrs.resize(len);
return attrs;
}
// STYLE_ prefixed constants are offsets within the typed data array.
// Keep this in sync with libs/androidfw/include/androidfw/AttributeResolution.h
static final int STYLE_NUM_ENTRIES = 7;
static final int STYLE_TYPE = 0;
static final int STYLE_DATA = 1;
static final int STYLE_ASSET_COOKIE = 2;
static final int STYLE_RESOURCE_ID = 3;
static final int STYLE_CHANGING_CONFIGURATIONS = 4;
static final int STYLE_DENSITY = 5;
static final int STYLE_SOURCE_RESOURCE_ID = 6;
@UnsupportedAppUsage
private final Resources mResources;
@UnsupportedAppUsage
private DisplayMetrics mMetrics;
@UnsupportedAppUsage
private AssetManager mAssets;
@UnsupportedAppUsage
private boolean mRecycled;
@UnsupportedAppUsage
/*package*/ XmlBlock.Parser mXml;
@UnsupportedAppUsage
/*package*/ Resources.Theme mTheme;
/**
* mData is used to hold the value/id and other metadata about each attribute.
*
* [type, data, asset cookie, resource id, changing configuration, density]
*
* type - type of this attribute, see TypedValue#TYPE_*
*
* data - can be used in various ways:
* a) actual value of the attribute if type is between #TYPE_FIRST_INT and #TYPE_LAST_INT
* 1) color represented by an integer (#TYPE_INT_COLOR_*)
* 2) boolean represented by an integer (#TYPE_INT_BOOLEAN)
* 3) integer number (#TYPE_TYPE_INT_DEC or #TYPE_INT_HEX)
* 4) float number where integer gets interpreted as float (#TYPE_FLOAT, #TYPE_FRACTION
* and #TYPE_DIMENSION)
* b) index into string block inside AssetManager (#TYPE_STRING)
* c) attribute resource id in the current theme/style (#TYPE_ATTRIBUTE)
*
* asset cookie - used in two ways:
* a) for strings, drawables, and fonts it specifies the set of apk assets to look at
* (multi-apk case)
* b) cookie + asset as a unique identifier for drawable caches
*
* resource id - id that was finally used to resolve this attribute
*
* changing configuration - a mask of the configuration parameters for which the values in this
* attribute may change
*
* density - density of drawable pointed to by this attribute
*/
@UnsupportedAppUsage
/*package*/ int[] mData;
/**
* Pointer to the start of the memory address of mData. It is passed via JNI and used to write
* to mData array directly from native code (AttributeResolution.cpp).
*/
/*package*/ long mDataAddress;
@UnsupportedAppUsage
/*package*/ int[] mIndices;
/**
* Similar to mDataAddress, but instead it is a pointer to mIndices address.
*/
/*package*/ long mIndicesAddress;
@UnsupportedAppUsage
/*package*/ int mLength;
@UnsupportedAppUsage
/*package*/ TypedValue mValue = new TypedValue();
private void resize(int len) {
mLength = len;
final int dataLen = len * STYLE_NUM_ENTRIES;
final int indicesLen = len + 1;
final VMRuntime runtime = VMRuntime.getRuntime();
if (mDataAddress == 0 || mData.length < dataLen) {
mData = (int[]) runtime.newNonMovableArray(int.class, dataLen);
mDataAddress = runtime.addressOf(mData);
mIndices = (int[]) runtime.newNonMovableArray(int.class, indicesLen);
mIndicesAddress = runtime.addressOf(mIndices);
}
}
/**
* Returns the number of values in this array.
*
* @throws RuntimeException if the TypedArray has already been recycled.
*/
public int length() {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
return mLength;
}
/**
* Returns the number of indices in the array that actually have data. Attributes with a value
* of @empty are included, as this is an explicit indicator.
*
* @throws RuntimeException if the TypedArray has already been recycled.
*/
public int getIndexCount() {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
return mIndices[0];
}
/**
* Returns an index in the array that has data. Attributes with a value of @empty are included,
* as this is an explicit indicator.
*
* @param at The index you would like to returned, ranging from 0 to
* {@link #getIndexCount()}.
*
* @return The index at the given offset, which can be used with
* {@link #getValue} and related APIs.
* @throws RuntimeException if the TypedArray has already been recycled.
*/
public int getIndex(int at) {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
return mIndices[1+at];
}
/**
* Returns the Resources object this array was loaded from.
*
* @throws RuntimeException if the TypedArray has already been recycled.
*/
public Resources getResources() {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
return mResources;
}
/**
* Retrieves the styled string value for the attribute at <var>index</var>.
* <p>
* If the attribute is not a string, this method will attempt to coerce
* it to a string.
*
* @param index Index of attribute to retrieve.
*
* @return CharSequence holding string data. May be styled. Returns
* {@code null} if the attribute is not defined or could not be
* coerced to a string.
* @throws RuntimeException if the TypedArray has already been recycled.
*/
public CharSequence getText(@StyleableRes int index) {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index + STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
return null;
} else if (type == TypedValue.TYPE_STRING) {
return loadStringValueAt(index);
}
final TypedValue v = mValue;
if (getValueAt(index, v)) {
return v.coerceToString();
}
// We already checked for TYPE_NULL. This should never happen.
throw new RuntimeException("getText of bad type: 0x" + Integer.toHexString(type));
}
/**
* Retrieves the string value for the attribute at <var>index</var>.
* <p>
* If the attribute is not a string, this method will attempt to coerce
* it to a string.
*
* @param index Index of attribute to retrieve.
*
* @return String holding string data. Any styling information is removed.
* Returns {@code null} if the attribute is not defined or could
* not be coerced to a string.
* @throws RuntimeException if the TypedArray has already been recycled.
*/
@Nullable
public String getString(@StyleableRes int index) {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index + STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
return null;
} else if (type == TypedValue.TYPE_STRING) {
return loadStringValueAt(index).toString();
}
final TypedValue v = mValue;
if (getValueAt(index, v)) {
final CharSequence cs = v.coerceToString();
return cs != null ? cs.toString() : null;
}
// We already checked for TYPE_NULL. This should never happen.
throw new RuntimeException("getString of bad type: 0x" + Integer.toHexString(type));
}
/**
* Retrieves the string value for the attribute at <var>index</var>, but
* only if that string comes from an immediate value in an XML file. That
* is, this does not allow references to string resources, string
* attributes, or conversions from other types. As such, this method
* will only return strings for TypedArray objects that come from
* attributes in an XML file.
*
* @param index Index of attribute to retrieve.
*
* @return String holding string data. Any styling information is removed.
* Returns {@code null} if the attribute is not defined or is not
* an immediate string value.
* @throws RuntimeException if the TypedArray has already been recycled.
*/
public String getNonResourceString(@StyleableRes int index) {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index + STYLE_TYPE];
if (type == TypedValue.TYPE_STRING) {
final int cookie = data[index + STYLE_ASSET_COOKIE];
if (cookie < 0) {
return mXml.getPooledString(data[index + STYLE_DATA]).toString();
}
}
return null;
}
/**
* Retrieves the string value for the attribute at <var>index</var> that is
* not allowed to change with the given configurations.
*
* @param index Index of attribute to retrieve.
* @param allowedChangingConfigs Bit mask of configurations from
* {@link Configuration}.NATIVE_CONFIG_* that are allowed to change.
*
* @return String holding string data. Any styling information is removed.
* Returns {@code null} if the attribute is not defined.
* @throws RuntimeException if the TypedArray has already been recycled.
* @hide
*/
@UnsupportedAppUsage
public String getNonConfigurationString(@StyleableRes int index,
@Config int allowedChangingConfigs) {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index + STYLE_TYPE];
final @Config int changingConfigs = ActivityInfo.activityInfoConfigNativeToJava(
data[index + STYLE_CHANGING_CONFIGURATIONS]);
if ((changingConfigs & ~allowedChangingConfigs) != 0) {
return null;
}
if (type == TypedValue.TYPE_NULL) {
return null;
} else if (type == TypedValue.TYPE_STRING) {
return loadStringValueAt(index).toString();
}
final TypedValue v = mValue;
if (getValueAt(index, v)) {
final CharSequence cs = v.coerceToString();
return cs != null ? cs.toString() : null;
}
// We already checked for TYPE_NULL. This should never happen.
throw new RuntimeException("getNonConfigurationString of bad type: 0x"
+ Integer.toHexString(type));
}
/**
* Retrieve the boolean value for the attribute at <var>index</var>.
* <p>
* If the attribute is an integer value, this method will return whether
* it is equal to zero. If the attribute is not a boolean or integer value,
* this method will attempt to coerce it to an integer using
* {@link Integer#decode(String)} and return whether it is equal to zero.
*
* @param index Index of attribute to retrieve.
* @param defValue Value to return if the attribute is not defined or
* cannot be coerced to an integer.
*
* @return Boolean value of the attribute, or defValue if the attribute was
* not defined or could not be coerced to an integer.
* @throws RuntimeException if the TypedArray has already been recycled.
*/
public boolean getBoolean(@StyleableRes int index, boolean defValue) {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index + STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
return defValue;
} else if (type >= TypedValue.TYPE_FIRST_INT
&& type <= TypedValue.TYPE_LAST_INT) {
return data[index + STYLE_DATA] != 0;
}
final TypedValue v = mValue;
if (getValueAt(index, v)) {
StrictMode.noteResourceMismatch(v);
return XmlUtils.convertValueToBoolean(v.coerceToString(), defValue);
}
// We already checked for TYPE_NULL. This should never happen.
throw new RuntimeException("getBoolean of bad type: 0x" + Integer.toHexString(type));
}
/**
* Retrieve the integer value for the attribute at <var>index</var>.
* <p>
* If the attribute is not an integer, this method will attempt to coerce
* it to an integer using {@link Integer#decode(String)}.
*
* @param index Index of attribute to retrieve.
* @param defValue Value to return if the attribute is not defined or
* cannot be coerced to an integer.
*
* @return Integer value of the attribute, or defValue if the attribute was
* not defined or could not be coerced to an integer.
* @throws RuntimeException if the TypedArray has already been recycled.
*/
public int getInt(@StyleableRes int index, int defValue) {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index + STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
return defValue;
} else if (type >= TypedValue.TYPE_FIRST_INT
&& type <= TypedValue.TYPE_LAST_INT) {
return data[index + STYLE_DATA];
}
final TypedValue v = mValue;
if (getValueAt(index, v)) {
StrictMode.noteResourceMismatch(v);
return XmlUtils.convertValueToInt(v.coerceToString(), defValue);
}
// We already checked for TYPE_NULL. This should never happen.
throw new RuntimeException("getInt of bad type: 0x" + Integer.toHexString(type));
}
/**
* Retrieve the float value for the attribute at <var>index</var>.
* <p>
* If the attribute is not a float or an integer, this method will attempt
* to coerce it to a float using {@link Float#parseFloat(String)}.
*
* @param index Index of attribute to retrieve.
*
* @return Attribute float value, or defValue if the attribute was
* not defined or could not be coerced to a float.
* @throws RuntimeException if the TypedArray has already been recycled.
*/
public float getFloat(@StyleableRes int index, float defValue) {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index + STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
return defValue;
} else if (type == TypedValue.TYPE_FLOAT) {
return Float.intBitsToFloat(data[index + STYLE_DATA]);
} else if (type >= TypedValue.TYPE_FIRST_INT
&& type <= TypedValue.TYPE_LAST_INT) {
return data[index + STYLE_DATA];
}
final TypedValue v = mValue;
if (getValueAt(index, v)) {
final CharSequence str = v.coerceToString();
if (str != null) {
StrictMode.noteResourceMismatch(v);
return Float.parseFloat(str.toString());
}
}
// We already checked for TYPE_NULL. This should never happen.
throw new RuntimeException("getFloat of bad type: 0x" + Integer.toHexString(type));
}
/**
* 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.
* <p>
* This method will throw an exception if the attribute is defined but is
* not an integer color or color state list.
*
* @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.
* @throws RuntimeException if the TypedArray has already been recycled.
* @throws UnsupportedOperationException if the attribute is defined but is
* not an integer color or color state list.
*/
@ColorInt
public int getColor(@StyleableRes int index, @ColorInt int defValue) {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
final int attrIndex = index;
index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index + STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
return defValue;
} else if (type >= TypedValue.TYPE_FIRST_INT
&& type <= TypedValue.TYPE_LAST_INT) {
return data[index + STYLE_DATA];
} else if (type == TypedValue.TYPE_STRING) {
final TypedValue value = mValue;
if (getValueAt(index, value)) {
final ColorStateList csl = mResources.loadColorStateList(
value, value.resourceId, mTheme);
return csl.getDefaultColor();
}
return defValue;
} else if (type == TypedValue.TYPE_ATTRIBUTE) {
final TypedValue value = mValue;
getValueAt(index, value);
throw new UnsupportedOperationException(
"Failed to resolve attribute at index " + attrIndex + ": " + value);
}
throw new UnsupportedOperationException("Can't convert value at index " + attrIndex
+ " to color: type=0x" + Integer.toHexString(type));
}
/**
* Retrieve the ComplexColor for the attribute at <var>index</var>.
* The value may be either a {@link android.content.res.ColorStateList} which can wrap a simple
* color value or a {@link android.content.res.GradientColor}
* <p>
* This method will return {@code null} if the attribute is not defined or
* is not an integer color, color state list or GradientColor.
*
* @param index Index of attribute to retrieve.
*
* @return ComplexColor for the attribute, or {@code null} if not defined.
* @throws RuntimeException if the attribute if the TypedArray has already
* been recycled.
* @throws UnsupportedOperationException if the attribute is defined but is
* not an integer color, color state list or GradientColor.
* @hide
*/
@Nullable
public ComplexColor getComplexColor(@StyleableRes int index) {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
final TypedValue value = mValue;
if (getValueAt(index * STYLE_NUM_ENTRIES, value)) {
if (value.type == TypedValue.TYPE_ATTRIBUTE) {
throw new UnsupportedOperationException(
"Failed to resolve attribute at index " + index + ": " + value);
}
return mResources.loadComplexColor(value, value.resourceId, mTheme);
}
return null;
}
/**
* 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.
* <p>
* This method will return {@code null} if the attribute is not defined or
* is not an integer color or color state list.
*
* @param index Index of attribute to retrieve.
*
* @return ColorStateList for the attribute, or {@code null} if not
* defined.
* @throws RuntimeException if the attribute if the TypedArray has already
* been recycled.
* @throws UnsupportedOperationException if the attribute is defined but is
* not an integer color or color state list.
*/
@Nullable
public ColorStateList getColorStateList(@StyleableRes int index) {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
final TypedValue value = mValue;
if (getValueAt(index * STYLE_NUM_ENTRIES, value)) {
if (value.type == TypedValue.TYPE_ATTRIBUTE) {
throw new UnsupportedOperationException(
"Failed to resolve attribute at index " + index + ": " + value);
}
return mResources.loadColorStateList(value, value.resourceId, mTheme);
}
return null;
}
/**
* Retrieve the integer value for the attribute at <var>index</var>.
* <p>
* Unlike {@link #getInt(int, int)}, this method will throw an exception if
* the attribute is defined but is not 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 integer value, or defValue if not defined.
* @throws RuntimeException if the TypedArray has already been recycled.
* @throws UnsupportedOperationException if the attribute is defined but is
* not an integer.
*/
public int getInteger(@StyleableRes int index, int defValue) {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
final int attrIndex = index;
index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index + STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
return defValue;
} else if (type >= TypedValue.TYPE_FIRST_INT
&& type <= TypedValue.TYPE_LAST_INT) {
return data[index + STYLE_DATA];
} else if (type == TypedValue.TYPE_ATTRIBUTE) {
final TypedValue value = mValue;
getValueAt(index, value);
throw new UnsupportedOperationException(
"Failed to resolve attribute at index " + attrIndex + ": " + value);
}
throw new UnsupportedOperationException("Can't convert value at index " + attrIndex
+ " to integer: type=0x" + Integer.toHexString(type));
}
/**
* 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.
* <p>
* This method will throw an exception if the attribute is defined but is
* not a dimension.
*
* @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.
* @throws RuntimeException if the TypedArray has already been recycled.
* @throws UnsupportedOperationException if the attribute is defined but is
* not an integer.
*
* @see #getDimensionPixelOffset
* @see #getDimensionPixelSize
*/
public float getDimension(@StyleableRes int index, float defValue) {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
final int attrIndex = index;
index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index + STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
return defValue;
} else if (type == TypedValue.TYPE_DIMENSION) {
return TypedValue.complexToDimension(data[index + STYLE_DATA], mMetrics);
} else if (type == TypedValue.TYPE_ATTRIBUTE) {
final TypedValue value = mValue;
getValueAt(index, value);
throw new UnsupportedOperationException(
"Failed to resolve attribute at index " + attrIndex + ": " + value);
}
throw new UnsupportedOperationException("Can't convert value at index " + attrIndex
+ " to dimension: type=0x" + Integer.toHexString(type));
}
/**
* 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.
* <p>
* This method will throw an exception if the attribute is defined but is
* not a dimension.
*
* @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.
* @throws RuntimeException if the TypedArray has already been recycled.
* @throws UnsupportedOperationException if the attribute is defined but is
* not an integer.
*
* @see #getDimension
* @see #getDimensionPixelSize
*/
public int getDimensionPixelOffset(@StyleableRes int index, int defValue) {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
final int attrIndex = index;
index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index + STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
return defValue;
} else if (type == TypedValue.TYPE_DIMENSION) {
return TypedValue.complexToDimensionPixelOffset(data[index + STYLE_DATA], mMetrics);
} else if (type == TypedValue.TYPE_ATTRIBUTE) {
final TypedValue value = mValue;
getValueAt(index, value);
throw new UnsupportedOperationException(
"Failed to resolve attribute at index " + attrIndex + ": " + value);
}
throw new UnsupportedOperationException("Can't convert value at index " + attrIndex
+ " to dimension: type=0x" + Integer.toHexString(type));
}
/**
* 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.
* <p>
* This method will throw an exception if the attribute is defined but is
* not a dimension.
*
* @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.
* @throws RuntimeException if the TypedArray has already been recycled.
* @throws UnsupportedOperationException if the attribute is defined but is
* not a dimension.
*
* @see #getDimension
* @see #getDimensionPixelOffset
*/
public int getDimensionPixelSize(@StyleableRes int index, int defValue) {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
final int attrIndex = index;
index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index + STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
return defValue;
} else if (type == TypedValue.TYPE_DIMENSION) {
return TypedValue.complexToDimensionPixelSize(data[index + STYLE_DATA], mMetrics);
} else if (type == TypedValue.TYPE_ATTRIBUTE) {
final TypedValue value = mValue;
getValueAt(index, value);
throw new UnsupportedOperationException(
"Failed to resolve attribute at index " + attrIndex + ": " + value);
}
throw new UnsupportedOperationException("Can't convert value at index " + attrIndex
+ " to dimension: type=0x" + Integer.toHexString(type));
}
/**
* 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}.
* <p>
* This method will throw an exception if the attribute is defined but is
* not a dimension or integer (enum).
*
* @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.
* @throws RuntimeException if the TypedArray has already been recycled.
* @throws UnsupportedOperationException if the attribute is defined but is
* not a dimension or integer (enum).
*/
public int getLayoutDimension(@StyleableRes int index, String name) {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
final int attrIndex = index;
index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index + STYLE_TYPE];
if (type >= TypedValue.TYPE_FIRST_INT
&& type <= TypedValue.TYPE_LAST_INT) {
return data[index + STYLE_DATA];
} else if (type == TypedValue.TYPE_DIMENSION) {
return TypedValue.complexToDimensionPixelSize(data[index + STYLE_DATA], mMetrics);
} else if (type == TypedValue.TYPE_ATTRIBUTE) {
final TypedValue value = mValue;
getValueAt(index, value);
throw new UnsupportedOperationException(
"Failed to resolve attribute at index " + attrIndex + ": " + value);
}
throw new UnsupportedOperationException(getPositionDescription()
+ ": You must supply a " + name + " attribute.");
}
/**
* 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 defValue The default value to return if this attribute is not
* default or contains the wrong type of data.
*
* @return Attribute dimension value multiplied by the appropriate
* metric and truncated to integer pixels.
* @throws RuntimeException if the TypedArray has already been recycled.
*/
public int getLayoutDimension(@StyleableRes int index, int defValue) {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index + STYLE_TYPE];
if (type >= TypedValue.TYPE_FIRST_INT
&& type <= TypedValue.TYPE_LAST_INT) {
return data[index + STYLE_DATA];
} else if (type == TypedValue.TYPE_DIMENSION) {
return TypedValue.complexToDimensionPixelSize(data[index + STYLE_DATA], mMetrics);
}
return defValue;
}
/**
* Retrieves 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.
* @throws RuntimeException if the TypedArray has already been recycled.
* @throws UnsupportedOperationException if the attribute is defined but is
* not a fraction.
*/
public float getFraction(@StyleableRes int index, int base, int pbase, float defValue) {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
final int attrIndex = index;
index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index + STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
return defValue;
} else if (type == TypedValue.TYPE_FRACTION) {
return TypedValue.complexToFraction(data[index + STYLE_DATA], base, pbase);
} else if (type == TypedValue.TYPE_ATTRIBUTE) {
final TypedValue value = mValue;
getValueAt(index, value);
throw new UnsupportedOperationException(
"Failed to resolve attribute at index " + attrIndex + ": " + value);
}
throw new UnsupportedOperationException("Can't convert value at index " + attrIndex
+ " to fraction: type=0x" + Integer.toHexString(type));
}
/**
* Retrieves 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.
* @throws RuntimeException if the TypedArray has already been recycled.
*/
@AnyRes
public int getResourceId(@StyleableRes int index, int defValue) {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
if (data[index + STYLE_TYPE] != TypedValue.TYPE_NULL) {
final int resid = data[index + STYLE_RESOURCE_ID];
if (resid != 0) {
return resid;
}
}
return defValue;
}
/**
* Retrieves the theme attribute resource identifier 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 Theme attribute resource identifier, or defValue if not defined.
* @throws RuntimeException if the TypedArray has already been recycled.
* @hide
*/
public int getThemeAttributeId(@StyleableRes int index, int defValue) {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
if (data[index + STYLE_TYPE] == TypedValue.TYPE_ATTRIBUTE) {
return data[index + STYLE_DATA];
}
return defValue;
}
/**
* Retrieve the Drawable for the attribute at <var>index</var>.
* <p>
* This method will throw an exception if the attribute is defined but is
* not a color or drawable resource.
*
* @param index Index of attribute to retrieve.
*
* @return Drawable for the attribute, or {@code null} if not defined.
* @throws RuntimeException if the TypedArray has already been recycled.
* @throws UnsupportedOperationException if the attribute is defined but is
* not a color or drawable resource.
*/
@Nullable
public Drawable getDrawable(@StyleableRes int index) {
return getDrawableForDensity(index, 0);
}
/**
* Version of {@link #getDrawable(int)} that accepts an override density.
* @hide
*/
@Nullable
public Drawable getDrawableForDensity(@StyleableRes int index, int density) {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
final TypedValue value = mValue;
if (getValueAt(index * STYLE_NUM_ENTRIES, value)) {
if (value.type == TypedValue.TYPE_ATTRIBUTE) {
throw new UnsupportedOperationException(
"Failed to resolve attribute at index " + index + ": " + value);
}
if (density > 0) {
// If the density is overridden, the value in the TypedArray will not reflect this.
// Do a separate lookup of the resourceId with the density override.
mResources.getValueForDensity(value.resourceId, density, value, true);
}
return mResources.loadDrawable(value, value.resourceId, density, mTheme);
}
return null;
}
/**
* Retrieve the Typeface for the attribute at <var>index</var>.
* <p>
* This method will throw an exception if the attribute is defined but is
* not a font.
*
* @param index Index of attribute to retrieve.
*
* @return Typeface for the attribute, or {@code null} if not defined.
* @throws RuntimeException if the TypedArray has already been recycled.
* @throws UnsupportedOperationException if the attribute is defined but is
* not a font resource.
*/
@Nullable
public Typeface getFont(@StyleableRes int index) {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
final TypedValue value = mValue;
if (getValueAt(index * STYLE_NUM_ENTRIES, value)) {
if (value.type == TypedValue.TYPE_ATTRIBUTE) {
throw new UnsupportedOperationException(
"Failed to resolve attribute at index " + index + ": " + value);
}
return mResources.getFont(value, value.resourceId);
}
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[].
* <p>
* This method will throw an exception if the attribute is defined but is
* not a text array resource.
*
* @param index Index of attribute to retrieve.
*
* @return CharSequence[] for the attribute, or {@code null} if not
* defined.
* @throws RuntimeException if the TypedArray has already been recycled.
*/
public CharSequence[] getTextArray(@StyleableRes int index) {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
final TypedValue value = mValue;
if (getValueAt(index * STYLE_NUM_ENTRIES, value)) {
return mResources.getTextArray(value.resourceId);
}
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 {@code true} if the value was retrieved and not @empty, {@code false} otherwise.
* @throws RuntimeException if the TypedArray has already been recycled.
*/
public boolean getValue(@StyleableRes int index, TypedValue outValue) {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
return getValueAt(index * STYLE_NUM_ENTRIES, outValue);
}
/**
* Returns the type of attribute at the specified index.
*
* @param index Index of attribute whose type to retrieve.
*
* @return Attribute type.
* @throws RuntimeException if the TypedArray has already been recycled.
*/
public int getType(@StyleableRes int index) {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
index *= STYLE_NUM_ENTRIES;
return mData[index + STYLE_TYPE];
}
/**
* Returns the resource ID of the style or layout against which the specified attribute was
* resolved, otherwise returns defValue.
*
* For example, if you we resolving two attributes {@code android:attribute1} and
* {@code android:attribute2} and you were inflating a {@link android.view.View} from
* {@code layout/my_layout.xml}:
* <pre>
* &lt;View
* style="@style/viewStyle"
* android:layout_width="wrap_content"
* android:layout_height="wrap_content"
* android:attribute1="foo"/&gt;
* </pre>
*
* and {@code @style/viewStyle} is:
* <pre>
* &lt;style android:name="viewStyle"&gt;
* &lt;item name="android:attribute2"&gt;bar&lt;item/&gt;
* &lt;style/&gt;
* </pre>
*
* then resolved {@link TypedArray} will have values that return source resource ID of
* {@code R.layout.my_layout} for {@code android:attribute1} and {@code R.style.viewStyle} for
* {@code android:attribute2}.
*
* @param index Index of attribute whose source style to retrieve.
* @param defaultValue Value to return if the attribute is not defined or
* not a resource.
*
* @return Either a style resource ID, layout resource ID, or defaultValue if it was not
* resolved in a style or layout.
* @throws RuntimeException if the TypedArray has already been recycled.
*/
@AnyRes
public int getSourceResourceId(@StyleableRes int index, @AnyRes int defaultValue) {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
index *= STYLE_NUM_ENTRIES;
final int resid = mData[index + STYLE_SOURCE_RESOURCE_ID];
if (resid != 0) {
return resid;
}
return defaultValue;
}
/**
* Determines whether there is an attribute at <var>index</var>.
* <p>
* <strong>Note:</strong> If the attribute was set to {@code @empty} or
* {@code @undefined}, this method returns {@code false}.
*
* @param index Index of attribute to retrieve.
*
* @return True if the attribute has a value, false otherwise.
* @throws RuntimeException if the TypedArray has already been recycled.
*/
public boolean hasValue(@StyleableRes int index) {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index + STYLE_TYPE];
return type != TypedValue.TYPE_NULL;
}
/**
* Determines whether there is an attribute at <var>index</var>, returning
* {@code true} if the attribute was explicitly set to {@code @empty} and
* {@code false} only if the attribute was undefined.
*
* @param index Index of attribute to retrieve.
*
* @return True if the attribute has a value or is empty, false otherwise.
* @throws RuntimeException if the TypedArray has already been recycled.
*/
public boolean hasValueOrEmpty(@StyleableRes int index) {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
index *= STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index + STYLE_TYPE];
return type != TypedValue.TYPE_NULL
|| data[index + STYLE_DATA] == TypedValue.DATA_NULL_EMPTY;
}
/**
* 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.)
* @throws RuntimeException if the TypedArray has already been recycled.
*/
public TypedValue peekValue(@StyleableRes int index) {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
final TypedValue value = mValue;
if (getValueAt(index * STYLE_NUM_ENTRIES, value)) {
return value;
}
return null;
}
/**
* Returns a message about the parser state suitable for printing error messages.
*
* @return Human-readable description of current parser state.
* @throws RuntimeException if the TypedArray has already been recycled.
*/
public String getPositionDescription() {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
return mXml != null ? mXml.getPositionDescription() : "<internal>";
}
/**
* Recycles the TypedArray, to be re-used by a later caller. After calling
* this function you must not ever touch the typed array again.
*
* @throws RuntimeException if the TypedArray has already been recycled.
*/
public void recycle() {
if (mRecycled) {
throw new RuntimeException(toString() + " recycled twice!");
}
mRecycled = true;
// These may have been set by the client.
mXml = null;
mTheme = null;
mAssets = null;
mResources.mTypedArrayPool.release(this);
}
/**
* Extracts theme attributes from a typed array for later resolution using
* {@link android.content.res.Resources.Theme#resolveAttributes(int[], int[])}.
* Removes the entries from the typed array so that subsequent calls to typed
* getters will return the default value without crashing.
*
* @return an array of length {@link #getIndexCount()} populated with theme
* attributes, or null if there are no theme attributes in the typed
* array
* @throws RuntimeException if the TypedArray has already been recycled.
* @hide
*/
@Nullable
@UnsupportedAppUsage
public int[] extractThemeAttrs() {
return extractThemeAttrs(null);
}
/**
* @hide
*/
@Nullable
@UnsupportedAppUsage
public int[] extractThemeAttrs(@Nullable int[] scrap) {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
int[] attrs = null;
final int[] data = mData;
final int N = length();
for (int i = 0; i < N; i++) {
final int index = i * STYLE_NUM_ENTRIES;
if (data[index + STYLE_TYPE] != TypedValue.TYPE_ATTRIBUTE) {
// Not an attribute, ignore.
continue;
}
// Null the entry so that we can safely call getZzz().
data[index + STYLE_TYPE] = TypedValue.TYPE_NULL;
final int attr = data[index + STYLE_DATA];
if (attr == 0) {
// Useless data, ignore.
continue;
}
// Ensure we have a usable attribute array.
if (attrs == null) {
if (scrap != null && scrap.length == N) {
attrs = scrap;
Arrays.fill(attrs, 0);
} else {
attrs = new int[N];
}
}
attrs[i] = attr;
}
return attrs;
}
/**
* Return a mask of the configuration parameters for which the values in
* this typed array may change.
*
* @return Returns a mask of the changing configuration parameters, as
* defined by {@link android.content.pm.ActivityInfo}.
* @throws RuntimeException if the TypedArray has already been recycled.
* @see android.content.pm.ActivityInfo
*/
public @Config int getChangingConfigurations() {
if (mRecycled) {
throw new RuntimeException("Cannot make calls to a recycled instance!");
}
@Config int changingConfig = 0;
final int[] data = mData;
final int N = length();
for (int i = 0; i < N; i++) {
final int index = i * STYLE_NUM_ENTRIES;
final int type = data[index + STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
continue;
}
changingConfig |= ActivityInfo.activityInfoConfigNativeToJava(
data[index + STYLE_CHANGING_CONFIGURATIONS]);
}
return changingConfig;
}
@UnsupportedAppUsage
private boolean getValueAt(int index, TypedValue outValue) {
final int[] data = mData;
final int type = data[index + STYLE_TYPE];
if (type == TypedValue.TYPE_NULL) {
return false;
}
outValue.type = type;
outValue.data = data[index + STYLE_DATA];
outValue.assetCookie = data[index + STYLE_ASSET_COOKIE];
outValue.resourceId = data[index + STYLE_RESOURCE_ID];
outValue.changingConfigurations = ActivityInfo.activityInfoConfigNativeToJava(
data[index + STYLE_CHANGING_CONFIGURATIONS]);
outValue.density = data[index + STYLE_DENSITY];
outValue.string = (type == TypedValue.TYPE_STRING) ? loadStringValueAt(index) : null;
outValue.sourceResourceId = data[index + STYLE_SOURCE_RESOURCE_ID];
return true;
}
private CharSequence loadStringValueAt(int index) {
final int[] data = mData;
final int cookie = data[index + STYLE_ASSET_COOKIE];
if (cookie < 0) {
if (mXml != null) {
return mXml.getPooledString(data[index + STYLE_DATA]);
}
return null;
}
return mAssets.getPooledStringForCookie(cookie, data[index + STYLE_DATA]);
}
/** @hide */
protected TypedArray(Resources resources) {
mResources = resources;
mMetrics = mResources.getDisplayMetrics();
mAssets = mResources.getAssets();
}
@Override
public String toString() {
return Arrays.toString(mData);
}
}