(csl));
+ // Prune the cache before adding new items.
+ final int N = sCache.size();
+ for (int i = N - 1; i >= 0; i--) {
+ if (sCache.valueAt(i).get() == null) {
+ sCache.removeAt(i);
+ }
+ }
+
+ final ColorStateList csl = new ColorStateList(EMPTY, new int[] {color});
+ sCache.put(color, new WeakReference<>(csl));
return csl;
}
}
/**
- * Create a ColorStateList from an XML document, given a set of {@link Resources}.
+ * Creates a ColorStateList with the same properties as another
+ * ColorStateList.
+ *
+ * The properties of the new ColorStateList can be modified without
+ * affecting the source ColorStateList.
+ *
+ * @param orig the source color state list
*/
+ private ColorStateList(ColorStateList orig) {
+ if (orig != null) {
+ mChangingConfigurations = orig.mChangingConfigurations;
+ mStateSpecs = orig.mStateSpecs;
+ mDefaultColor = orig.mDefaultColor;
+ mIsOpaque = orig.mIsOpaque;
+
+ // Deep copy, these may change due to applyTheme().
+ mThemeAttrs = orig.mThemeAttrs.clone();
+ mColors = orig.mColors.clone();
+ }
+ }
+
+ /**
+ * Creates a ColorStateList from an XML document.
+ *
+ * @param r Resources against which the ColorStateList should be inflated.
+ * @param parser Parser for the XML document defining the ColorStateList.
+ * @return A new color state list.
+ *
+ * @deprecated Use #createFromXml(Resources, XmlPullParser parser, Theme)
+ */
+ @NonNull
+ @Deprecated
public static ColorStateList createFromXml(Resources r, XmlPullParser parser)
throws XmlPullParserException, IOException {
+ return createFromXml(r, parser, null);
+ }
- AttributeSet attrs = Xml.asAttributeSet(parser);
+ /**
+ * Creates a ColorStateList from an XML document using given a set of
+ * {@link Resources} and a {@link Theme}.
+ *
+ * @param r Resources against which the ColorStateList should be inflated.
+ * @param parser Parser for the XML document defining the ColorStateList.
+ * @param theme Optional theme to apply to the color state list, may be
+ * {@code null}.
+ * @return A new color state list.
+ */
+ @NonNull
+ public static ColorStateList createFromXml(@NonNull Resources r, @NonNull XmlPullParser parser,
+ @Nullable Theme theme) throws XmlPullParserException, IOException {
+ final AttributeSet attrs = Xml.asAttributeSet(parser);
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) {
+ // Seek parser to start tag.
}
if (type != XmlPullParser.START_TAG) {
throw new XmlPullParserException("No start tag found");
}
- return createFromXmlInner(r, parser, attrs);
+ return createFromXmlInner(r, parser, attrs, theme);
}
- /* Create from inside an XML document. Called on a parser positioned at
- * a tag in an XML document, tries to create a ColorStateList from that tag.
- * Returns null if the tag is not a valid ColorStateList.
+ /**
+ * Create from inside an XML document. Called on a parser positioned at a
+ * tag in an XML document, tries to create a ColorStateList from that tag.
+ *
+ * @throws XmlPullParserException if the current tag is not <selector>
+ * @return A new color state list for the current tag.
*/
- private static ColorStateList createFromXmlInner(Resources r, XmlPullParser parser,
- AttributeSet attrs) throws XmlPullParserException, IOException {
-
- ColorStateList colorStateList;
-
+ @NonNull
+ static ColorStateList createFromXmlInner(@NonNull Resources r,
+ @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme)
+ throws XmlPullParserException, IOException {
final String name = parser.getName();
-
- if (name.equals("selector")) {
- colorStateList = new ColorStateList();
- } else {
+ if (!name.equals("selector")) {
throw new XmlPullParserException(
- parser.getPositionDescription() + ": invalid drawable tag " + name);
+ parser.getPositionDescription() + ": invalid color state list tag " + name);
}
- colorStateList.inflate(r, parser, attrs);
+ final ColorStateList colorStateList = new ColorStateList();
+ colorStateList.inflate(r, parser, attrs, theme);
return colorStateList;
}
/**
- * Creates a new ColorStateList that has the same states and
- * colors as this one but where each color has the specified alpha value
- * (0-255).
+ * Creates a new ColorStateList that has the same states and colors as this
+ * one but where each color has the specified alpha value (0-255).
+ *
+ * @param alpha The new alpha channel value (0-255).
+ * @return A new color state list.
*/
+ @NonNull
public ColorStateList withAlpha(int alpha) {
- int[] colors = new int[mColors.length];
-
- int len = colors.length;
+ final int[] colors = new int[mColors.length];
+ final int len = colors.length;
for (int i = 0; i < len; i++) {
colors[i] = (mColors[i] & 0xFFFFFF) | (alpha << 24);
}
@@ -165,95 +243,254 @@ public class ColorStateList {
/**
* Fill in this object based on the contents of an XML "selector" element.
*/
- private void inflate(Resources r, XmlPullParser parser, AttributeSet attrs)
+ private void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+ @NonNull AttributeSet attrs, @Nullable Theme theme)
throws XmlPullParserException, IOException {
-
- int type;
-
final int innerDepth = parser.getDepth() + 1;
int depth;
+ int type;
- int listAllocated = 20;
+ int changingConfigurations = 0;
+ int defaultColor = DEFAULT_COLOR;
+
+ boolean hasUnresolvedAttrs = false;
+
+ int[][] stateSpecList = ArrayUtils.newUnpaddedArray(int[].class, 20);
+ int[][] themeAttrsList = new int[stateSpecList.length][];
+ int[] colorList = new int[stateSpecList.length];
int listSize = 0;
- int[] colorList = new int[listAllocated];
- int[][] stateSpecList = new int[listAllocated][];
while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
- if (type != XmlPullParser.START_TAG) {
+ if (type != XmlPullParser.START_TAG || depth > innerDepth || !parser.getName().equals("item")) {
continue;
}
- if (depth > innerDepth || !parser.getName().equals("item")) {
- continue;
- }
+ final TypedArray a = Resources.obtainAttributes(r, theme, attrs,
+ R.styleable.ColorStateListItem);
+ final int[] themeAttrs = a.extractThemeAttrs();
+ final int baseColor = a.getColor(R.styleable.ColorStateListItem_color, Color.MAGENTA);
+ final float alphaMod = a.getFloat(R.styleable.ColorStateListItem_alpha, 1.0f);
- int color = 0xffff0000;
+ changingConfigurations |= a.getChangingConfigurations();
- int i;
+ a.recycle();
+
+ // Parse all unrecognized attributes as state specifiers.
int j = 0;
final int numAttrs = attrs.getAttributeCount();
int[] stateSpec = new int[numAttrs];
- TypedArray a = Context.this_application.obtainStyledAttributes(attrs, com.android.internal.R.styleable.ColorStateListItem);
- color = a.getColor(com.android.internal.R.styleable.ColorStateListItem_color, color);
- a.recycle();
- for (i = 0; i < numAttrs; i++) {
+ for (int i = 0; i < numAttrs; i++) {
final int stateResId = attrs.getAttributeNameResource(i);
- if (stateResId == 0)
- break;
- if (stateResId == com.android.internal.R.attr.color) {
- } else {
- stateSpec[j++] = attrs.getAttributeBooleanValue(i, false)
- ? stateResId
- : -stateResId;
+ switch (stateResId) {
+ case R.attr.color:
+ case R.attr.alpha:
+ // Recognized attribute, ignore.
+ break;
+ default:
+ stateSpec[j++] = attrs.getAttributeBooleanValue(i, false)
+ ? stateResId
+ : -stateResId;
}
}
stateSpec = StateSet.trimStateSet(stateSpec, j);
+ // Apply alpha modulation. If we couldn't resolve the color or
+ // alpha yet, the default values leave us enough information to
+ // modulate again during applyTheme().
+ final int color = modulateColorAlpha(baseColor, alphaMod);
if (listSize == 0 || stateSpec.length == 0) {
- mDefaultColor = color;
+ defaultColor = color;
}
- if (listSize + 1 >= listAllocated) {
- listAllocated = ArrayUtils.idealIntArraySize(listSize + 1);
-
- int[] ncolor = new int[listAllocated];
- System.arraycopy(colorList, 0, ncolor, 0, listSize);
-
- int[][] nstate = new int[listAllocated][];
- System.arraycopy(stateSpecList, 0, nstate, 0, listSize);
-
- colorList = ncolor;
- stateSpecList = nstate;
+ if (themeAttrs != null) {
+ hasUnresolvedAttrs = true;
}
- colorList[listSize] = color;
- stateSpecList[listSize] = stateSpec;
+ colorList = GrowingArrayUtils.append(colorList, listSize, color);
+ themeAttrsList = GrowingArrayUtils.append(themeAttrsList, listSize, themeAttrs);
+ stateSpecList = GrowingArrayUtils.append(stateSpecList, listSize, stateSpec);
listSize++;
}
+ mChangingConfigurations = changingConfigurations;
+ mDefaultColor = defaultColor;
+
+ if (hasUnresolvedAttrs) {
+ mThemeAttrs = new int[listSize][];
+ System.arraycopy(themeAttrsList, 0, mThemeAttrs, 0, listSize);
+ } else {
+ mThemeAttrs = null;
+ }
+
mColors = new int[listSize];
mStateSpecs = new int[listSize][];
System.arraycopy(colorList, 0, mColors, 0, listSize);
System.arraycopy(stateSpecList, 0, mStateSpecs, 0, listSize);
+
+ onColorsChanged();
}
+ /**
+ * Returns whether a theme can be applied to this color state list, which
+ * usually indicates that the color state list has unresolved theme
+ * attributes.
+ *
+ * @return whether a theme can be applied to this color state list
+ * @hide only for resource preloading
+ */
+ @Override
+ public boolean canApplyTheme() {
+ return mThemeAttrs != null;
+ }
+
+ /**
+ * Applies a theme to this color state list.
+ *
+ * Note: Applying a theme may affect the changing
+ * configuration parameters of this color state list. After calling this
+ * method, any dependent configurations must be updated by obtaining the
+ * new configuration mask from {@link #getChangingConfigurations()}.
+ *
+ * @param t the theme to apply
+ */
+ private void applyTheme(Theme t) {
+ if (mThemeAttrs == null) {
+ return;
+ }
+
+ boolean hasUnresolvedAttrs = false;
+
+ final int[][] themeAttrsList = mThemeAttrs;
+ final int N = themeAttrsList.length;
+ for (int i = 0; i < N; i++) {
+ if (themeAttrsList[i] != null) {
+ final TypedArray a = t.resolveAttributes(themeAttrsList[i],
+ R.styleable.ColorStateListItem);
+
+ final float defaultAlphaMod;
+ if (themeAttrsList[i][R.styleable.ColorStateListItem_color] != 0) {
+ // If the base color hasn't been resolved yet, the current
+ // color's alpha channel is either full-opacity (if we
+ // haven't resolved the alpha modulation yet) or
+ // pre-modulated. Either is okay as a default value.
+ defaultAlphaMod = Color.alpha(mColors[i]) / 255.0f;
+ } else {
+ // Otherwise, the only correct default value is 1. Even if
+ // nothing is resolved during this call, we can apply this
+ // multiple times without losing of information.
+ defaultAlphaMod = 1.0f;
+ }
+
+ // Extract the theme attributes, if any, before attempting to
+ // read from the typed array. This prevents a crash if we have
+ // unresolved attrs.
+ themeAttrsList[i] = a.extractThemeAttrs(themeAttrsList[i]);
+ if (themeAttrsList[i] != null) {
+ hasUnresolvedAttrs = true;
+ }
+
+ final int baseColor = a.getColor(
+ R.styleable.ColorStateListItem_color, mColors[i]);
+ final float alphaMod = a.getFloat(
+ R.styleable.ColorStateListItem_alpha, defaultAlphaMod);
+ mColors[i] = modulateColorAlpha(baseColor, alphaMod);
+
+ // Account for any configuration changes.
+ mChangingConfigurations |= a.getChangingConfigurations();
+
+ a.recycle();
+ }
+ }
+
+ if (!hasUnresolvedAttrs) {
+ mThemeAttrs = null;
+ }
+
+ onColorsChanged();
+ }
+
+ /**
+ * Returns an appropriately themed color state list.
+ *
+ * @param t the theme to apply
+ * @return a copy of the color state list with the theme applied, or the
+ * color state list itself if there were no unresolved theme
+ * attributes
+ * @hide only for resource preloading
+ */
+ @Override
+ public ColorStateList obtainForTheme(Theme t) {
+ if (t == null || !canApplyTheme()) {
+ return this;
+ }
+
+ final ColorStateList clone = new ColorStateList(this);
+ clone.applyTheme(t);
+ return clone;
+ }
+
+ /**
+ * Returns a mask of the configuration parameters for which this color
+ * state list may change, requiring that it be re-created.
+ *
+ * @return a mask of the changing configuration parameters, as defined by
+ * {@link android.content.pm.ActivityInfo}
+ *
+ * @see android.content.pm.ActivityInfo
+ */
+ public int getChangingConfigurations() {
+ return mChangingConfigurations;
+ }
+
+ private int modulateColorAlpha(int baseColor, float alphaMod) {
+ if (alphaMod == 1.0f) {
+ return baseColor;
+ }
+
+ final int baseAlpha = Color.alpha(baseColor);
+ final int alpha = MathUtils.constrain((int)(baseAlpha * alphaMod + 0.5f), 0, 255);
+ return (baseColor & 0xFFFFFF) | (alpha << 24);
+ }
+
+ /**
+ * Indicates whether this color state list contains more than one state spec
+ * and will change color based on state.
+ *
+ * @return True if this color state list changes color based on state, false
+ * otherwise.
+ * @see #getColorForState(int[], int)
+ */
+ @Override
public boolean isStateful() {
return mStateSpecs.length > 1;
}
/**
- * Return the color associated with the given set of {@link android.view.View} states.
+ * Indicates whether this color state list is opaque, which means that every
+ * color returned from {@link #getColorForState(int[], int)} has an alpha
+ * value of 255.
+ *
+ * @return True if this color state list is opaque.
+ */
+ public boolean isOpaque() {
+ return mIsOpaque;
+ }
+
+ /**
+ * Return the color associated with the given set of
+ * {@link android.view.View} states.
*
* @param stateSet an array of {@link android.view.View} states
- * @param defaultColor the color to return if there's not state spec in this
- * {@link ColorStateList} that matches the stateSet.
+ * @param defaultColor the color to return if there's no matching state
+ * spec in this {@link ColorStateList} that matches the
+ * stateSet.
*
* @return the color associated with that set of states in this {@link ColorStateList}.
*/
- public int getColorForState(int[] stateSet, int defaultColor) {
+ public int getColorForState(@Nullable int[] stateSet, int defaultColor) {
final int setLength = mStateSpecs.length;
for (int i = 0; i < setLength; i++) {
- int[] stateSpec = mStateSpecs[i];
+ final int[] stateSpec = mStateSpecs[i];
if (StateSet.stateSetMatches(stateSpec, stateSet)) {
return mColors[i];
}
@@ -266,19 +503,170 @@ public class ColorStateList {
*
* @return the default color in this {@link ColorStateList}.
*/
+ //@ColorInt
public int getDefaultColor() {
return mDefaultColor;
}
+ /**
+ * Return the states in this {@link ColorStateList}. The returned array
+ * should not be modified.
+ *
+ * @return the states in this {@link ColorStateList}
+ * @hide
+ */
+ public int[][] getStates() {
+ return mStateSpecs;
+ }
+
+ /**
+ * Return the colors in this {@link ColorStateList}. The returned array
+ * should not be modified.
+ *
+ * @return the colors in this {@link ColorStateList}
+ * @hide
+ */
+ public int[] getColors() {
+ return mColors;
+ }
+
+ /**
+ * Returns whether the specified state is referenced in any of the state
+ * specs contained within this ColorStateList.
+ *
+ * Any reference, either positive or negative {ex. ~R.attr.state_enabled},
+ * will cause this method to return {@code true}. Wildcards are not counted
+ * as references.
+ *
+ * @param state the state to search for
+ * @return {@code true} if the state if referenced, {@code false} otherwise
+ * @hide Use only as directed. For internal use only.
+ */
+ public boolean hasState(int state) {
+ final int[][] stateSpecs = mStateSpecs;
+ final int specCount = stateSpecs.length;
+ for (int specIndex = 0; specIndex < specCount; specIndex++) {
+ final int[] states = stateSpecs[specIndex];
+ final int stateCount = states.length;
+ for (int stateIndex = 0; stateIndex < stateCount; stateIndex++) {
+ if (states[stateIndex] == state || states[stateIndex] == ~state) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ @Override
public String toString() {
return "ColorStateList{"
+
+ "mThemeAttrs=" + Arrays.deepToString(mThemeAttrs) +
+ "mChangingConfigurations=" + mChangingConfigurations +
"mStateSpecs=" + Arrays.deepToString(mStateSpecs) +
"mColors=" + Arrays.toString(mColors) +
"mDefaultColor=" + mDefaultColor + '}';
}
+ /**
+ * Updates the default color and opacity.
+ */
+ private void onColorsChanged() {
+ int defaultColor = DEFAULT_COLOR;
+ boolean isOpaque = true;
+
+ final int[][] states = mStateSpecs;
+ final int[] colors = mColors;
+ final int N = states.length;
+ if (N > 0) {
+ defaultColor = colors[0];
+
+ for (int i = N - 1; i > 0; i--) {
+ if (states[i].length == 0) {
+ defaultColor = colors[i];
+ break;
+ }
+ }
+
+ for (int i = 0; i < N; i++) {
+ if (Color.alpha(colors[i]) != 0xFF) {
+ isOpaque = false;
+ break;
+ }
+ }
+ }
+
+ mDefaultColor = defaultColor;
+ mIsOpaque = isOpaque;
+ }
+
+ /**
+ * @return a factory that can create new instances of this ColorStateList
+ * @hide only for resource preloading
+ */
+ public ConstantState getConstantState() {
+ if (mFactory == null) {
+ mFactory = new ColorStateListFactory(this);
+ }
+ return mFactory;
+ }
+
+ private static class ColorStateListFactory extends ConstantState {
+ private final ColorStateList mSrc;
+
+ public ColorStateListFactory(ColorStateList src) {
+ mSrc = src;
+ }
+
+ @Override
+ public int getChangingConfigurations() {
+ return mSrc.mChangingConfigurations;
+ }
+
+ @Override
+ public ColorStateList newInstance() {
+ return mSrc;
+ }
+
+ @Override
+ public ColorStateList newInstance(Resources res, Theme theme) {
+ return (ColorStateList)mSrc.obtainForTheme(theme);
+ }
+ }
+
+ /*@Override
public int describeContents() {
return 0;
}
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ if (canApplyTheme()) {
+ Log.w(TAG, "Wrote partially-resolved ColorStateList to parcel!");
+ }
+ final int N = mStateSpecs.length;
+ dest.writeInt(N);
+ for (int i = 0; i < N; i++) {
+ dest.writeIntArray(mStateSpecs[i]);
+ }
+ dest.writeIntArray(mColors);
+ }
+
+ public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
+ @Override
+ public ColorStateList[] newArray(int size) {
+ return new ColorStateList[size];
+ }
+
+ @Override
+ public ColorStateList createFromParcel(Parcel source) {
+ final int N = source.readInt();
+ final int[][] stateSpecs = new int[N][];
+ for (int i = 0; i < N; i++) {
+ stateSpecs[i] = source.createIntArray();
+ }
+ final int[] colors = source.createIntArray();
+ return new ColorStateList(stateSpecs, colors);
+ }
+ };*/
}
diff --git a/src/api-impl/android/content/res/ComplexColor.java b/src/api-impl/android/content/res/ComplexColor.java
new file mode 100644
index 00000000..ad547e67
--- /dev/null
+++ b/src/api-impl/android/content/res/ComplexColor.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2016 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.ColorInt;
+import android.content.res.Resources.Theme;
+import android.graphics.Color;
+
+/**
+ * Defines an abstract class for the complex color information, like
+ * {@link android.content.res.ColorStateList} or {@link android.content.res.GradientColor}
+ */
+public abstract class ComplexColor {
+ /**
+ * @return {@code true} if this ComplexColor changes color based on state, {@code false}
+ * otherwise.
+ */
+ public boolean isStateful() { return false; }
+
+ /**
+ * @return the default color.
+ */
+ //@ColorInt
+ public abstract int getDefaultColor();
+
+ /**
+ * @hide only for resource preloading
+ *
+ */
+ public abstract ConstantState getConstantState();
+
+ /**
+ * @hide only for resource preloading
+ */
+ public abstract boolean canApplyTheme();
+
+ /**
+ * @hide only for resource preloading
+ */
+ public abstract ComplexColor obtainForTheme(Theme t);
+}
diff --git a/src/api-impl/android/content/res/ConfigurationBoundResourceCache.java b/src/api-impl/android/content/res/ConfigurationBoundResourceCache.java
new file mode 100644
index 00000000..5ad012aa
--- /dev/null
+++ b/src/api-impl/android/content/res/ConfigurationBoundResourceCache.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2014 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;
+
+/**
+ * A Cache class which can be used to cache resource objects that are easy to clone but more
+ * expensive to inflate.
+ *
+ * @hide For internal use only.
+ */
+public class ConfigurationBoundResourceCache extends ThemedResourceCache> {
+ private final Resources mResources;
+
+ /**
+ * Creates a cache for the given Resources instance.
+ *
+ * @param resources the resources to use when creating new instances
+ */
+ public ConfigurationBoundResourceCache(Resources resources) {
+ mResources = resources;
+ }
+
+ /**
+ * If the resource is cached, creates and returns a new instance of it.
+ *
+ * @param key a key that uniquely identifies the drawable resource
+ * @param theme the theme where the resource will be used
+ * @return a new instance of the resource, or {@code null} if not in
+ * the cache
+ */
+ public T getInstance(long key, Resources.Theme theme) {
+ final ConstantState entry = get(key, theme);
+ if (entry != null) {
+ return entry.newInstance(mResources, theme);
+ }
+
+ return null;
+ }
+
+ @Override
+ public boolean shouldInvalidateEntry(ConstantState entry, int configChanges) {
+ return Configuration.needNewResources(configChanges, entry.getChangingConfigurations());
+ }
+}
diff --git a/src/api-impl/android/content/res/ConstantState.java b/src/api-impl/android/content/res/ConstantState.java
new file mode 100644
index 00000000..73bfc819
--- /dev/null
+++ b/src/api-impl/android/content/res/ConstantState.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2014 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;
+
+/**
+ * A cache class that can provide new instances of a particular resource which may change
+ * depending on the current {@link Resources.Theme} or {@link Configuration}.
+ *
+ * A constant state should be able to return a bitmask of changing configurations, which
+ * identifies the type of configuration changes that may invalidate this resource. These
+ * configuration changes can be obtained from {@link android.util.TypedValue}. Entities such as
+ * {@link android.animation.Animator} also provide a changing configuration method to include
+ * their dependencies (e.g. An AnimatorSet's changing configuration is the union of the
+ * changing configurations of each Animator in the set)
+ * @hide
+ */
+abstract public class ConstantState {
+
+ /**
+ * Return a bit mask of configuration changes that will impact
+ * this resource (and thus require completely reloading it).
+ */
+ abstract public int getChangingConfigurations();
+
+ /**
+ * Create a new instance without supplying resources the caller
+ * is running in.
+ */
+ public abstract T newInstance();
+
+ /**
+ * Create a new instance from its constant state. This
+ * must be implemented for resources that change based on the target
+ * density of their caller (that is depending on whether it is
+ * in compatibility mode).
+ */
+ public T newInstance(Resources res) {
+ return newInstance();
+ }
+
+ /**
+ * Create a new instance from its constant state. This must be
+ * implemented for resources that can have a theme applied.
+ */
+ public T newInstance(Resources res, Resources.Theme theme) {
+ return newInstance(res);
+ }
+}
diff --git a/src/api-impl/android/content/res/GradientColor.java b/src/api-impl/android/content/res/GradientColor.java
new file mode 100644
index 00000000..90808526
--- /dev/null
+++ b/src/api-impl/android/content/res/GradientColor.java
@@ -0,0 +1,512 @@
+/*
+ * Copyright (C) 2016 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.ColorInt;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.Resources.Theme;
+import android.graphics.LinearGradient;
+import android.graphics.RadialGradient;
+import android.graphics.Shader;
+import android.graphics.SweepGradient;
+import android.graphics.drawable.GradientDrawable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Xml;
+import com.android.internal.R;
+import com.android.internal.util.GrowingArrayUtils;
+import java.io.IOException;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+public class GradientColor extends ComplexColor {
+ private static final String TAG = "GradientColor";
+
+ private static final boolean DBG_GRADIENT = false;
+
+ /**
+ * Lazily-created factory for this GradientColor.
+ */
+ private GradientColorFactory mFactory;
+
+ private int mChangingConfigurations;
+ private int mDefaultColor;
+
+ // After parsing all the attributes from XML, this shader is the ultimate result containing
+ // all the XML information.
+ private Shader mShader = null;
+
+ // Below are the attributes at the root element
+ private int mGradientType = GradientDrawable.LINEAR_GRADIENT;
+
+ private float mCenterX = 0f;
+ private float mCenterY = 0f;
+
+ private float mStartX = 0f;
+ private float mStartY = 0f;
+ private float mEndX = 0f;
+ private float mEndY = 0f;
+
+ private int mStartColor = 0;
+ private int mCenterColor = 0;
+ private int mEndColor = 0;
+ private boolean mHasCenterColor = false;
+
+ private float mGradientRadius = 0f;
+
+ // Below are the attributes for the - element.
+ private int[] mItemColors;
+ private float[] mItemOffsets;
+
+ // Theme attributes for the root and item elements.
+ private int[] mThemeAttrs;
+ private int[][] mItemsThemeAttrs;
+
+ private GradientColor() {
+ }
+
+ private GradientColor(GradientColor copy) {
+ if (copy != null) {
+ mChangingConfigurations = copy.mChangingConfigurations;
+ mDefaultColor = copy.mDefaultColor;
+ mShader = copy.mShader;
+ mGradientType = copy.mGradientType;
+ mCenterX = copy.mCenterX;
+ mCenterY = copy.mCenterY;
+ mStartX = copy.mStartX;
+ mStartY = copy.mStartY;
+ mEndX = copy.mEndX;
+ mEndY = copy.mEndY;
+ mStartColor = copy.mStartColor;
+ mCenterColor = copy.mCenterColor;
+ mEndColor = copy.mEndColor;
+ mHasCenterColor = copy.mHasCenterColor;
+ mGradientRadius = copy.mGradientRadius;
+
+ if (copy.mItemColors != null) {
+ mItemColors = copy.mItemColors.clone();
+ }
+ if (copy.mItemOffsets != null) {
+ mItemOffsets = copy.mItemOffsets.clone();
+ }
+
+ if (copy.mThemeAttrs != null) {
+ mThemeAttrs = copy.mThemeAttrs.clone();
+ }
+ if (copy.mItemsThemeAttrs != null) {
+ mItemsThemeAttrs = copy.mItemsThemeAttrs.clone();
+ }
+ }
+ }
+
+ /**
+ * Update the root level's attributes, either for inflate or applyTheme.
+ */
+ private void updateRootElementState(TypedArray a) {
+ // Extract the theme attributes, if any.
+ mThemeAttrs = a.extractThemeAttrs();
+
+ mStartX = a.getFloat(
+ R.styleable.GradientColor_startX, mStartX);
+ mStartY = a.getFloat(
+ R.styleable.GradientColor_startY, mStartY);
+ mEndX = a.getFloat(
+ R.styleable.GradientColor_endX, mEndX);
+ mEndY = a.getFloat(
+ R.styleable.GradientColor_endY, mEndY);
+
+ mCenterX = a.getFloat(
+ R.styleable.GradientColor_centerX, mCenterX);
+ mCenterY = a.getFloat(
+ R.styleable.GradientColor_centerY, mCenterY);
+
+ mGradientType = a.getInt(
+ R.styleable.GradientColor_type, mGradientType);
+
+ mStartColor = a.getColor(
+ R.styleable.GradientColor_startColor, mStartColor);
+ mHasCenterColor |= a.hasValue(
+ R.styleable.GradientColor_centerColor);
+ mCenterColor = a.getColor(
+ R.styleable.GradientColor_centerColor, mCenterColor);
+ mEndColor = a.getColor(
+ R.styleable.GradientColor_endColor, mEndColor);
+
+ if (DBG_GRADIENT) {
+ Log.v(TAG, "hasCenterColor is " + mHasCenterColor);
+ if (mHasCenterColor) {
+ Log.v(TAG, "centerColor:" + mCenterColor);
+ }
+ Log.v(TAG, "startColor: " + mStartColor);
+ Log.v(TAG, "endColor: " + mEndColor);
+ }
+
+ mGradientRadius = a.getFloat(R.styleable.GradientColor_gradientRadius,
+ mGradientRadius);
+ }
+
+ /**
+ * Check if the XML content is valid.
+ *
+ * @throws XmlPullParserException if errors were found.
+ */
+ private void validateXmlContent() throws XmlPullParserException {
+ if (mGradientRadius <= 0 && mGradientType == GradientDrawable.RADIAL_GRADIENT) {
+ throw new XmlPullParserException(
+ " tag requires 'gradientRadius' "
+ + "attribute with radial type");
+ }
+ }
+
+ /**
+ * The shader information will be applied to the native VectorDrawable's path.
+ * @hide
+ */
+ public Shader getShader() {
+ return mShader;
+ }
+
+ /**
+ * A public method to create GradientColor from a XML resource.
+ */
+ public static GradientColor createFromXml(Resources r, XmlResourceParser parser, Theme theme)
+ throws XmlPullParserException, IOException {
+ final AttributeSet attrs = Xml.asAttributeSet(parser);
+
+ int type;
+ while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) {
+ // Seek parser to start tag.
+ }
+
+ if (type != XmlPullParser.START_TAG) {
+ throw new XmlPullParserException("No start tag found");
+ }
+
+ return createFromXmlInner(r, parser, attrs, theme);
+ }
+
+ /**
+ * Create from inside an XML document. Called on a parser positioned at a
+ * tag in an XML document, tries to create a GradientColor from that tag.
+ *
+ * @return A new GradientColor for the current tag.
+ * @throws XmlPullParserException if the current tag is not <gradient>
+ */
+ @NonNull
+ static GradientColor createFromXmlInner(@NonNull Resources r,
+ @NonNull XmlPullParser parser, @NonNull AttributeSet attrs, @Nullable Theme theme)
+ throws XmlPullParserException, IOException {
+ final String name = parser.getName();
+ if (!name.equals("gradient")) {
+ throw new XmlPullParserException(
+ parser.getPositionDescription() + ": invalid gradient color tag " + name);
+ }
+
+ final GradientColor gradientColor = new GradientColor();
+ gradientColor.inflate(r, parser, attrs, theme);
+ return gradientColor;
+ }
+
+ /**
+ * Fill in this object based on the contents of an XML "gradient" element.
+ */
+ private void inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+ @NonNull AttributeSet attrs, @Nullable Theme theme)
+ throws XmlPullParserException, IOException {
+ final TypedArray a = Resources.obtainAttributes(r, theme, attrs, R.styleable.GradientColor);
+ updateRootElementState(a);
+ mChangingConfigurations |= a.getChangingConfigurations();
+ a.recycle();
+
+ // Check correctness and throw exception if errors found.
+ validateXmlContent();
+
+ inflateChildElements(r, parser, attrs, theme);
+
+ onColorsChange();
+ }
+
+ /**
+ * Inflates child elements "item"s for each color stop.
+ *
+ * Note that at root level, we need to save ThemeAttrs for theme applied later.
+ * Here similarly, at each child item, we need to save the theme's attributes, and apply theme
+ * later as applyItemsAttrsTheme().
+ */
+ private void inflateChildElements(@NonNull Resources r, @NonNull XmlPullParser parser,
+ @NonNull AttributeSet attrs, @NonNull Theme theme)
+ throws XmlPullParserException, IOException {
+ final int innerDepth = parser.getDepth() + 1;
+ int type;
+ int depth;
+
+ // Pre-allocate the array with some size, for better performance.
+ float[] offsetList = new float[20];
+ int[] colorList = new int[offsetList.length];
+ int[][] themeAttrsList = new int[offsetList.length][];
+
+ int listSize = 0;
+ boolean hasUnresolvedAttrs = false;
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
+ if (type != XmlPullParser.START_TAG) {
+ continue;
+ }
+ if (depth > innerDepth || !parser.getName().equals("item")) {
+ continue;
+ }
+
+ final TypedArray a = Resources.obtainAttributes(r, theme, attrs,
+ R.styleable.GradientColorItem);
+ boolean hasColor = a.hasValue(R.styleable.GradientColorItem_color);
+ boolean hasOffset = a.hasValue(R.styleable.GradientColorItem_offset);
+ if (!hasColor || !hasOffset) {
+ throw new XmlPullParserException(
+ parser.getPositionDescription() + ":
- tag requires a 'color' attribute and a 'offset' "
+ + "attribute!");
+ }
+
+ final int[] themeAttrs = a.extractThemeAttrs();
+ int color = a.getColor(R.styleable.GradientColorItem_color, 0);
+ float offset = a.getFloat(R.styleable.GradientColorItem_offset, 0);
+
+ if (DBG_GRADIENT) {
+ Log.v(TAG, "new item color " + color + " " + Integer.toHexString(color));
+ Log.v(TAG, "offset" + offset);
+ }
+ mChangingConfigurations |= a.getChangingConfigurations();
+ a.recycle();
+
+ if (themeAttrs != null) {
+ hasUnresolvedAttrs = true;
+ }
+
+ colorList = GrowingArrayUtils.append(colorList, listSize, color);
+ offsetList = GrowingArrayUtils.append(offsetList, listSize, offset);
+ themeAttrsList = GrowingArrayUtils.append(themeAttrsList, listSize, themeAttrs);
+ listSize++;
+ }
+ if (listSize > 0) {
+ if (hasUnresolvedAttrs) {
+ mItemsThemeAttrs = new int[listSize][];
+ System.arraycopy(themeAttrsList, 0, mItemsThemeAttrs, 0, listSize);
+ } else {
+ mItemsThemeAttrs = null;
+ }
+
+ mItemColors = new int[listSize];
+ mItemOffsets = new float[listSize];
+ System.arraycopy(colorList, 0, mItemColors, 0, listSize);
+ System.arraycopy(offsetList, 0, mItemOffsets, 0, listSize);
+ }
+ }
+
+ /**
+ * Apply theme to all the items.
+ */
+ private void applyItemsAttrsTheme(Theme t) {
+ if (mItemsThemeAttrs == null) {
+ return;
+ }
+
+ boolean hasUnresolvedAttrs = false;
+
+ final int[][] themeAttrsList = mItemsThemeAttrs;
+ final int N = themeAttrsList.length;
+ for (int i = 0; i < N; i++) {
+ if (themeAttrsList[i] != null) {
+ final TypedArray a = t.resolveAttributes(themeAttrsList[i],
+ R.styleable.GradientColorItem);
+
+ // Extract the theme attributes, if any, before attempting to
+ // read from the typed array. This prevents a crash if we have
+ // unresolved attrs.
+ themeAttrsList[i] = a.extractThemeAttrs(themeAttrsList[i]);
+ if (themeAttrsList[i] != null) {
+ hasUnresolvedAttrs = true;
+ }
+
+ mItemColors[i] = a.getColor(R.styleable.GradientColorItem_color, mItemColors[i]);
+ mItemOffsets[i] = a.getFloat(R.styleable.GradientColorItem_offset, mItemOffsets[i]);
+ if (DBG_GRADIENT) {
+ Log.v(TAG, "applyItemsAttrsTheme Colors[i] " + i + " " +
+ Integer.toHexString(mItemColors[i]));
+ Log.v(TAG, "Offsets[i] " + i + " " + mItemOffsets[i]);
+ }
+
+ // Account for any configuration changes.
+ mChangingConfigurations |= a.getChangingConfigurations();
+
+ a.recycle();
+ }
+ }
+
+ if (!hasUnresolvedAttrs) {
+ mItemsThemeAttrs = null;
+ }
+ }
+
+ private void onColorsChange() {
+ int[] tempColors = null;
+ float[] tempOffsets = null;
+
+ if (mItemColors != null) {
+ int length = mItemColors.length;
+ tempColors = new int[length];
+ tempOffsets = new float[length];
+
+ for (int i = 0; i < length; i++) {
+ tempColors[i] = mItemColors[i];
+ tempOffsets[i] = mItemOffsets[i];
+ }
+ } else {
+ if (mHasCenterColor) {
+ tempColors = new int[3];
+ tempColors[0] = mStartColor;
+ tempColors[1] = mCenterColor;
+ tempColors[2] = mEndColor;
+
+ tempOffsets = new float[3];
+ tempOffsets[0] = 0.0f;
+ // Since 0.5f is default value, try to take the one that isn't 0.5f
+ tempOffsets[1] = 0.5f;
+ tempOffsets[2] = 1f;
+ } else {
+ tempColors = new int[2];
+ tempColors[0] = mStartColor;
+ tempColors[1] = mEndColor;
+ }
+ }
+ if (tempColors.length < 2) {
+ Log.w(TAG, " tag requires 2 color values specified!" + tempColors.length + " " + tempColors);
+ }
+
+ if (mGradientType == GradientDrawable.LINEAR_GRADIENT) {
+ mShader = new LinearGradient(mStartX, mStartY, mEndX, mEndY, tempColors, tempOffsets,
+ Shader.TileMode.CLAMP);
+ } else {
+ if (mGradientType == GradientDrawable.RADIAL_GRADIENT) {
+ mShader = new RadialGradient(mCenterX, mCenterY, mGradientRadius, tempColors,
+ tempOffsets, Shader.TileMode.CLAMP);
+ } else {
+ mShader = new SweepGradient(mCenterX, mCenterY, tempColors, tempOffsets);
+ }
+ }
+ mDefaultColor = tempColors[0];
+ }
+
+ /**
+ * For Gradient color, the default color is not very useful, since the gradient will override
+ * the color information anyway.
+ */
+ @Override
+ //@ColorInt
+ public int getDefaultColor() {
+ return mDefaultColor;
+ }
+
+ /**
+ * Similar to ColorStateList, setup constant state and its factory.
+ * @hide only for resource preloading
+ */
+ @Override
+ public ConstantState getConstantState() {
+ if (mFactory == null) {
+ mFactory = new GradientColorFactory(this);
+ }
+ return mFactory;
+ }
+
+ private static class GradientColorFactory extends ConstantState {
+ private final GradientColor mSrc;
+
+ public GradientColorFactory(GradientColor src) {
+ mSrc = src;
+ }
+
+ @Override
+ public int getChangingConfigurations() {
+ return mSrc.mChangingConfigurations;
+ }
+
+ @Override
+ public GradientColor newInstance() {
+ return mSrc;
+ }
+
+ @Override
+ public GradientColor newInstance(Resources res, Theme theme) {
+ return mSrc.obtainForTheme(theme);
+ }
+ }
+
+ /**
+ * Returns an appropriately themed gradient color.
+ *
+ * @param t the theme to apply
+ * @return a copy of the gradient color the theme applied, or the
+ * gradient itself if there were no unresolved theme
+ * attributes
+ * @hide only for resource preloading
+ */
+ @Override
+ public GradientColor obtainForTheme(Theme t) {
+ if (t == null || !canApplyTheme()) {
+ return this;
+ }
+
+ final GradientColor clone = new GradientColor(this);
+ clone.applyTheme(t);
+ return clone;
+ }
+
+ private void applyTheme(Theme t) {
+ if (mThemeAttrs != null) {
+ applyRootAttrsTheme(t);
+ }
+ if (mItemsThemeAttrs != null) {
+ applyItemsAttrsTheme(t);
+ }
+ onColorsChange();
+ }
+
+ private void applyRootAttrsTheme(Theme t) {
+ final TypedArray a = t.resolveAttributes(mThemeAttrs, R.styleable.GradientColor);
+ // mThemeAttrs will be set to null if if there are no theme attributes in the
+ // typed array.
+ mThemeAttrs = a.extractThemeAttrs(mThemeAttrs);
+ // merging the attributes update inside the updateRootElementState().
+ updateRootElementState(a);
+
+ // Account for any configuration changes.
+ mChangingConfigurations |= a.getChangingConfigurations();
+ a.recycle();
+ }
+
+ /**
+ * Returns whether a theme can be applied to this gradient color, which
+ * usually indicates that the gradient color has unresolved theme
+ * attributes.
+ *
+ * @return whether a theme can be applied to this gradient color.
+ * @hide only for resource preloading
+ */
+ @Override
+ public boolean canApplyTheme() {
+ return mThemeAttrs != null || mItemsThemeAttrs != null;
+ }
+}
diff --git a/src/api-impl/android/content/res/Resources.java b/src/api-impl/android/content/res/Resources.java
index 492695c6..099c66d1 100644
--- a/src/api-impl/android/content/res/Resources.java
+++ b/src/api-impl/android/content/res/Resources.java
@@ -16,6 +16,8 @@
package android.content.res;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.icu.text.PluralRules;
@@ -33,6 +35,9 @@ import android.util.Log;
import android.util.LongSparseArray;
import android.util.Slog;
import android.util.TypedValue;
+import android.util.Xml;
+import android.util.Pools.SynchronizedPool;
+import com.android.internal.util.GrowingArrayUtils;
import com.android.internal.util.XmlUtils;
// import android.view.DisplayAdjustments;
import java.io.IOException;
@@ -91,7 +96,10 @@ public class Resources {
// single-threaded, and after that these are immutable.
private static final LongSparseArray[] sPreloadedDrawables;
private static final LongSparseArray sPreloadedColorDrawables = new LongSparseArray();
- private static final LongSparseArray sPreloadedColorStateLists = new LongSparseArray();
+ private static final LongSparseArray> sPreloadedComplexColors = new LongSparseArray<>();
+
+ // Pool of TypedArrays targeted to this Resources object.
+ final SynchronizedPool mTypedArrayPool = new SynchronizedPool<>(5);
private static boolean sPreloaded;
private static int sPreloadedDensity;
@@ -102,7 +110,7 @@ public class Resources {
/*package*/ final Configuration mTmpConfig = new Configuration();
/*package*/ TypedValue mTmpValue = new TypedValue();
/*package*/ final Map> mDrawableCache = new HashMap>(0);
- /*package*/ final LongSparseArray> mColorStateListCache = new LongSparseArray>(0);
+ private final ConfigurationBoundResourceCache mComplexColorCache = new ConfigurationBoundResourceCache<>(this);
/*package*/ final LongSparseArray> mColorDrawableCache = new LongSparseArray>(0);
/*package*/ boolean mPreloading;
@@ -807,6 +815,10 @@ public class Resources {
* @return Returns a single color value in the form 0xAARRGGBB.
*/
public int getColor(int id) throws NotFoundException {
+ return getColor(id, null);
+ }
+
+ public int getColor(int id, Theme theme) throws NotFoundException {
TypedValue value;
synchronized (mAccessLock) {
value = mTmpValue;
@@ -823,7 +835,7 @@ public class Resources {
}
mTmpValue = null;
}
- ColorStateList csl = loadColorStateList(value, id);
+ ColorStateList csl = loadColorStateList(value, id, theme);
synchronized (mAccessLock) {
if (mTmpValue == null) {
mTmpValue = value;
@@ -832,11 +844,6 @@ public class Resources {
return csl.getDefaultColor();
}
- public int getColor(int id, Theme theme) throws NotFoundException {
- // TODO fix it
- return 0;
- }
-
/**
* Return a color state list associated with a particular resource ID. The
* resource may contain either a single raw color value, or a complex
@@ -867,7 +874,7 @@ public class Resources {
}
getValue(id, value, true);
}
- ColorStateList res = loadColorStateList(value, id);
+ ColorStateList res = loadColorStateList(value, id, theme);
synchronized (mAccessLock) {
if (mTmpValue == null) {
mTmpValue = value;
@@ -1418,6 +1425,36 @@ public class Resources {
return array;
}
+ /**
+ * Retrieve the values for a set of attributes in the Theme. The
+ * contents of the typed array are ultimately filled in by
+ * {@link Resources#getValue}.
+ *
+ * @param values The base set of attribute values, must be equal in
+ * length to {@code attrs}. All values must be of type
+ * {@link TypedValue#TYPE_ATTRIBUTE}.
+ * @param attrs The desired attributes to be retrieved.
+ * @return Returns a TypedArray holding an array of the attribute
+ * values. Be sure to call {@link TypedArray#recycle()}
+ * when done with it.
+ * @hide
+ */
+ @NonNull
+ public TypedArray resolveAttributes(@NonNull int[] values, @NonNull int[] attrs) {
+ final int len = attrs.length;
+ if (values == null || len != values.length) {
+ throw new IllegalArgumentException(
+ "Base attribute values must the same length as attrs");
+ }
+
+ final TypedArray array = TypedArray.obtain(Resources.this, len);
+ AssetManager.resolveAttrs(theme, 0, 0, values, attrs, array.mData, array.mIndices);
+ array.mTheme = this;
+ array.mXml = null;
+
+ return array;
+ }
+
/**
* Retrieve the value of an attribute in the Theme. The contents of
* outValue are ultimately filled in by
@@ -1461,6 +1498,8 @@ public class Resources {
mAssets.releaseTheme(theme);
}
+ private final ThemeKey mKey = new ThemeKey();
+
/*package*/ Theme() {
mAssets = Resources.this.mAssets;
theme = mAssets.createTheme();
@@ -1470,11 +1509,93 @@ public class Resources {
return Resources.this;
}
+ /*package*/ ThemeKey getKey() {
+ return mKey;
+ }
+
private final AssetManager mAssets;
public void rebase() {}
}
+ static class ThemeKey implements Cloneable {
+ int[] mResId;
+ boolean[] mForce;
+ int mCount;
+
+ private int mHashCode = 0;
+
+ public void append(int resId, boolean force) {
+ if (mResId == null) {
+ mResId = new int[4];
+ }
+
+ if (mForce == null) {
+ mForce = new boolean[4];
+ }
+
+ mResId = GrowingArrayUtils.append(mResId, mCount, resId);
+ mForce = GrowingArrayUtils.append(mForce, mCount, force);
+ mCount++;
+
+ mHashCode = 31 * (31 * mHashCode + resId) + (force ? 1 : 0);
+ }
+
+ /**
+ * Sets up this key as a deep copy of another key.
+ *
+ * @param other the key to deep copy into this key
+ */
+ public void setTo(ThemeKey other) {
+ mResId = other.mResId == null ? null : other.mResId.clone();
+ mForce = other.mForce == null ? null : other.mForce.clone();
+ mCount = other.mCount;
+ }
+
+ @Override
+ public int hashCode() {
+ return mHashCode;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (o == null || getClass() != o.getClass() || hashCode() != o.hashCode()) {
+ return false;
+ }
+
+ final ThemeKey t = (ThemeKey)o;
+ if (mCount != t.mCount) {
+ return false;
+ }
+
+ final int N = mCount;
+ for (int i = 0; i < N; i++) {
+ if (mResId[i] != t.mResId[i] || mForce[i] != t.mForce[i]) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * @return a shallow copy of this key
+ */
+ @Override
+ public ThemeKey clone() {
+ final ThemeKey other = new ThemeKey();
+ other.mResId = mResId;
+ other.mForce = mForce;
+ other.mCount = mCount;
+ other.mHashCode = mHashCode;
+ return other;
+ }
+ }
+
/**
* Generate a new Theme object for this set of Resources. It initially
* starts out empty.
@@ -1615,7 +1736,7 @@ public class Resources {
// clearDrawableCacheLocked(mDrawableCache, configChanges);
// clearDrawableCacheLocked(mColorDrawableCache, configChanges);
- mColorStateListCache.clear();
+ mComplexColorCache.onConfigurationChange(configChanges);
flushLayoutCache();
}
@@ -2023,8 +2144,11 @@ public class Resources {
static private final int LAYOUT_DIR_CONFIG = ActivityInfo.activityInfoConfigToNative(
ActivityInfo.CONFIG_LAYOUT_DIRECTION);
- /*package*/ Drawable loadDrawable(TypedValue value, int id)
- throws NotFoundException { /*
+ /*package*/ Drawable loadDrawable(TypedValue value, int id) throws NotFoundException {
+ return loadDrawable(value, id, null);
+ }
+
+ /*package*/ Drawable loadDrawable(TypedValue value, int id, Theme theme) throws NotFoundException { /*
if (TRACE_FOR_PRELOAD) {
// Log only framework resources
@@ -2189,7 +2313,80 @@ public class Resources {
return null;
}
- /*package*/ ColorStateList loadColorStateList(TypedValue value, int id)
+ /**
+ * Given the value and id, we can get the XML filename as in value.data, based on that, we
+ * first try to load CSL from the cache. If not found, try to get from the constant state.
+ * Last, parse the XML and generate the CSL.
+ */
+ private ComplexColor loadComplexColorFromName(Theme theme, TypedValue value, int id) {
+ final long key = (((long)value.assetCookie) << 32) | value.data;
+ final ConfigurationBoundResourceCache cache = mComplexColorCache;
+ ComplexColor complexColor = cache.getInstance(key, theme);
+ if (complexColor != null) {
+ return complexColor;
+ }
+
+ final android.content.res.ConstantState factory = sPreloadedComplexColors.get(key);
+
+ if (factory != null) {
+ complexColor = factory.newInstance(this, theme);
+ }
+ if (complexColor == null) {
+ complexColor = loadComplexColorForCookie(value, id, theme);
+ }
+
+ if (complexColor != null) {
+ if (mPreloading) {
+ if (verifyPreloadConfig(value.changingConfigurations, 0, value.resourceId, "color")) {
+ sPreloadedComplexColors.put(key, complexColor.getConstantState());
+ }
+ } else {
+ cache.put(key, theme, complexColor.getConstantState());
+ }
+ }
+ return complexColor;
+ }
+
+ @Nullable
+ public ComplexColor loadComplexColor(@NonNull TypedValue value, int id, Theme theme) {
+ if (TRACE_FOR_PRELOAD) {
+ // Log only framework resources
+ if ((id >>> 24) == 0x1) {
+ final String name = getResourceName(id);
+ if (name != null)
+ android.util.Log.d("loadComplexColor", name);
+ }
+ }
+
+ final long key = (((long)value.assetCookie) << 32) | value.data;
+
+ // Handle inline color definitions.
+ if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT && value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
+ return getColorStateListFromInt(value, key);
+ }
+
+ final String file = value.string.toString();
+
+ ComplexColor complexColor;
+ if (file.endsWith(".xml")) {
+ try {
+ complexColor = loadComplexColorFromName(theme, value, id);
+ } catch (Exception e) {
+ final NotFoundException rnf = new NotFoundException(
+ "File " + file + " from complex color resource ID #0x" + Integer.toHexString(id));
+ rnf.initCause(e);
+ throw rnf;
+ }
+ } else {
+ throw new NotFoundException(
+ "File " + file + " from drawable resource ID #0x" + Integer.toHexString(id) + ": .xml extension required");
+ }
+
+ return complexColor;
+ }
+
+ @Nullable
+ ColorStateList loadColorStateList(TypedValue value, int id, Theme theme)
throws NotFoundException {
if (TRACE_FOR_PRELOAD) {
// Log only framework resources
@@ -2202,99 +2399,112 @@ public class Resources {
final long key = (((long)value.assetCookie) << 32) | value.data;
+ // Handle inline color definitions.
+ if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT && value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
+ return getColorStateListFromInt(value, key);
+ }
+
+ ComplexColor complexColor = loadComplexColorFromName(theme, value, id);
+ if (complexColor != null && complexColor instanceof ColorStateList) {
+ return (ColorStateList)complexColor;
+ }
+
+ throw new NotFoundException(
+ "Can't find ColorStateList from drawable resource ID #0x" + Integer.toHexString(id));
+ }
+
+ @NonNull
+ private ColorStateList getColorStateListFromInt(@NonNull TypedValue value, long key) {
ColorStateList csl;
-
- if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT &&
- value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
-
- csl = sPreloadedColorStateLists.get(key);
- if (csl != null) {
- return csl;
- }
-
- csl = ColorStateList.valueOf(value.data);
- if (mPreloading) {
- if (verifyPreloadConfig(value.changingConfigurations, 0, value.resourceId,
- "color")) {
- sPreloadedColorStateLists.put(key, csl);
- }
- }
-
- return csl;
+ final android.content.res.ConstantState factory =
+ sPreloadedComplexColors.get(key);
+ if (factory != null) {
+ return (ColorStateList)factory.newInstance();
}
- csl = getCachedColorStateList(key);
- if (csl != null) {
- return csl;
- }
+ csl = ColorStateList.valueOf(value.data);
- csl = sPreloadedColorStateLists.get(key);
- if (csl != null) {
- return csl;
- }
-
- if (value.string == null) {
- throw new NotFoundException(
- "Resource is not a ColorStateList (color or path): " + value);
- }
-
- String file = value.string.toString();
-
- if (file.endsWith(".xml")) {
- Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
- try {
- XmlResourceParser rp = loadXmlResourceParser(
- file, id, value.assetCookie, "colorstatelist");
- csl = ColorStateList.createFromXml(this, rp);
- rp.close();
- } catch (Exception e) {
- Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
- NotFoundException rnf = new NotFoundException(
- "File " + file + " from color state list resource ID #0x" + Integer.toHexString(id));
- rnf.initCause(e);
- throw rnf;
- }
- Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
- } else {
- throw new NotFoundException(
- "File " + file + " from drawable resource ID #0x" + Integer.toHexString(id) + ": .xml extension required");
- }
-
- if (csl != null) {
- if (mPreloading) {
- if (verifyPreloadConfig(value.changingConfigurations, 0, value.resourceId,
- "color")) {
- sPreloadedColorStateLists.put(key, csl);
- }
- } else {
- synchronized (mAccessLock) {
- // Log.i(TAG, "Saving cached color state list @ #" +
- // Integer.toHexString(key.intValue())
- // + " in " + this + ": " + csl);
- mColorStateListCache.put(key, new WeakReference(csl));
- }
+ if (mPreloading) {
+ if (verifyPreloadConfig(value.changingConfigurations, 0, value.resourceId,
+ "color")) {
+ sPreloadedComplexColors.put(key, csl.getConstantState());
}
}
return csl;
}
- private ColorStateList getCachedColorStateList(long key) {
- synchronized (mAccessLock) {
- WeakReference wr = mColorStateListCache.get(key);
- if (wr != null) { // we have the key
- ColorStateList entry = wr.get();
- if (entry != null) {
- // Log.i(TAG, "Returning cached color state list @ #" +
- // Integer.toHexString(((Integer)key).intValue())
- // + " in " + this + ": " + entry);
- return entry;
- } else { // our entry has been purged
- mColorStateListCache.delete(key);
+ /**
+ * Load a ComplexColor based on the XML file content. The result can be a GradientColor or
+ * ColorStateList. Note that pure color will be wrapped into a ColorStateList.
+ *
+ * We deferred the parser creation to this function b/c we need to differentiate b/t gradient
+ * and selector tag.
+ *
+ * @return a ComplexColor (GradientColor or ColorStateList) based on the XML file content.
+ */
+ @Nullable
+ private ComplexColor loadComplexColorForCookie(TypedValue value, int id, Theme theme) {
+ if (value.string == null) {
+ throw new UnsupportedOperationException(
+ "Can't convert to ComplexColor: type=0x" + value.type);
+ }
+
+ final String file = value.string.toString();
+
+ if (TRACE_FOR_MISS_PRELOAD) {
+ // Log only framework resources
+ if ((id >>> 24) == 0x1) {
+ final String name = getResourceName(id);
+ if (name != null) {
+ Log.d(TAG, "Loading framework ComplexColor #" + Integer.toHexString(id) + ": " + name + " at " + file);
}
}
}
- return null;
+
+ if (DEBUG_LOAD) {
+ Log.v(TAG, "Loading ComplexColor for cookie " + value.assetCookie + ": " + file);
+ }
+
+ ComplexColor complexColor = null;
+
+ Trace.traceBegin(Trace.TRACE_TAG_RESOURCES, file);
+ if (file.endsWith(".xml")) {
+ try {
+ final XmlResourceParser parser = loadXmlResourceParser(
+ file, id, value.assetCookie, "ComplexColor");
+
+ final AttributeSet attrs = Xml.asAttributeSet(parser);
+ int type;
+ while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) {
+ // Seek parser to start tag.
+ }
+ if (type != XmlPullParser.START_TAG) {
+ throw new XmlPullParserException("No start tag found");
+ }
+
+ final String name = parser.getName();
+ if (name.equals("gradient")) {
+ complexColor = GradientColor.createFromXmlInner(this, parser, attrs, theme);
+ } else if (name.equals("selector")) {
+ complexColor = ColorStateList.createFromXmlInner(this, parser, attrs, theme);
+ }
+ parser.close();
+ } catch (Exception e) {
+ Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
+ final NotFoundException rnf = new NotFoundException(
+ "File " + file + " from ComplexColor resource ID #0x" + Integer.toHexString(id));
+ rnf.initCause(e);
+ throw rnf;
+ }
+ } else {
+ Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
+ throw new NotFoundException(
+ "File " + file + " from drawable resource ID #0x" + Integer.toHexString(id) + ": .xml extension required");
+ }
+ Trace.traceEnd(Trace.TRACE_TAG_RESOURCES);
+
+ return complexColor;
}
/*package*/ XmlResourceParser loadXmlResourceParser(int id, String type)
@@ -2321,95 +2531,111 @@ public class Resources {
} catch (IOException e) {
throw new RuntimeException(e);
}
-/* if (id != 0) {
- try {
- // These may be compiled...
- synchronized (mCachedXmlBlockIds) {
- // First see if this block is in our cache.
- final int num = mCachedXmlBlockIds.length;
- for (int i=0; i= fullLen) {
+ attrs.mLength = len;
+ int fullLen = len * AssetManager.STYLE_NUM_ENTRIES;
+ if (attrs.mData.length >= fullLen) {
+ return attrs;
+ }
+ attrs.mData = new int[fullLen];
+ attrs.mIndices = new int[1 + len];
return attrs;
}
- attrs.mData = new int[fullLen];
- attrs.mIndices = new int[1 + len];
- return attrs;
- }
- if (DEBUG_ATTRIBUTES_CACHE) {
- RuntimeException here = new RuntimeException("here");
- here.fillInStackTrace();
- if (mLastRetrievedAttrs != null) {
- Log.i(TAG, "Allocated new TypedArray of " + len + " in " + this, here);
- Log.i(TAG, "Last retrieved attributes here", mLastRetrievedAttrs);
+ if (DEBUG_ATTRIBUTES_CACHE) {
+ RuntimeException here = new RuntimeException("here");
+ here.fillInStackTrace();
+ if (mLastRetrievedAttrs != null) {
+ Log.i(TAG, "Allocated new TypedArray of " + len + " in " + this, here);
+ Log.i(TAG, "Last retrieved attributes here", mLastRetrievedAttrs);
+ }
+ mLastRetrievedAttrs = here;
}
- mLastRetrievedAttrs = here;
+ return new TypedArray(this,
+ new int[len * AssetManager.STYLE_NUM_ENTRIES],
+ new int[1 + len], len);
}
- return new TypedArray(this,
- new int[len * AssetManager.STYLE_NUM_ENTRIES],
- new int[1 + len], len);
+ }
+
+
+ /**
+ * Obtains styled attributes from the theme, if available, or unstyled
+ * resources if the theme is null.
+ *
+ * @hide
+ */
+ public static TypedArray obtainAttributes(
+ Resources res, Theme theme, AttributeSet set, int[] attrs) {
+ if (theme == null) {
+ return res.obtainAttributes(set, attrs);
+ }
+
+ return theme.obtainStyledAttributes(set, attrs, 0, 0);
+ }
+
+ private Resources() {
+ mAssets = AssetManager.getSystem();
+ // NOTE: Intentionally leaving this uninitialized (all values set
+ // to zero), so that anyone who tries to do something that requires
+ // metrics will get a very wrong value.
+ mConfiguration.setToDefaults();
+ mMetrics.setToDefaults();
+ updateConfiguration(null, null);
+ mAssets.ensureStringBlocks();
}
}
-
-private Resources() {
- mAssets = AssetManager.getSystem();
- // NOTE: Intentionally leaving this uninitialized (all values set
- // to zero), so that anyone who tries to do something that requires
- // metrics will get a very wrong value.
- mConfiguration.setToDefaults();
- mMetrics.setToDefaults();
- updateConfiguration(null, null);
- mAssets.ensureStringBlocks();
-}
-}
diff --git a/src/api-impl/android/content/res/ThemedResourceCache.java b/src/api-impl/android/content/res/ThemedResourceCache.java
new file mode 100644
index 00000000..e4a9e7c7
--- /dev/null
+++ b/src/api-impl/android/content/res/ThemedResourceCache.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2015 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.NonNull;
+import android.annotation.Nullable;
+import android.content.res.Resources.Theme;
+import android.content.res.Resources.ThemeKey;
+import android.util.ArrayMap;
+import android.util.LongSparseArray;
+import java.lang.ref.WeakReference;
+
+/**
+ * Data structure used for caching data against themes.
+ *
+ * @param type of data to cache
+ */
+abstract class ThemedResourceCache {
+ private ArrayMap>> mThemedEntries;
+ private LongSparseArray> mUnthemedEntries;
+ private LongSparseArray> mNullThemedEntries;
+
+ /**
+ * Adds a new theme-dependent entry to the cache.
+ *
+ * @param key a key that uniquely identifies the entry
+ * @param theme the theme against which this entry was inflated, or
+ * {@code null} if the entry has no theme applied
+ * @param entry the entry to cache
+ */
+ public void put(long key, @Nullable Theme theme, @NonNull T entry) {
+ put(key, theme, entry, true);
+ }
+
+ /**
+ * Adds a new entry to the cache.
+ *
+ * @param key a key that uniquely identifies the entry
+ * @param theme the theme against which this entry was inflated, or
+ * {@code null} if the entry has no theme applied
+ * @param entry the entry to cache
+ * @param usesTheme {@code true} if the entry is affected theme changes,
+ * {@code false} otherwise
+ */
+ public void put(long key, @Nullable Theme theme, @NonNull T entry, boolean usesTheme) {
+ if (entry == null) {
+ return;
+ }
+
+ synchronized (this) {
+ final LongSparseArray> entries;
+ if (!usesTheme) {
+ entries = getUnthemedLocked(true);
+ } else {
+ entries = getThemedLocked(theme, true);
+ }
+ if (entries != null) {
+ entries.put(key, new WeakReference<>(entry));
+ }
+ }
+ }
+
+ /**
+ * Returns an entry from the cache.
+ *
+ * @param key a key that uniquely identifies the entry
+ * @param theme the theme where the entry will be used
+ * @return a cached entry, or {@code null} if not in the cache
+ */
+ @Nullable
+ public T get(long key, @Nullable Theme theme) {
+ // The themed (includes null-themed) and unthemed caches are mutually
+ // exclusive, so we'll give priority to whichever one we think we'll
+ // hit first. Since most of the framework drawables are themed, that's
+ // probably going to be the themed cache.
+ synchronized (this) {
+ final LongSparseArray> themedEntries = getThemedLocked(theme, false);
+ if (themedEntries != null) {
+ final WeakReference themedEntry = themedEntries.get(key);
+ if (themedEntry != null) {
+ return themedEntry.get();
+ }
+ }
+
+ final LongSparseArray> unthemedEntries = getUnthemedLocked(false);
+ if (unthemedEntries != null) {
+ final WeakReference unthemedEntry = unthemedEntries.get(key);
+ if (unthemedEntry != null) {
+ return unthemedEntry.get();
+ }
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Prunes cache entries that have been invalidated by a configuration
+ * change.
+ *
+ * @param configChanges a bitmask of configuration changes
+ */
+ public void onConfigurationChange(int configChanges) {
+ prune(configChanges);
+ }
+
+ /**
+ * Returns whether a cached entry has been invalidated by a configuration
+ * change.
+ *
+ * @param entry a cached entry
+ * @param configChanges a non-zero bitmask of configuration changes
+ * @return {@code true} if the entry is invalid, {@code false} otherwise
+ */
+ protected abstract boolean shouldInvalidateEntry(@NonNull T entry, int configChanges);
+
+ /**
+ * Returns the cached data for the specified theme, optionally creating a
+ * new entry if one does not already exist.
+ *
+ * @param t the theme for which to return cached data
+ * @param create {@code true} to create an entry if one does not already
+ * exist, {@code false} otherwise
+ * @return the cached data for the theme, or {@code null} if the cache is
+ * empty and {@code create} was {@code false}
+ */
+ @Nullable
+ private LongSparseArray> getThemedLocked(@Nullable Theme t, boolean create) {
+ if (t == null) {
+ if (mNullThemedEntries == null && create) {
+ mNullThemedEntries = new LongSparseArray<>(1);
+ }
+ return mNullThemedEntries;
+ }
+
+ if (mThemedEntries == null) {
+ if (create) {
+ mThemedEntries = new ArrayMap<>(1);
+ } else {
+ return null;
+ }
+ }
+
+ final ThemeKey key = t.getKey();
+ LongSparseArray> cache = mThemedEntries.get(key);
+ if (cache == null && create) {
+ cache = new LongSparseArray<>(1);
+
+ final ThemeKey keyClone = key.clone();
+ mThemedEntries.put(keyClone, cache);
+ }
+
+ return cache;
+ }
+
+ /**
+ * Returns the theme-agnostic cached data.
+ *
+ * @param create {@code true} to create an entry if one does not already
+ * exist, {@code false} otherwise
+ * @return the theme-agnostic cached data, or {@code null} if the cache is
+ * empty and {@code create} was {@code false}
+ */
+ @Nullable
+ private LongSparseArray> getUnthemedLocked(boolean create) {
+ if (mUnthemedEntries == null && create) {
+ mUnthemedEntries = new LongSparseArray<>(1);
+ }
+ return mUnthemedEntries;
+ }
+
+ /**
+ * Prunes cache entries affected by configuration changes or where weak
+ * references have expired.
+ *
+ * @param configChanges a bitmask of configuration changes, or {@code 0} to
+ * simply prune missing weak references
+ * @return {@code true} if the cache is completely empty after pruning
+ */
+ private boolean prune(int configChanges) {
+ synchronized (this) {
+ if (mThemedEntries != null) {
+ for (int i = mThemedEntries.size() - 1; i >= 0; i--) {
+ if (pruneEntriesLocked(mThemedEntries.valueAt(i), configChanges)) {
+ mThemedEntries.removeAt(i);
+ }
+ }
+ }
+
+ pruneEntriesLocked(mNullThemedEntries, configChanges);
+ pruneEntriesLocked(mUnthemedEntries, configChanges);
+
+ return mThemedEntries == null && mNullThemedEntries == null && mUnthemedEntries == null;
+ }
+ }
+
+ private boolean pruneEntriesLocked(@Nullable LongSparseArray> entries,
+ int configChanges) {
+ if (entries == null) {
+ return true;
+ }
+
+ for (int i = entries.size() - 1; i >= 0; i--) {
+ final WeakReference ref = entries.valueAt(i);
+ if (ref == null || pruneEntryLocked(ref.get(), configChanges)) {
+ entries.removeAt(i);
+ }
+ }
+
+ return entries.size() == 0;
+ }
+
+ private boolean pruneEntryLocked(@Nullable T entry, int configChanges) {
+ return entry == null || (configChanges != 0 && shouldInvalidateEntry(entry, configChanges));
+ }
+}
diff --git a/src/api-impl/android/content/res/TypedArray.java b/src/api-impl/android/content/res/TypedArray.java
index c5b6aaa8..5e25b137 100644
--- a/src/api-impl/android/content/res/TypedArray.java
+++ b/src/api-impl/android/content/res/TypedArray.java
@@ -16,13 +16,16 @@
package android.content.res;
+//import android.annotation.AnyRes;
+//import android.annotation.ColorInt;
+import android.annotation.Nullable;
+//import android.annotation.StyleableRes;
import android.graphics.drawable.Drawable;
+import android.os.StrictMode;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
-import android.util.Log;
import android.util.TypedValue;
import com.android.internal.util.XmlUtils;
-
import java.util.Arrays;
/**
@@ -35,57 +38,118 @@ import java.util.Arrays;
* the positions of the attributes given to obtainStyledAttributes.
*/
public class TypedArray {
+
+ static TypedArray obtain(Resources res, int len) {
+ final TypedArray attrs = res.mTypedArrayPool.acquire();
+ if (attrs != null) {
+ attrs.mLength = len;
+ attrs.mRecycled = false;
+
+ final int fullLen = len * AssetManager.STYLE_NUM_ENTRIES;
+ if (attrs.mData.length >= fullLen) {
+ return attrs;
+ }
+
+ attrs.mData = new int[fullLen];
+ attrs.mIndices = new int[1 + len];
+ return attrs;
+ }
+
+ return new TypedArray(res,
+ new int[len * AssetManager.STYLE_NUM_ENTRIES],
+ new int[1 + len], len);
+ }
+
private final Resources mResources;
+ private final DisplayMetrics mMetrics;
+ private final AssetManager mAssets;
+
+ private boolean mRecycled;
+
+ /*package*/ int[] mRsrcs; // TODO update Resources.java to not need this?
+
/*package*/ XmlResourceParser mXml;
- /*package*/ int[] mRsrcs;
+ /*package*/ Resources.Theme mTheme;
/*package*/ int[] mData;
/*package*/ int[] mIndices;
/*package*/ int mLength;
/*package*/ TypedValue mValue = new TypedValue();
/**
- * Return the number of values in this array.
+ * 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;
}
/**
* Return the number of indices in the array that actually have data.
+ *
+ * @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];
}
/**
- * Return an index in the array that has data.
+ * Returns an index in the array that has data.
*
* @param at The index you would like to returned, ranging from 0 to
- * {@link #getIndexCount()}.
+ * {@link #getIndexCount()}.
*
* @return The index at the given offset, which can be used with
- * {@link #getValue} and related APIs.
+ * {@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];
}
/**
- * Return the Resources object this array was loaded from.
+ * 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;
}
/**
- * Retrieve the styled string value for the attribute at index.
+ * Retrieves the styled string value for the attribute at index.
+ *
+ * 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
- * null if the attribute is not defined.
+ * @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(int index) {
+ public CharSequence getText(/*@StyleableRes*/ int index) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
index *= AssetManager.STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index + AssetManager.STYLE_TYPE];
@@ -95,24 +159,34 @@ public class TypedArray {
return loadStringValueAt(index);
}
- TypedValue v = mValue;
+ final TypedValue v = mValue;
if (getValueAt(index, v)) {
- Log.w(Resources.TAG, "Converting to string: " + v);
return v.coerceToString();
}
- Log.w(Resources.TAG, "getString of bad type: 0x" + Integer.toHexString(type));
- return null;
+
+ // We already checked for TYPE_NULL. This should never happen.
+ throw new RuntimeException("getText of bad type: 0x" + Integer.toHexString(type));
}
/**
- * Retrieve the string value for the attribute at index.
+ * Retrieves the string value for the attribute at index.
+ *
+ * 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 null if the attribute is not defined.
+ * @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.
*/
- public String getString(int index) {
+ @Nullable
+ public String getString(/*@StyleableRes*/ int index) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
index *= AssetManager.STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index + AssetManager.STYLE_TYPE];
@@ -122,18 +196,18 @@ public class TypedArray {
return loadStringValueAt(index).toString();
}
- TypedValue v = mValue;
+ final TypedValue v = mValue;
if (getValueAt(index, v)) {
- Log.w(Resources.TAG, "Converting to string: " + v);
- CharSequence cs = v.coerceToString();
+ final CharSequence cs = v.coerceToString();
return cs != null ? cs.toString() : null;
}
- Log.w(Resources.TAG, "getString of bad type: 0x" + Integer.toHexString(type));
- return null;
+
+ // We already checked for TYPE_NULL. This should never happen.
+ throw new RuntimeException("getString of bad type: 0x" + Integer.toHexString(type));
}
/**
- * Retrieve the string value for the attribute at index, but
+ * Retrieves the string value for the attribute at index, 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
@@ -142,38 +216,48 @@ public class TypedArray {
*
* @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 or is not
- * an immediate string value.
+ * @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(int index) {
+ public String getNonResourceString(/*@StyleableRes*/ int index) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
index *= AssetManager.STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index + AssetManager.STYLE_TYPE];
if (type == TypedValue.TYPE_STRING) {
final int cookie = data[index + AssetManager.STYLE_ASSET_COOKIE];
if (cookie < 0) {
- CharSequence string = ((XmlBlock.Parser)mXml).getPooledString(data[index + AssetManager.STYLE_DATA]);
- if (string != null)
- return string.toString();
+ return ((XmlBlock.Parser)mXml).getPooledString(
+ data[index + AssetManager.STYLE_DATA])
+ .toString();
}
}
return null;
}
/**
- * @hide
- * Retrieve the string value for the attribute at index that is
+ * Retrieves the string value for the attribute at index 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.
+ * {@link Configuration}.NATIVE_CONFIG_* that are allowed to change.
*
- * @return String holding string data. Any styling information is
- * removed. Returns null if the attribute is not defined.
+ * @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
*/
- public String getNonConfigurationString(int index, int allowedChangingConfigs) {
+ public String getNonConfigurationString(/*@StyleableRes*/ int index, int allowedChangingConfigs) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
index *= AssetManager.STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index + AssetManager.STYLE_TYPE];
@@ -186,25 +270,37 @@ public class TypedArray {
return loadStringValueAt(index).toString();
}
- TypedValue v = mValue;
+ final TypedValue v = mValue;
if (getValueAt(index, v)) {
- Log.w(Resources.TAG, "Converting to string: " + v);
- CharSequence cs = v.coerceToString();
+ final CharSequence cs = v.coerceToString();
return cs != null ? cs.toString() : null;
}
- Log.w(Resources.TAG, "getString of bad type: 0x" + Integer.toHexString(type));
- return 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 index.
+ *
+ * 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.
+ * @param defValue Value to return if the attribute is not defined or
+ * cannot be coerced to an integer.
*
- * @return Attribute boolean value, or defValue if not defined.
+ * @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(int index, boolean defValue) {
+ public boolean getBoolean(/*@StyleableRes*/ int index, boolean defValue) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
index *= AssetManager.STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index + AssetManager.STYLE_TYPE];
@@ -214,25 +310,35 @@ public class TypedArray {
return data[index + AssetManager.STYLE_DATA] != 0;
}
- TypedValue v = mValue;
+ final TypedValue v = mValue;
if (getValueAt(index, v)) {
- Log.w(Resources.TAG, "Converting to boolean: " + v);
- return XmlUtils.convertValueToBoolean(
- v.coerceToString(), defValue);
+ //StrictMode.noteResourceMismatch(v);
+ return XmlUtils.convertValueToBoolean(v.coerceToString(), defValue);
}
- Log.w(Resources.TAG, "getBoolean of bad type: 0x" + Integer.toHexString(type));
- return 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 index.
+ *
+ * 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.
+ * @param defValue Value to return if the attribute is not defined or
+ * cannot be coerced to an integer.
*
- * @return Attribute int value, or defValue if not defined.
+ * @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(int index, int defValue) {
+ public int getInt(/*@StyleableRes*/ int index, int defValue) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
index *= AssetManager.STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index + AssetManager.STYLE_TYPE];
@@ -242,24 +348,33 @@ public class TypedArray {
return data[index + AssetManager.STYLE_DATA];
}
- TypedValue v = mValue;
+ final TypedValue v = mValue;
if (getValueAt(index, v)) {
- Log.w(Resources.TAG, "Converting to int: " + v);
- return XmlUtils.convertValueToInt(
- v.coerceToString(), defValue);
+ //StrictMode.noteResourceMismatch(v);
+ return XmlUtils.convertValueToInt(v.coerceToString(), defValue);
}
- Log.w(Resources.TAG, "getInt of bad type: 0x" + Integer.toHexString(type));
- return 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 index.
+ *
+ * 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 not defined..
+ * @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(int index, float defValue) {
+ public float getFloat(/*@StyleableRes*/ int index, float defValue) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
index *= AssetManager.STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index + AssetManager.STYLE_TYPE];
@@ -271,16 +386,17 @@ public class TypedArray {
return data[index + AssetManager.STYLE_DATA];
}
- TypedValue v = mValue;
+ final TypedValue v = mValue;
if (getValueAt(index, v)) {
- Log.w(Resources.TAG, "Converting to float: " + v);
- CharSequence str = v.coerceToString();
+ final CharSequence str = v.coerceToString();
if (str != null) {
+ //StrictMode.noteResourceMismatch(v);
return Float.parseFloat(str.toString());
}
}
- Log.w(Resources.TAG, "getFloat of bad type: 0x" + Integer.toHexString(type));
- return defValue;
+
+ // We already checked for TYPE_NULL. This should never happen.
+ throw new RuntimeException("getFloat of bad type: 0x" + Integer.toHexString(type));
}
/**
@@ -288,14 +404,25 @@ public class TypedArray {
* the attribute references a color resource holding a complex
* {@link android.content.res.ColorStateList}, then the default color from
* the set is returned.
+ *
+ * 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.
*/
- public int getColor(int index, int defValue) {
+ //@ColorInt
+ public int getColor(/*@StyleableRes*/ int index, /*@ColorInt*/ int defValue) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
index *= AssetManager.STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index + AssetManager.STYLE_TYPE];
@@ -306,48 +433,109 @@ public class TypedArray {
} else if (type == TypedValue.TYPE_STRING) {
final TypedValue value = mValue;
if (getValueAt(index, value)) {
- ColorStateList csl = mResources.loadColorStateList(
- value, value.resourceId);
- return csl.getDefaultColor();
+ final ComplexColor cc = mResources.loadComplexColor(
+ value, value.resourceId, mTheme);
+ return cc.getDefaultColor();
}
return defValue;
+ } else if (type == TypedValue.TYPE_ATTRIBUTE) {
+ final TypedValue value = mValue;
+ getValueAt(index * AssetManager.STYLE_NUM_ENTRIES, value);
+ throw new UnsupportedOperationException(
+ "Failed to resolve attribute at index " + index + ": " + value);
}
- Log.e(Resources.TAG, "Can't convert to color: type=0x" + Integer.toHexString(type));
- return defValue;
+ throw new UnsupportedOperationException("Can't convert to color: type=0x" + Integer.toHexString(type));
+ }
+
+ /**
+ * Retrieve the ComplexColor for the attribute at index.
+ * 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}
+ *
+ * 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.
+ */
+ @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 * AssetManager.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 index.
* The value may be either a single solid color or a reference to
- * a color or complex {@link android.content.res.ColorStateList} description.
+ * a color or complex {@link android.content.res.ColorStateList}
+ * description.
+ *
+ * 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 null if not defined.
+ * @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.
*/
- public ColorStateList getColorStateList(int index) {
+ @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 * AssetManager.STYLE_NUM_ENTRIES, value)) {
- if (value.type == -1)
- return null;
- if (value.type == TypedValue.TYPE_REFERENCE && value.data == 0)
- return null;
- return mResources.loadColorStateList(value, value.resourceId);
+ 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 index.
+ *
+ * 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(int index, int defValue) {
+ public int getInteger(/*@StyleableRes*/ int index, int defValue) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
index *= AssetManager.STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index + AssetManager.STYLE_TYPE];
@@ -355,28 +543,43 @@ public class TypedArray {
return defValue;
} else if (type >= TypedValue.TYPE_FIRST_INT && type <= TypedValue.TYPE_LAST_INT) {
return data[index + AssetManager.STYLE_DATA];
+ } else if (type == TypedValue.TYPE_ATTRIBUTE) {
+ final TypedValue value = mValue;
+ getValueAt(index * AssetManager.STYLE_NUM_ENTRIES, value);
+ throw new UnsupportedOperationException(
+ "Failed to resolve attribute at index " + index + ": " + value);
}
throw new UnsupportedOperationException("Can't convert to integer: type=0x" + Integer.toHexString(type));
}
/**
- * Retrieve a dimensional unit attribute at index. Unit
+ * Retrieve a dimensional unit attribute at index. Unit
* conversions are based on the current {@link DisplayMetrics}
* associated with the resources this {@link TypedArray} object
* came from.
+ *
+ * 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.
+ * 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(int index, float defValue) {
+ public float getDimension(/*@StyleableRes*/ int index, float defValue) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
index *= AssetManager.STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index + AssetManager.STYLE_TYPE];
@@ -384,7 +587,12 @@ public class TypedArray {
return defValue;
} else if (type == TypedValue.TYPE_DIMENSION) {
return TypedValue.complexToDimension(
- data[index + AssetManager.STYLE_DATA], mResources.mMetrics);
+ data[index + AssetManager.STYLE_DATA], mMetrics);
+ } else if (type == TypedValue.TYPE_ATTRIBUTE) {
+ final TypedValue value = mValue;
+ getValueAt(index * AssetManager.STYLE_NUM_ENTRIES, value);
+ throw new UnsupportedOperationException(
+ "Failed to resolve attribute at index " + index + ": " + value);
}
throw new UnsupportedOperationException("Can't convert to dimension: type=0x" + Integer.toHexString(type));
@@ -396,18 +604,28 @@ public class TypedArray {
* {@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.
+ *
+ * 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.
+ * 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(int index, int defValue) {
+ public int getDimensionPixelOffset(/*@StyleableRes*/ int index, int defValue) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
index *= AssetManager.STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index + AssetManager.STYLE_TYPE];
@@ -415,7 +633,12 @@ public class TypedArray {
return defValue;
} else if (type == TypedValue.TYPE_DIMENSION) {
return TypedValue.complexToDimensionPixelOffset(
- data[index + AssetManager.STYLE_DATA], mResources.mMetrics);
+ data[index + AssetManager.STYLE_DATA], mMetrics);
+ } else if (type == TypedValue.TYPE_ATTRIBUTE) {
+ final TypedValue value = mValue;
+ getValueAt(index * AssetManager.STYLE_NUM_ENTRIES, value);
+ throw new UnsupportedOperationException(
+ "Failed to resolve attribute at index " + index + ": " + value);
}
throw new UnsupportedOperationException("Can't convert to dimension: type=0x" + Integer.toHexString(type));
@@ -428,18 +651,28 @@ public class TypedArray {
* 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.
+ *
+ * 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.
+ * 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(int index, int defValue) {
+ public int getDimensionPixelSize(/*@StyleableRes*/ int index, int defValue) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
index *= AssetManager.STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index + AssetManager.STYLE_TYPE];
@@ -447,7 +680,12 @@ public class TypedArray {
return defValue;
} else if (type == TypedValue.TYPE_DIMENSION) {
return TypedValue.complexToDimensionPixelSize(
- data[index + AssetManager.STYLE_DATA], mResources.mMetrics);
+ data[index + AssetManager.STYLE_DATA], mMetrics);
+ } else if (type == TypedValue.TYPE_ATTRIBUTE) {
+ final TypedValue value = mValue;
+ getValueAt(index * AssetManager.STYLE_NUM_ENTRIES, value);
+ throw new UnsupportedOperationException(
+ "Failed to resolve attribute at index " + index + ": " + value);
}
throw new UnsupportedOperationException("Can't convert to dimension: type=0x" + Integer.toHexString(type));
@@ -458,14 +696,24 @@ public class TypedArray {
* {@link android.view.ViewGroup}'s layout_width and layout_height
* attributes. This is only here for performance reasons; applications
* should use {@link #getDimensionPixelSize}.
+ *
+ * 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.
+ * 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(int index, String name) {
+ public int getLayoutDimension(/*@StyleableRes*/ int index, String name) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
index *= AssetManager.STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index + AssetManager.STYLE_TYPE];
@@ -473,10 +721,15 @@ public class TypedArray {
return data[index + AssetManager.STYLE_DATA];
} else if (type == TypedValue.TYPE_DIMENSION) {
return TypedValue.complexToDimensionPixelSize(
- data[index + AssetManager.STYLE_DATA], mResources.mMetrics);
+ data[index + AssetManager.STYLE_DATA], mMetrics);
+ } else if (type == TypedValue.TYPE_ATTRIBUTE) {
+ final TypedValue value = mValue;
+ getValueAt(index * AssetManager.STYLE_NUM_ENTRIES, value);
+ throw new UnsupportedOperationException(
+ "Failed to resolve attribute at index " + index + ": " + value);
}
- throw new RuntimeException(getPositionDescription() + ": You must supply a " + name + " attribute.");
+ throw new UnsupportedOperationException(getPositionDescription() + ": You must supply a " + name + " attribute.");
}
/**
@@ -487,12 +740,17 @@ public class TypedArray {
*
* @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.
+ * default or contains the wrong type of data.
*
* @return Attribute dimension value multiplied by the appropriate
- * metric and truncated to integer pixels.
+ * metric and truncated to integer pixels.
+ * @throws RuntimeException if the TypedArray has already been recycled.
*/
- public int getLayoutDimension(int index, int defValue) {
+ public int getLayoutDimension(/*@StyleableRes*/ int index, int defValue) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
index *= AssetManager.STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index + AssetManager.STYLE_TYPE];
@@ -500,14 +758,14 @@ public class TypedArray {
return data[index + AssetManager.STYLE_DATA];
} else if (type == TypedValue.TYPE_DIMENSION) {
return TypedValue.complexToDimensionPixelSize(
- data[index + AssetManager.STYLE_DATA], mResources.mMetrics);
+ data[index + AssetManager.STYLE_DATA], mMetrics);
}
return defValue;
}
/**
- * Retrieve a fractional unit attribute at index.
+ * Retrieves a fractional unit attribute at index.
*
* @param index Index of attribute to retrieve.
* @param base The base value of this fraction. In other words, a
@@ -519,9 +777,16 @@ public class TypedArray {
* not a resource.
*
* @return Attribute fractional value multiplied by the appropriate
- * base value, or defValue if not defined.
+ * 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(int index, int base, int pbase, float defValue) {
+ public float getFraction(/*@StyleableRes*/ int index, int base, int pbase, float defValue) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
index *= AssetManager.STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index + AssetManager.STYLE_TYPE];
@@ -530,13 +795,18 @@ public class TypedArray {
} else if (type == TypedValue.TYPE_FRACTION) {
return TypedValue.complexToFraction(
data[index + AssetManager.STYLE_DATA], base, pbase);
+ } else if (type == TypedValue.TYPE_ATTRIBUTE) {
+ final TypedValue value = mValue;
+ getValueAt(index * AssetManager.STYLE_NUM_ENTRIES, value);
+ throw new UnsupportedOperationException(
+ "Failed to resolve attribute at index " + index + ": " + value);
}
throw new UnsupportedOperationException("Can't convert to fraction: type=0x" + Integer.toHexString(type));
}
/**
- * Retrieve the resource identifier for the attribute at
+ * Retrieves the resource identifier for the attribute at
* index. 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
@@ -548,8 +818,14 @@ public class TypedArray {
* not a resource.
*
* @return Attribute resource identifier, or defValue if not defined.
+ * @throws RuntimeException if the TypedArray has already been recycled.
*/
- public int getResourceId(int index, int defValue) {
+ //@AnyRes
+ public int getResourceId(/*@StyleableRes*/ int index, int defValue) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
index *= AssetManager.STYLE_NUM_ENTRIES;
final int[] data = mData;
if (data[index + AssetManager.STYLE_TYPE] != TypedValue.TYPE_NULL) {
@@ -562,24 +838,56 @@ public class TypedArray {
}
/**
- * Retrieve the Drawable for the attribute at index. 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.
+ * Retrieves the theme attribute resource identifier for the attribute at
+ * index.
+ *
+ * @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 *= AssetManager.STYLE_NUM_ENTRIES;
+ final int[] data = mData;
+ if (data[index + AssetManager.STYLE_TYPE] == TypedValue.TYPE_ATTRIBUTE) {
+ return data[index + AssetManager.STYLE_DATA];
+ }
+ return defValue;
+ }
+
+ /**
+ * Retrieve the Drawable for the attribute at index.
+ *
+ * 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 null if not defined.
+ * @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.
*/
- public Drawable getDrawable(int index) {
+ @Nullable
+ public Drawable getDrawable(/*@StyleableRes*/ int index) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
final TypedValue value = mValue;
if (getValueAt(index * AssetManager.STYLE_NUM_ENTRIES, value)) {
- if (false) {
- System.out.println("******************************************************************");
- System.out.println("Got drawable resource: type=" + value.type + " str=" + value.string + " int=0x" + Integer.toHexString(value.data) + " cookie=" + value.assetCookie);
- System.out.println("******************************************************************");
+ if (value.type == TypedValue.TYPE_ATTRIBUTE) {
+ throw new UnsupportedOperationException(
+ "Failed to resolve attribute at index " + index + ": " + value);
}
- return mResources.loadDrawable(value, value.resourceId);
+ return mResources.loadDrawable(value, value.resourceId, mTheme);
}
return null;
}
@@ -589,19 +897,23 @@ public class TypedArray {
* 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[].
+ *
+ * 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 null if not defined.
+ * @return CharSequence[] for the attribute, or {@code null} if not
+ * defined.
+ * @throws RuntimeException if the TypedArray has already been recycled.
*/
- public CharSequence[] getTextArray(int index) {
+ 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 * AssetManager.STYLE_NUM_ENTRIES, value)) {
- if (false) {
- System.out.println("******************************************************************");
- System.out.println("Got drawable resource: type=" + value.type + " str=" + value.string + " int=0x" + Integer.toHexString(value.data) + " cookie=" + value.assetCookie);
- System.out.println("******************************************************************");
- }
return mResources.getTextArray(value.resourceId);
}
return null;
@@ -614,26 +926,77 @@ public class TypedArray {
* @param outValue TypedValue object in which to place the attribute's
* data.
*
- * @return Returns true if the value was retrieved, else false.
+ * @return {@code true} if the value was retrieved, false otherwise.
+ * @throws RuntimeException if the TypedArray has already been recycled.
*/
- public boolean getValue(int index, TypedValue outValue) {
+ public boolean getValue(/*@StyleableRes*/ int index, TypedValue outValue) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
return getValueAt(index * AssetManager.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 *= AssetManager.STYLE_NUM_ENTRIES;
+ return mData[index + AssetManager.STYLE_TYPE];
+ }
+
/**
* Determines whether there is an attribute at index.
+ *
+ * Note: 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(int index) {
+ public boolean hasValue(/*@StyleableRes*/ int index) {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
index *= AssetManager.STYLE_NUM_ENTRIES;
final int[] data = mData;
final int type = data[index + AssetManager.STYLE_TYPE];
return type != TypedValue.TYPE_NULL;
}
+ /**
+ * Determines whether there is an attribute at index, 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 *= AssetManager.STYLE_NUM_ENTRIES;
+ final int[] data = mData;
+ final int type = data[index + AssetManager.STYLE_TYPE];
+ return type != TypedValue.TYPE_NULL || data[index + AssetManager.STYLE_DATA] == TypedValue.DATA_NULL_EMPTY;
+ }
+
/**
* Retrieve the raw TypedValue for the attribute at index
* and return a temporary object holding its data. This object is only
@@ -644,8 +1007,13 @@ public class TypedArray {
* @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(int index) {
+ 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 * AssetManager.STYLE_NUM_ENTRIES, value)) {
return value;
@@ -655,22 +1023,127 @@ public class TypedArray {
/**
* 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() : "";
}
/**
- * Give back a previously retrieved array, for later re-use.
+ * 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() {
- synchronized (mResources.mAccessLock) {
- TypedArray cached = mResources.mCachedStyledAttributes;
- if (cached == null || cached.mData.length < mData.length) {
- mXml = null;
- mResources.mCachedStyledAttributes = this;
- }
+ if (mRecycled) {
+ throw new RuntimeException(toString() + " recycled twice!");
}
+
+ mRecycled = true;
+
+ // These may have been set by the client.
+ mXml = null;
+ mTheme = 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
+ public int[] extractThemeAttrs() {
+ return extractThemeAttrs(null);
+ }
+
+ /**
+ * @hide
+ */
+ @Nullable
+ 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 * AssetManager.STYLE_NUM_ENTRIES;
+ if (data[index + AssetManager.STYLE_TYPE] != TypedValue.TYPE_ATTRIBUTE) {
+ // Not an attribute, ignore.
+ continue;
+ }
+
+ // Null the entry so that we can safely call getZzz().
+ data[index + AssetManager.STYLE_TYPE] = TypedValue.TYPE_NULL;
+
+ final int attr = data[index + AssetManager.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 int getChangingConfigurations() {
+ if (mRecycled) {
+ throw new RuntimeException("Cannot make calls to a recycled instance!");
+ }
+
+ int changingConfig = 0;
+
+ final int[] data = mData;
+ final int N = length();
+ for (int i = 0; i < N; i++) {
+ final int index = i * AssetManager.STYLE_NUM_ENTRIES;
+ final int type = data[index + AssetManager.STYLE_TYPE];
+ if (type == TypedValue.TYPE_NULL) {
+ continue;
+ }
+ changingConfig |= data[index + AssetManager.STYLE_CHANGING_CONFIGURATIONS];
+ }
+ return changingConfig;
}
private boolean getValueAt(int index, TypedValue outValue) {
@@ -694,32 +1167,26 @@ public class TypedArray {
final int cookie = data[index + AssetManager.STYLE_ASSET_COOKIE];
if (cookie < 0) {
if (mXml != null) {
- CharSequence string = ((XmlBlock.Parser)mXml).getPooledString(data[index + AssetManager.STYLE_DATA]);
- if (string != null)
- return string;
- }
- if (data[index + AssetManager.STYLE_RESOURCE_ID] != 0) {
- return mResources.mAssets.getResourceText(data[index + AssetManager.STYLE_RESOURCE_ID]);
+ return ((XmlBlock.Parser)mXml).getPooledString(
+ data[index + AssetManager.STYLE_DATA]);
}
+ return null;
}
- // System.out.println("Getting pooled from: " + v);
- return mResources.mAssets.getPooledString(
- cookie, data[index + AssetManager.STYLE_DATA]);
+ return mAssets.getPooledString(cookie, data[index + AssetManager.STYLE_DATA]);
}
// FIXME
public /*package*/ TypedArray(Resources resources, int[] data, int[] indices, int len) {
mResources = resources;
+ mMetrics = mResources.mMetrics;
+ mAssets = mResources.mAssets;
mData = data;
mIndices = indices;
mLength = len;
}
+ @Override
public String toString() {
return Arrays.toString(mData);
}
-
- public int getChangingConfigurations() {
- return 0;
- }
}
diff --git a/src/api-impl/android/graphics/Shader.java b/src/api-impl/android/graphics/Shader.java
index 3c743c8b..06568ac7 100644
--- a/src/api-impl/android/graphics/Shader.java
+++ b/src/api-impl/android/graphics/Shader.java
@@ -8,5 +8,11 @@ public class Shader {
REPEAT
}
+ protected void init(long ni) {
+ }
+
public void setLocalMatrix(Matrix matrix) {}
+
+
+ protected void copyLocalMatrix(Shader dest) {}
}
diff --git a/src/api-impl/android/graphics/SweepGradient.java b/src/api-impl/android/graphics/SweepGradient.java
new file mode 100644
index 00000000..bba12195
--- /dev/null
+++ b/src/api-impl/android/graphics/SweepGradient.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2007 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.graphics;
+
+public class SweepGradient extends Shader {
+
+ private static final int TYPE_COLORS_AND_POSITIONS = 1;
+ private static final int TYPE_COLOR_START_AND_COLOR_END = 2;
+
+ /**
+ * Type of the LinearGradient: can be either TYPE_COLORS_AND_POSITIONS or
+ * TYPE_COLOR_START_AND_COLOR_END.
+ */
+ private int mType;
+
+ private float mCx;
+ private float mCy;
+ private int[] mColors;
+ private float[] mPositions;
+ private int mColor0;
+ private int mColor1;
+
+ /**
+ * A subclass of Shader that draws a sweep gradient around a center point.
+ *
+ * @param cx The x-coordinate of the center
+ * @param cy The y-coordinate of the center
+ * @param colors The colors to be distributed between around the center.
+ * There must be at least 2 colors in the array.
+ * @param positions May be NULL. The relative position of
+ * each corresponding color in the colors array, beginning
+ * with 0 and ending with 1.0. If the values are not
+ * monotonic, the drawing may produce unexpected results.
+ * If positions is NULL, then the colors are automatically
+ * spaced evenly.
+ */
+ public SweepGradient(float cx, float cy,
+ int colors[], float positions[]) {
+ if (colors.length < 2) {
+ throw new IllegalArgumentException("needs >= 2 number of colors");
+ }
+ if (positions != null && colors.length != positions.length) {
+ throw new IllegalArgumentException(
+ "color and position arrays must be of equal length");
+ }
+ mType = TYPE_COLORS_AND_POSITIONS;
+ mCx = cx;
+ mCy = cy;
+ mColors = colors;
+ mPositions = positions;
+ init(nativeCreate1(cx, cy, colors, positions));
+ }
+
+ /**
+ * A subclass of Shader that draws a sweep gradient around a center point.
+ *
+ * @param cx The x-coordinate of the center
+ * @param cy The y-coordinate of the center
+ * @param color0 The color to use at the start of the sweep
+ * @param color1 The color to use at the end of the sweep
+ */
+ public SweepGradient(float cx, float cy, int color0, int color1) {
+ mType = TYPE_COLOR_START_AND_COLOR_END;
+ mCx = cx;
+ mCy = cy;
+ mColor0 = color0;
+ mColor1 = color1;
+ init(nativeCreate2(cx, cy, color0, color1));
+ }
+
+ /**
+ * @hide
+ */
+ //@Override
+ protected Shader copy() {
+ final SweepGradient copy;
+ switch (mType) {
+ case TYPE_COLORS_AND_POSITIONS:
+ copy = new SweepGradient(mCx, mCy, mColors.clone(),
+ mPositions != null ? mPositions.clone() : null);
+ break;
+ case TYPE_COLOR_START_AND_COLOR_END:
+ copy = new SweepGradient(mCx, mCy, mColor0, mColor1);
+ break;
+ default:
+ throw new IllegalArgumentException("SweepGradient should be created with either "
+ +
+ "colors and positions or start color and end color");
+ }
+ copyLocalMatrix(copy);
+ return copy;
+ }
+
+ private static native long nativeCreate1(float x, float y, int colors[], float positions[]);
+ private static native long nativeCreate2(float x, float y, int color0, int color1);
+}
diff --git a/src/api-impl/android/util/MathUtils.java b/src/api-impl/android/util/MathUtils.java
new file mode 100644
index 00000000..ae6a15f0
--- /dev/null
+++ b/src/api-impl/android/util/MathUtils.java
@@ -0,0 +1,299 @@
+/*
+ * Copyright (C) 2009 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.util;
+
+//import android.compat.annotation.UnsupportedAppUsage;
+import android.graphics.Rect;
+
+/**
+ * A class that contains utility methods related to numbers.
+ *
+ * @hide Pending API council approval
+ */
+public final class MathUtils {
+ private static final float DEG_TO_RAD = 3.1415926f / 180.0f;
+ private static final float RAD_TO_DEG = 180.0f / 3.1415926f;
+
+ private MathUtils() {
+ }
+
+ //@UnsupportedAppUsage
+ public static float abs(float v) {
+ return v > 0 ? v : -v;
+ }
+
+ //@UnsupportedAppUsage
+ public static int constrain(int amount, int low, int high) {
+ return amount < low ? low : (amount > high ? high : amount);
+ }
+
+ public static long constrain(long amount, long low, long high) {
+ return amount < low ? low : (amount > high ? high : amount);
+ }
+
+ //@UnsupportedAppUsage
+ public static float constrain(float amount, float low, float high) {
+ return amount < low ? low : (amount > high ? high : amount);
+ }
+
+ public static float log(float a) {
+ return (float)Math.log(a);
+ }
+
+ public static float exp(float a) {
+ return (float)Math.exp(a);
+ }
+
+ public static float pow(float a, float b) {
+ return (float)Math.pow(a, b);
+ }
+
+ public static float sqrt(float a) {
+ return (float)Math.sqrt(a);
+ }
+
+ public static float max(float a, float b) {
+ return a > b ? a : b;
+ }
+
+ //@UnsupportedAppUsage
+ public static float max(int a, int b) {
+ return a > b ? a : b;
+ }
+
+ public static float max(float a, float b, float c) {
+ return a > b ? (a > c ? a : c) : (b > c ? b : c);
+ }
+
+ public static float max(int a, int b, int c) {
+ return a > b ? (a > c ? a : c) : (b > c ? b : c);
+ }
+
+ public static float min(float a, float b) {
+ return a < b ? a : b;
+ }
+
+ public static float min(int a, int b) {
+ return a < b ? a : b;
+ }
+
+ public static float min(float a, float b, float c) {
+ return a < b ? (a < c ? a : c) : (b < c ? b : c);
+ }
+
+ public static float min(int a, int b, int c) {
+ return a < b ? (a < c ? a : c) : (b < c ? b : c);
+ }
+
+ public static float dist(float x1, float y1, float x2, float y2) {
+ final float x = (x2 - x1);
+ final float y = (y2 - y1);
+ return (float)Math.hypot(x, y);
+ }
+
+ public static float dist(float x1, float y1, float z1, float x2, float y2, float z2) {
+ final float x = (x2 - x1);
+ final float y = (y2 - y1);
+ final float z = (z2 - z1);
+ return (float)Math.sqrt(x * x + y * y + z * z);
+ }
+
+ public static float mag(float a, float b) {
+ return (float)Math.hypot(a, b);
+ }
+
+ public static float mag(float a, float b, float c) {
+ return (float)Math.sqrt(a * a + b * b + c * c);
+ }
+
+ public static float sq(float v) {
+ return v * v;
+ }
+
+ public static float dot(float v1x, float v1y, float v2x, float v2y) {
+ return v1x * v2x + v1y * v2y;
+ }
+
+ public static float cross(float v1x, float v1y, float v2x, float v2y) {
+ return v1x * v2y - v1y * v2x;
+ }
+
+ public static float radians(float degrees) {
+ return degrees * DEG_TO_RAD;
+ }
+
+ public static float degrees(float radians) {
+ return radians * RAD_TO_DEG;
+ }
+
+ public static float acos(float value) {
+ return (float)Math.acos(value);
+ }
+
+ public static float asin(float value) {
+ return (float)Math.asin(value);
+ }
+
+ public static float atan(float value) {
+ return (float)Math.atan(value);
+ }
+
+ public static float atan2(float a, float b) {
+ return (float)Math.atan2(a, b);
+ }
+
+ public static float tan(float angle) {
+ return (float)Math.tan(angle);
+ }
+
+ //@UnsupportedAppUsage
+ public static float lerp(float start, float stop, float amount) {
+ return start + (stop - start) * amount;
+ }
+
+ public static float lerp(int start, int stop, float amount) {
+ return lerp((float)start, (float)stop, amount);
+ }
+
+ /**
+ * Returns the interpolation scalar (s) that satisfies the equation: {@code value = }{@link
+ * #lerp}{@code (a, b, s)}
+ *
+ * If {@code a == b}, then this function will return 0.
+ */
+ public static float lerpInv(float a, float b, float value) {
+ return a != b ? ((value - a) / (b - a)) : 0.0f;
+ }
+
+ /**
+ * Returns the single argument constrained between [0.0, 1.0].
+ */
+ public static float saturate(float value) {
+ return constrain(value, 0.0f, 1.0f);
+ }
+
+ /**
+ * Returns the saturated (constrained between [0, 1]) result of {@link #lerpInv}.
+ */
+ public static float lerpInvSat(float a, float b, float value) {
+ return saturate(lerpInv(a, b, value));
+ }
+
+ /**
+ * Returns an interpolated angle in degrees between a set of start and end
+ * angles.
+ *
+ * Unlike {@link #lerp(float, float, float)}, the direction and distance of
+ * travel is determined by the shortest angle between the start and end
+ * angles. For example, if the starting angle is 0 and the ending angle is
+ * 350, then the interpolated angle will be in the range [0,-10] rather
+ * than [0,350].
+ *
+ * @param start the starting angle in degrees
+ * @param end the ending angle in degrees
+ * @param amount the position between start and end in the range [0,1]
+ * where 0 is the starting angle and 1 is the ending angle
+ * @return the interpolated angle in degrees
+ */
+ public static float lerpDeg(float start, float end, float amount) {
+ final float minAngle = (((end - start) + 180) % 360) - 180;
+ return minAngle * amount + start;
+ }
+
+ public static float norm(float start, float stop, float value) {
+ return (value - start) / (stop - start);
+ }
+
+ public static float map(float minStart, float minStop, float maxStart, float maxStop, float value) {
+ return maxStart + (maxStop - maxStart) * ((value - minStart) / (minStop - minStart));
+ }
+
+ /**
+ * Calculates a value in [rangeMin, rangeMax] that maps value in [valueMin, valueMax] to
+ * returnVal in [rangeMin, rangeMax].
+ *
+ * Always returns a constrained value in the range [rangeMin, rangeMax], even if value is
+ * outside [valueMin, valueMax].
+ *
+ * Eg:
+ * constrainedMap(0f, 100f, 0f, 1f, 0.5f) = 50f
+ * constrainedMap(20f, 200f, 10f, 20f, 20f) = 200f
+ * constrainedMap(20f, 200f, 10f, 20f, 50f) = 200f
+ * constrainedMap(10f, 50f, 10f, 20f, 5f) = 10f
+ *
+ * @param rangeMin minimum of the range that should be returned.
+ * @param rangeMax maximum of the range that should be returned.
+ * @param valueMin minimum of range to map {@code value} to.
+ * @param valueMax maximum of range to map {@code value} to.
+ * @param value to map to the range [{@code valueMin}, {@code valueMax}]. Note, can be outside
+ * this range, resulting in a clamped value.
+ * @return the mapped value, constrained to [{@code rangeMin}, {@code rangeMax}.
+ */
+ public static float constrainedMap(
+ float rangeMin, float rangeMax, float valueMin, float valueMax, float value) {
+ return lerp(rangeMin, rangeMax, lerpInvSat(valueMin, valueMax, value));
+ }
+
+ /**
+ * Perform Hermite interpolation between two values.
+ * Eg:
+ * smoothStep(0, 0.5f, 0.5f) = 1f
+ * smoothStep(0, 0.5f, 0.25f) = 0.5f
+ *
+ * @param start Left edge.
+ * @param end Right edge.
+ * @param x A value between {@code start} and {@code end}.
+ * @return A number between 0 and 1 representing where {@code x} is in the interpolation.
+ */
+ public static float smoothStep(float start, float end, float x) {
+ return constrain((x - start) / (end - start), 0f, 1f);
+ }
+
+ /**
+ * Returns the sum of the two parameters, or throws an exception if the resulting sum would
+ * cause an overflow or underflow.
+ * @throws IllegalArgumentException when overflow or underflow would occur.
+ */
+ public static int addOrThrow(int a, int b) throws IllegalArgumentException {
+ if (b == 0) {
+ return a;
+ }
+
+ if (b > 0 && a <= (Integer.MAX_VALUE - b)) {
+ return a + b;
+ }
+
+ if (b < 0 && a >= (Integer.MIN_VALUE - b)) {
+ return a + b;
+ }
+ throw new IllegalArgumentException("Addition overflow: " + a + " + " + b);
+ }
+
+ /**
+ * Resize a {@link Rect} so one size would be {@param largestSide}.
+ *
+ * @param outToResize Rectangle that will be resized.
+ * @param largestSide Size of the largest side.
+ */
+ public static void fitRect(Rect outToResize, int largestSide) {
+ if (outToResize.isEmpty()) {
+ return;
+ }
+ float maxSize = Math.max(outToResize.width(), outToResize.height());
+ outToResize.scale(largestSide / maxSize);
+ }
+}
diff --git a/src/api-impl/android/util/Pools.java b/src/api-impl/android/util/Pools.java
new file mode 100644
index 00000000..d2410d63
--- /dev/null
+++ b/src/api-impl/android/util/Pools.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2009 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.util;
+
+/**
+ * Helper class for crating pools of objects. An example use looks like this:
+ *
+ * public class MyPooledClass {
+ *
+ * private static final SynchronizedPool sPool =
+ * new SynchronizedPool(10);
+ *
+ * public static MyPooledClass obtain() {
+ * MyPooledClass instance = sPool.acquire();
+ * return (instance != null) ? instance : new MyPooledClass();
+ * }
+ *
+ * public void recycle() {
+ * // Clear state if needed.
+ * sPool.release(this);
+ * }
+ *
+ * . . .
+ * }
+ *
+ *
+ * @hide
+ */
+public final class Pools {
+
+ /**
+ * Interface for managing a pool of objects.
+ *
+ * @param The pooled type.
+ */
+ public static interface Pool {
+
+ /**
+ * @return An instance from the pool if such, null otherwise.
+ */
+ public T acquire();
+
+ /**
+ * Release an instance to the pool.
+ *
+ * @param instance The instance to release.
+ * @return Whether the instance was put in the pool.
+ *
+ * @throws IllegalStateException If the instance is already in the pool.
+ */
+ public boolean release(T instance);
+ }
+
+ private Pools() {
+ /* do nothing - hiding constructor */
+ }
+
+ /**
+ * Simple (non-synchronized) pool of objects.
+ *
+ * @param The pooled type.
+ */
+ public static class SimplePool implements Pool {
+ private final Object[] mPool;
+
+ private int mPoolSize;
+
+ /**
+ * Creates a new instance.
+ *
+ * @param maxPoolSize The max pool size.
+ *
+ * @throws IllegalArgumentException If the max pool size is less than zero.
+ */
+ public SimplePool(int maxPoolSize) {
+ if (maxPoolSize <= 0) {
+ throw new IllegalArgumentException("The max pool size must be > 0");
+ }
+ mPool = new Object[maxPoolSize];
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public T acquire() {
+ if (mPoolSize > 0) {
+ final int lastPooledIndex = mPoolSize - 1;
+ T instance = (T)mPool[lastPooledIndex];
+ mPool[lastPooledIndex] = null;
+ mPoolSize--;
+ return instance;
+ }
+ return null;
+ }
+
+ @Override
+ public boolean release(T instance) {
+ if (isInPool(instance)) {
+ throw new IllegalStateException("Already in the pool!");
+ }
+ if (mPoolSize < mPool.length) {
+ mPool[mPoolSize] = instance;
+ mPoolSize++;
+ return true;
+ }
+ return false;
+ }
+
+ private boolean isInPool(T instance) {
+ for (int i = 0; i < mPoolSize; i++) {
+ if (mPool[i] == instance) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Synchronized) pool of objects.
+ *
+ * @param The pooled type.
+ */
+ public static class SynchronizedPool extends SimplePool {
+ private final Object mLock = new Object();
+
+ /**
+ * Creates a new instance.
+ *
+ * @param maxPoolSize The max pool size.
+ *
+ * @throws IllegalArgumentException If the max pool size is less than zero.
+ */
+ public SynchronizedPool(int maxPoolSize) {
+ super(maxPoolSize);
+ }
+
+ @Override
+ public T acquire() {
+ synchronized (mLock) {
+ return super.acquire();
+ }
+ }
+
+ @Override
+ public boolean release(T element) {
+ synchronized (mLock) {
+ return super.release(element);
+ }
+ }
+ }
+}
diff --git a/src/api-impl/android/util/TypedValue.java b/src/api-impl/android/util/TypedValue.java
index baadb25b..e486ff29 100644
--- a/src/api-impl/android/util/TypedValue.java
+++ b/src/api-impl/android/util/TypedValue.java
@@ -210,6 +210,17 @@ public class TypedValue {
/* ------------------------------------------------------------ */
+ /**
+ * {@link #TYPE_NULL} data indicating the value was not specified.
+ */
+ public static final int DATA_NULL_UNDEFINED = 0;
+ /**
+ * {@link #TYPE_NULL} data indicating the value was explicitly set to null.
+ */
+ public static final int DATA_NULL_EMPTY = 1;
+
+ /* ------------------------------------------------------------ */
+
/**
* If {@link #density} is equal to this value, then the density should be
* treated as the system's default density value: {@link DisplayMetrics#DENSITY_DEFAULT}.
diff --git a/src/api-impl/com/android/internal/util/GrowingArrayUtils.java b/src/api-impl/com/android/internal/util/GrowingArrayUtils.java
index aee8ab4e..d6468f90 100644
--- a/src/api-impl/com/android/internal/util/GrowingArrayUtils.java
+++ b/src/api-impl/com/android/internal/util/GrowingArrayUtils.java
@@ -27,6 +27,7 @@ package com.android.internal.util;
* @hide
*/
public final class GrowingArrayUtils {
+
/**
* Appends an element to the end of the array, growing the array if there is no more room.
* @param array The array to which to append the element. This must NOT be null.
@@ -38,10 +39,11 @@ public final class GrowingArrayUtils {
*/
public static T[] append(T[] array, int currentSize, T element) {
assert currentSize <= array.length;
+
if (currentSize + 1 > array.length) {
@SuppressWarnings("unchecked")
T[] newArray = ArrayUtils.newUnpaddedArray(
- (Class) array.getClass().getComponentType(), growSize(currentSize));
+ (Class)array.getClass().getComponentType(), growSize(currentSize));
System.arraycopy(array, 0, newArray, 0, currentSize);
array = newArray;
}
@@ -54,6 +56,7 @@ public final class GrowingArrayUtils {
*/
public static int[] append(int[] array, int currentSize, int element) {
assert currentSize <= array.length;
+
if (currentSize + 1 > array.length) {
int[] newArray = ArrayUtils.newUnpaddedIntArray(growSize(currentSize));
System.arraycopy(array, 0, newArray, 0, currentSize);
@@ -68,6 +71,7 @@ public final class GrowingArrayUtils {
*/
public static long[] append(long[] array, int currentSize, long element) {
assert currentSize <= array.length;
+
if (currentSize + 1 > array.length) {
long[] newArray = ArrayUtils.newUnpaddedLongArray(growSize(currentSize));
System.arraycopy(array, 0, newArray, 0, currentSize);
@@ -82,6 +86,7 @@ public final class GrowingArrayUtils {
*/
public static boolean[] append(boolean[] array, int currentSize, boolean element) {
assert currentSize <= array.length;
+
if (currentSize + 1 > array.length) {
boolean[] newArray = ArrayUtils.newUnpaddedBooleanArray(growSize(currentSize));
System.arraycopy(array, 0, newArray, 0, currentSize);
@@ -91,6 +96,21 @@ public final class GrowingArrayUtils {
return array;
}
+ /**
+ * Primitive float version of {@link #append(Object[], int, Object)}.
+ */
+ public static float[] append(float[] array, int currentSize, float element) {
+ assert currentSize <= array.length;
+
+ if (currentSize + 1 > array.length) {
+ float[] newArray = ArrayUtils.newUnpaddedFloatArray(growSize(currentSize));
+ System.arraycopy(array, 0, newArray, 0, currentSize);
+ array = newArray;
+ }
+ array[currentSize] = element;
+ return array;
+ }
+
/**
* Inserts an element into the array at the specified index, growing the array if there is no
* more room.
@@ -104,14 +124,16 @@ public final class GrowingArrayUtils {
*/
public static T[] insert(T[] array, int currentSize, int index, T element) {
assert currentSize <= array.length;
+
if (currentSize + 1 <= array.length) {
System.arraycopy(array, index, array, index + 1, currentSize - index);
array[index] = element;
return array;
}
+
@SuppressWarnings("unchecked")
T[] newArray = ArrayUtils.newUnpaddedArray((Class)array.getClass().getComponentType(),
- growSize(currentSize));
+ growSize(currentSize));
System.arraycopy(array, 0, newArray, 0, index);
newArray[index] = element;
System.arraycopy(array, index, newArray, index + 1, array.length - index);
@@ -123,11 +145,13 @@ public final class GrowingArrayUtils {
*/
public static int[] insert(int[] array, int currentSize, int index, int element) {
assert currentSize <= array.length;
+
if (currentSize + 1 <= array.length) {
System.arraycopy(array, index, array, index + 1, currentSize - index);
array[index] = element;
return array;
}
+
int[] newArray = ArrayUtils.newUnpaddedIntArray(growSize(currentSize));
System.arraycopy(array, 0, newArray, 0, index);
newArray[index] = element;
@@ -140,11 +164,13 @@ public final class GrowingArrayUtils {
*/
public static long[] insert(long[] array, int currentSize, int index, long element) {
assert currentSize <= array.length;
+
if (currentSize + 1 <= array.length) {
System.arraycopy(array, index, array, index + 1, currentSize - index);
array[index] = element;
return array;
}
+
long[] newArray = ArrayUtils.newUnpaddedLongArray(growSize(currentSize));
System.arraycopy(array, 0, newArray, 0, index);
newArray[index] = element;
@@ -157,11 +183,13 @@ public final class GrowingArrayUtils {
*/
public static boolean[] insert(boolean[] array, int currentSize, int index, boolean element) {
assert currentSize <= array.length;
+
if (currentSize + 1 <= array.length) {
System.arraycopy(array, index, array, index + 1, currentSize - index);
array[index] = element;
return array;
}
+
boolean[] newArray = ArrayUtils.newUnpaddedBooleanArray(growSize(currentSize));
System.arraycopy(array, 0, newArray, 0, index);
newArray[index] = element;
@@ -177,6 +205,7 @@ public final class GrowingArrayUtils {
public static int growSize(int currentSize) {
return currentSize <= 4 ? 8 : currentSize * 2;
}
+
// Uninstantiable
private GrowingArrayUtils() {}
-}
\ No newline at end of file
+}
diff --git a/src/api-impl/meson.build b/src/api-impl/meson.build
index 90146aa7..faf78abf 100644
--- a/src/api-impl/meson.build
+++ b/src/api-impl/meson.build
@@ -118,9 +118,13 @@ srcs = [
'android/content/res/AssetManager.java',
'android/content/res/ColorStateList.java',
'android/content/res/CompatibilityInfo.java',
+ 'android/content/res/ComplexColor.java',
'android/content/res/Configuration.java',
+ 'android/content/res/ConfigurationBoundResourceCache.java',
+ 'android/content/res/GradientColor.java',
'android/content/res/Resources.java',
'android/content/res/StringBlock.java',
+ 'android/content/res/ThemedResourceCache.java',
'android/content/res/TypedArray.java',
'android/content/res/XmlBlock.java',
'android/content/res/XmlResourceParser.java',
@@ -210,6 +214,7 @@ srcs = [
'android/graphics/RectF.java',
'android/graphics/Region.java',
'android/graphics/Shader.java',
+ 'android/graphics/SweepGradient.java',
'android/graphics/Typeface.java',
'android/graphics/Xfermode.java',
'android/graphics/drawable/Animatable.java',
@@ -426,6 +431,7 @@ srcs = [
'android/util/JsonScope.java',
'android/util/JsonToken.java',
'android/util/JsonWriter.java',
+ 'android/util/MathUtils.java',
'android/util/LayoutDirection.java',
'android/util/Log.java',
'android/util/LongSparseArray.java',