summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/current.txt27
-rw-r--r--core/java/android/animation/Animator.java115
-rw-r--r--core/java/android/animation/AnimatorInflater.java90
-rw-r--r--core/java/android/animation/AnimatorSet.java78
-rw-r--r--core/java/android/animation/FloatKeyframeSet.java5
-rw-r--r--core/java/android/animation/IntKeyframeSet.java3
-rw-r--r--core/java/android/animation/KeyframeSet.java18
-rw-r--r--core/java/android/animation/Keyframes.java3
-rw-r--r--core/java/android/animation/PropertyValuesHolder.java9
-rw-r--r--core/java/android/animation/StateListAnimator.java148
-rw-r--r--core/java/android/animation/ValueAnimator.java8
-rw-r--r--core/java/android/content/res/ConfigurationBoundResourceCache.java138
-rw-r--r--core/java/android/content/res/ConstantState.java61
-rw-r--r--core/java/android/content/res/Resources.java68
-rw-r--r--core/java/android/view/animation/AccelerateDecelerateInterpolator.java8
-rw-r--r--core/java/android/view/animation/AccelerateInterpolator.java4
-rw-r--r--core/java/android/view/animation/AnimationUtils.java5
-rw-r--r--core/java/android/view/animation/AnticipateInterpolator.java7
-rw-r--r--core/java/android/view/animation/AnticipateOvershootInterpolator.java5
-rw-r--r--core/java/android/view/animation/BaseInterpolator.java37
-rw-r--r--core/java/android/view/animation/BounceInterpolator.java2
-rw-r--r--core/java/android/view/animation/CycleInterpolator.java4
-rw-r--r--core/java/android/view/animation/DecelerateInterpolator.java4
-rw-r--r--core/java/android/view/animation/LinearInterpolator.java7
-rw-r--r--core/java/android/view/animation/OvershootInterpolator.java7
-rw-r--r--core/java/android/view/animation/PathInterpolator.java4
-rw-r--r--core/tests/coretests/AndroidManifest.xml7
-rw-r--r--core/tests/coretests/res/anim/reset_state_anim.xml14
-rw-r--r--core/tests/coretests/res/anim/test_animator.xml22
-rw-r--r--core/tests/coretests/res/anim/test_state_anim.xml14
-rw-r--r--core/tests/coretests/res/values-land/dimens.xml18
-rw-r--r--core/tests/coretests/res/values/dimens.xml19
-rw-r--r--core/tests/coretests/src/android/animation/AnimatorInflaterTest.java61
-rw-r--r--core/tests/coretests/src/android/animation/BasicAnimatorActivity.java3
-rw-r--r--core/tests/coretests/src/android/content/res/ConfigurationBoundResourceCacheTest.java224
-rw-r--r--core/tests/coretests/src/android/content/res/ResourceCacheActivity.java37
-rw-r--r--graphics/java/android/graphics/drawable/Drawable.java112
37 files changed, 1201 insertions, 195 deletions
diff --git a/api/current.txt b/api/current.txt
index f9ef046..074175b 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3052,9 +3052,10 @@ package android.animation {
method public android.graphics.Rect evaluate(float, android.graphics.Rect, android.graphics.Rect);
}
- public class StateListAnimator {
+ public class StateListAnimator implements java.lang.Cloneable {
ctor public StateListAnimator();
method public void addState(int[], android.animation.Animator);
+ method public android.animation.StateListAnimator clone();
method public void jumpToCurrentState();
}
@@ -35428,13 +35429,13 @@ package android.view.accessibility {
package android.view.animation {
- public class AccelerateDecelerateInterpolator implements android.view.animation.Interpolator {
+ public class AccelerateDecelerateInterpolator extends android.view.animation.BaseInterpolator {
ctor public AccelerateDecelerateInterpolator();
ctor public AccelerateDecelerateInterpolator(android.content.Context, android.util.AttributeSet);
method public float getInterpolation(float);
}
- public class AccelerateInterpolator implements android.view.animation.Interpolator {
+ public class AccelerateInterpolator extends android.view.animation.BaseInterpolator {
ctor public AccelerateInterpolator();
ctor public AccelerateInterpolator(float);
ctor public AccelerateInterpolator(android.content.Context, android.util.AttributeSet);
@@ -35536,14 +35537,14 @@ package android.view.animation {
method public static android.view.animation.Animation makeOutAnimation(android.content.Context, boolean);
}
- public class AnticipateInterpolator implements android.view.animation.Interpolator {
+ public class AnticipateInterpolator extends android.view.animation.BaseInterpolator {
ctor public AnticipateInterpolator();
ctor public AnticipateInterpolator(float);
ctor public AnticipateInterpolator(android.content.Context, android.util.AttributeSet);
method public float getInterpolation(float);
}
- public class AnticipateOvershootInterpolator implements android.view.animation.Interpolator {
+ public class AnticipateOvershootInterpolator extends android.view.animation.BaseInterpolator {
ctor public AnticipateOvershootInterpolator();
ctor public AnticipateOvershootInterpolator(float);
ctor public AnticipateOvershootInterpolator(float, float);
@@ -35551,19 +35552,23 @@ package android.view.animation {
method public float getInterpolation(float);
}
- public class BounceInterpolator implements android.view.animation.Interpolator {
+ public abstract class BaseInterpolator implements android.view.animation.Interpolator {
+ ctor public BaseInterpolator();
+ }
+
+ public class BounceInterpolator extends android.view.animation.BaseInterpolator {
ctor public BounceInterpolator();
ctor public BounceInterpolator(android.content.Context, android.util.AttributeSet);
method public float getInterpolation(float);
}
- public class CycleInterpolator implements android.view.animation.Interpolator {
+ public class CycleInterpolator extends android.view.animation.BaseInterpolator {
ctor public CycleInterpolator(float);
ctor public CycleInterpolator(android.content.Context, android.util.AttributeSet);
method public float getInterpolation(float);
}
- public class DecelerateInterpolator implements android.view.animation.Interpolator {
+ public class DecelerateInterpolator extends android.view.animation.BaseInterpolator {
ctor public DecelerateInterpolator();
ctor public DecelerateInterpolator(float);
ctor public DecelerateInterpolator(android.content.Context, android.util.AttributeSet);
@@ -35638,20 +35643,20 @@ package android.view.animation {
field public int index;
}
- public class LinearInterpolator implements android.view.animation.Interpolator {
+ public class LinearInterpolator extends android.view.animation.BaseInterpolator {
ctor public LinearInterpolator();
ctor public LinearInterpolator(android.content.Context, android.util.AttributeSet);
method public float getInterpolation(float);
}
- public class OvershootInterpolator implements android.view.animation.Interpolator {
+ public class OvershootInterpolator extends android.view.animation.BaseInterpolator {
ctor public OvershootInterpolator();
ctor public OvershootInterpolator(float);
ctor public OvershootInterpolator(android.content.Context, android.util.AttributeSet);
method public float getInterpolation(float);
}
- public class PathInterpolator implements android.view.animation.Interpolator {
+ public class PathInterpolator extends android.view.animation.BaseInterpolator {
ctor public PathInterpolator(android.graphics.Path);
ctor public PathInterpolator(float, float);
ctor public PathInterpolator(float, float, float, float);
diff --git a/core/java/android/animation/Animator.java b/core/java/android/animation/Animator.java
index 3720c81..da48709 100644
--- a/core/java/android/animation/Animator.java
+++ b/core/java/android/animation/Animator.java
@@ -16,6 +16,8 @@
package android.animation;
+import android.content.res.ConstantState;
+
import java.util.ArrayList;
/**
@@ -41,6 +43,18 @@ public abstract class Animator implements Cloneable {
boolean mPaused = false;
/**
+ * A set of flags which identify the type of configuration changes that can affect this
+ * Animator. Used by the Animator cache.
+ */
+ int mChangingConfigurations = 0;
+
+ /**
+ * If this animator is inflated from a constant state, keep a reference to it so that
+ * ConstantState will not be garbage collected until this animator is collected
+ */
+ private AnimatorConstantState mConstantState;
+
+ /**
* Starts this animation. If the animation has a nonzero startDelay, the animation will start
* running after that delay elapses. A non-delayed animation will have its initial
* value(s) set immediately, followed by calls to
@@ -295,25 +309,71 @@ public abstract class Animator implements Cloneable {
}
}
+ /**
+ * Return a mask of the configuration parameters for which this animator may change, requiring
+ * that it should be re-created from Resources. The default implementation returns whatever
+ * value was provided through setChangingConfigurations(int) or 0 by default.
+ *
+ * @return Returns a mask of the changing configuration parameters, as defined by
+ * {@link android.content.pm.ActivityInfo}.
+ * @see android.content.pm.ActivityInfo
+ * @hide
+ */
+ public int getChangingConfigurations() {
+ return mChangingConfigurations;
+ }
+
+ /**
+ * Set a mask of the configuration parameters for which this animator may change, requiring
+ * that it be re-created from resource.
+ *
+ * @param configs A mask of the changing configuration parameters, as
+ * defined by {@link android.content.pm.ActivityInfo}.
+ *
+ * @see android.content.pm.ActivityInfo
+ * @hide
+ */
+ public void setChangingConfigurations(int configs) {
+ mChangingConfigurations = configs;
+ }
+
+ /**
+ * Sets the changing configurations value to the union of the current changing configurations
+ * and the provided configs.
+ * This method is called while loading the animator.
+ * @hide
+ */
+ public void appendChangingConfigurations(int configs) {
+ mChangingConfigurations |= configs;
+ }
+
+ /**
+ * Return a {@link android.content.res.ConstantState} instance that holds the shared state of
+ * this Animator.
+ * <p>
+ * This constant state is used to create new instances of this animator when needed, instead
+ * of re-loading it from resources. Default implementation creates a new
+ * {@link AnimatorConstantState}. You can override this method to provide your custom logic or
+ * return null if you don't want this animator to be cached.
+ *
+ * @return The ConfigurationBoundResourceCache.BaseConstantState associated to this Animator.
+ * @see android.content.res.ConstantState
+ * @see #clone()
+ * @hide
+ */
+ public ConstantState<Animator> createConstantState() {
+ return new AnimatorConstantState(this);
+ }
+
@Override
public Animator clone() {
try {
final Animator anim = (Animator) super.clone();
if (mListeners != null) {
- ArrayList<AnimatorListener> oldListeners = mListeners;
- anim.mListeners = new ArrayList<AnimatorListener>();
- int numListeners = oldListeners.size();
- for (int i = 0; i < numListeners; ++i) {
- anim.mListeners.add(oldListeners.get(i));
- }
+ anim.mListeners = new ArrayList<AnimatorListener>(mListeners);
}
if (mPauseListeners != null) {
- ArrayList<AnimatorPauseListener> oldListeners = mPauseListeners;
- anim.mPauseListeners = new ArrayList<AnimatorPauseListener>();
- int numListeners = oldListeners.size();
- for (int i = 0; i < numListeners; ++i) {
- anim.mPauseListeners.add(oldListeners.get(i));
- }
+ anim.mPauseListeners = new ArrayList<AnimatorPauseListener>(mPauseListeners);
}
return anim;
} catch (CloneNotSupportedException e) {
@@ -469,4 +529,35 @@ public abstract class Animator implements Cloneable {
public void setAllowRunningAsynchronously(boolean mayRunAsync) {
// It is up to subclasses to support this, if they can.
}
+
+ /**
+ * Creates a {@link ConstantState} which holds changing configurations information associated
+ * with the given Animator.
+ * <p>
+ * When {@link #newInstance()} is called, default implementation clones the Animator.
+ */
+ private static class AnimatorConstantState extends ConstantState<Animator> {
+
+ final Animator mAnimator;
+ int mChangingConf;
+
+ public AnimatorConstantState(Animator animator) {
+ mAnimator = animator;
+ // ensure a reference back to here so that constante state is not gc'ed.
+ mAnimator.mConstantState = this;
+ mChangingConf = mAnimator.getChangingConfigurations();
+ }
+
+ @Override
+ public int getChangingConfigurations() {
+ return mChangingConf;
+ }
+
+ @Override
+ public Animator newInstance() {
+ final Animator clone = mAnimator.clone();
+ clone.mConstantState = this;
+ return clone;
+ }
+ }
}
diff --git a/core/java/android/animation/AnimatorInflater.java b/core/java/android/animation/AnimatorInflater.java
index 25417ed..688d7e4 100644
--- a/core/java/android/animation/AnimatorInflater.java
+++ b/core/java/android/animation/AnimatorInflater.java
@@ -16,6 +16,8 @@
package android.animation;
import android.content.Context;
+import android.content.res.ConfigurationBoundResourceCache;
+import android.content.res.ConstantState;
import android.content.res.Resources;
import android.content.res.Resources.NotFoundException;
import android.content.res.Resources.Theme;
@@ -30,6 +32,8 @@ import android.util.TypedValue;
import android.util.Xml;
import android.view.InflateException;
import android.view.animation.AnimationUtils;
+import android.view.animation.BaseInterpolator;
+import android.view.animation.Interpolator;
import com.android.internal.R;
@@ -67,6 +71,9 @@ public class AnimatorInflater {
private static final boolean DBG_ANIMATOR_INFLATER = false;
+ // used to calculate changing configs for resource references
+ private static final TypedValue sTmpTypedValue = new TypedValue();
+
/**
* Loads an {@link Animator} object from a resource
*
@@ -98,11 +105,34 @@ public class AnimatorInflater {
/** @hide */
public static Animator loadAnimator(Resources resources, Theme theme, int id,
float pathErrorScale) throws NotFoundException {
-
+ final ConfigurationBoundResourceCache<Animator> animatorCache = resources
+ .getAnimatorCache();
+ Animator animator = animatorCache.get(id, theme);
+ if (animator != null) {
+ if (DBG_ANIMATOR_INFLATER) {
+ Log.d(TAG, "loaded animator from cache, " + resources.getResourceName(id));
+ }
+ return animator;
+ } else if (DBG_ANIMATOR_INFLATER) {
+ Log.d(TAG, "cache miss for animator " + resources.getResourceName(id));
+ }
XmlResourceParser parser = null;
try {
parser = resources.getAnimation(id);
- return createAnimatorFromXml(resources, theme, parser, pathErrorScale);
+ animator = createAnimatorFromXml(resources, theme, parser, pathErrorScale);
+ if (animator != null) {
+ animator.appendChangingConfigurations(getChangingConfigs(resources, id));
+ final ConstantState<Animator> constantState = animator.createConstantState();
+ if (constantState != null) {
+ if (DBG_ANIMATOR_INFLATER) {
+ Log.d(TAG, "caching animator for res " + resources.getResourceName(id));
+ }
+ animatorCache.put(id, theme, constantState);
+ // create a new animator so that cached version is never used by the user
+ animator = constantState.newInstance(resources, theme);
+ }
+ }
+ return animator;
} catch (XmlPullParserException ex) {
Resources.NotFoundException rnf =
new Resources.NotFoundException("Can't load animation resource ID #0x" +
@@ -122,10 +152,29 @@ public class AnimatorInflater {
public static StateListAnimator loadStateListAnimator(Context context, int id)
throws NotFoundException {
+ final Resources resources = context.getResources();
+ final ConfigurationBoundResourceCache<StateListAnimator> cache = resources
+ .getStateListAnimatorCache();
+ final Theme theme = context.getTheme();
+ StateListAnimator animator = cache.get(id, theme);
+ if (animator != null) {
+ return animator;
+ }
XmlResourceParser parser = null;
try {
- parser = context.getResources().getAnimation(id);
- return createStateListAnimatorFromXml(context, parser, Xml.asAttributeSet(parser));
+ parser = resources.getAnimation(id);
+ animator = createStateListAnimatorFromXml(context, parser, Xml.asAttributeSet(parser));
+ if (animator != null) {
+ animator.appendChangingConfigurations(getChangingConfigs(resources, id));
+ final ConstantState<StateListAnimator> constantState = animator
+ .createConstantState();
+ if (constantState != null) {
+ cache.put(id, theme, constantState);
+ // return a clone so that the animator in constant state is never used.
+ animator = constantState.newInstance(resources, theme);
+ }
+ }
+ return animator;
} catch (XmlPullParserException ex) {
Resources.NotFoundException rnf =
new Resources.NotFoundException(
@@ -172,14 +221,13 @@ public class AnimatorInflater {
for (int i = 0; i < attributeCount; i++) {
int attrName = attributeSet.getAttributeNameResource(i);
if (attrName == R.attr.animation) {
- animator = loadAnimator(context,
- attributeSet.getAttributeResourceValue(i, 0));
+ final int animId = attributeSet.getAttributeResourceValue(i, 0);
+ animator = loadAnimator(context, animId);
} else {
states[stateIndex++] =
attributeSet.getAttributeBooleanValue(i, false) ?
attrName : -attrName;
}
-
}
if (animator == null) {
animator = createAnimatorFromXml(context.getResources(),
@@ -192,7 +240,6 @@ public class AnimatorInflater {
}
stateListAnimator
.addState(StateSet.trimStateSet(states, stateIndex), animator);
-
}
break;
}
@@ -508,7 +555,6 @@ public class AnimatorInflater {
private static Animator createAnimatorFromXml(Resources res, Theme theme, XmlPullParser parser,
AttributeSet attrs, AnimatorSet parent, int sequenceOrdering, float pixelSize)
throws XmlPullParserException, IOException {
-
Animator anim = null;
ArrayList<Animator> childAnims = null;
@@ -537,8 +583,8 @@ public class AnimatorInflater {
} else {
a = res.obtainAttributes(attrs, R.styleable.AnimatorSet);
}
- int ordering = a.getInt(R.styleable.AnimatorSet_ordering,
- TOGETHER);
+ anim.appendChangingConfigurations(a.getChangingConfigurations());
+ int ordering = a.getInt(R.styleable.AnimatorSet_ordering, TOGETHER);
createAnimatorFromXml(res, theme, parser, attrs, (AnimatorSet) anim, ordering,
pixelSize);
a.recycle();
@@ -565,7 +611,6 @@ public class AnimatorInflater {
parent.playSequentially(animsArray);
}
}
-
return anim;
}
@@ -591,7 +636,6 @@ public class AnimatorInflater {
private static ValueAnimator loadAnimator(Resources res, Theme theme,
AttributeSet attrs, ValueAnimator anim, float pathErrorScale)
throws NotFoundException {
-
TypedArray arrayAnimator = null;
TypedArray arrayObjectAnimator = null;
@@ -609,25 +653,37 @@ public class AnimatorInflater {
} else {
arrayObjectAnimator = res.obtainAttributes(attrs, R.styleable.PropertyAnimator);
}
+ anim.appendChangingConfigurations(arrayObjectAnimator.getChangingConfigurations());
}
if (anim == null) {
anim = new ValueAnimator();
}
+ anim.appendChangingConfigurations(arrayAnimator.getChangingConfigurations());
parseAnimatorFromTypeArray(anim, arrayAnimator, arrayObjectAnimator, pathErrorScale);
- final int resID =
- arrayAnimator.getResourceId(R.styleable.Animator_interpolator, 0);
+ final int resID = arrayAnimator.getResourceId(R.styleable.Animator_interpolator, 0);
if (resID > 0) {
- anim.setInterpolator(AnimationUtils.loadInterpolator(res, theme, resID));
+ final Interpolator interpolator = AnimationUtils.loadInterpolator(res, theme, resID);
+ if (interpolator instanceof BaseInterpolator) {
+ anim.appendChangingConfigurations(
+ ((BaseInterpolator) interpolator).getChangingConfiguration());
+ }
+ anim.setInterpolator(interpolator);
}
arrayAnimator.recycle();
if (arrayObjectAnimator != null) {
arrayObjectAnimator.recycle();
}
-
return anim;
}
+
+ private static int getChangingConfigs(Resources resources, int id) {
+ synchronized (sTmpTypedValue) {
+ resources.getValue(id, sTmpTypedValue, true);
+ return sTmpTypedValue.changingConfigurations;
+ }
+ }
}
diff --git a/core/java/android/animation/AnimatorSet.java b/core/java/android/animation/AnimatorSet.java
index 0aa8fdd..92762c3 100644
--- a/core/java/android/animation/AnimatorSet.java
+++ b/core/java/android/animation/AnimatorSet.java
@@ -241,6 +241,19 @@ public final class AnimatorSet extends Animator {
}
/**
+ * @hide
+ */
+ @Override
+ public int getChangingConfigurations() {
+ int conf = super.getChangingConfigurations();
+ final int nodeCount = mNodes.size();
+ for (int i = 0; i < nodeCount; i ++) {
+ conf |= mNodes.get(i).animation.getChangingConfigurations();
+ }
+ return conf;
+ }
+
+ /**
* Sets the TimeInterpolator for all current {@link #getChildAnimations() child animations}
* of this AnimatorSet. The default value is null, which means that no interpolator
* is set on this AnimatorSet. Setting the interpolator to any non-null value
@@ -628,23 +641,25 @@ public final class AnimatorSet extends Animator {
* manually, as we clone each Node (and its animation). The clone will then be sorted,
* and will populate any appropriate lists, when it is started.
*/
+ final int nodeCount = mNodes.size();
anim.mNeedsSort = true;
anim.mTerminated = false;
anim.mStarted = false;
anim.mPlayingSet = new ArrayList<Animator>();
anim.mNodeMap = new HashMap<Animator, Node>();
- anim.mNodes = new ArrayList<Node>();
- anim.mSortedNodes = new ArrayList<Node>();
+ anim.mNodes = new ArrayList<Node>(nodeCount);
+ anim.mSortedNodes = new ArrayList<Node>(nodeCount);
anim.mReversible = mReversible;
anim.mSetListener = null;
// Walk through the old nodes list, cloning each node and adding it to the new nodemap.
// One problem is that the old node dependencies point to nodes in the old AnimatorSet.
// We need to track the old/new nodes in order to reconstruct the dependencies in the clone.
- HashMap<Node, Node> nodeCloneMap = new HashMap<Node, Node>(); // <old, new>
- for (Node node : mNodes) {
+
+ for (int n = 0; n < nodeCount; n++) {
+ final Node node = mNodes.get(n);
Node nodeClone = node.clone();
- nodeCloneMap.put(node, nodeClone);
+ node.mTmpClone = nodeClone;
anim.mNodes.add(nodeClone);
anim.mNodeMap.put(nodeClone.animation, nodeClone);
// Clear out the dependencies in the clone; we'll set these up manually later
@@ -652,40 +667,50 @@ public final class AnimatorSet extends Animator {
nodeClone.tmpDependencies = null;
nodeClone.nodeDependents = null;
nodeClone.nodeDependencies = null;
+
// clear out any listeners that were set up by the AnimatorSet; these will
// be set up when the clone's nodes are sorted
- ArrayList<AnimatorListener> cloneListeners = nodeClone.animation.getListeners();
+ final ArrayList<AnimatorListener> cloneListeners = nodeClone.animation.getListeners();
if (cloneListeners != null) {
- ArrayList<AnimatorListener> listenersToRemove = null;
- for (AnimatorListener listener : cloneListeners) {
+ for (int i = cloneListeners.size() - 1; i >= 0; i--) {
+ final AnimatorListener listener = cloneListeners.get(i);
if (listener instanceof AnimatorSetListener) {
- if (listenersToRemove == null) {
- listenersToRemove = new ArrayList<AnimatorListener>();
- }
- listenersToRemove.add(listener);
- }
- }
- if (listenersToRemove != null) {
- for (AnimatorListener listener : listenersToRemove) {
- cloneListeners.remove(listener);
+ cloneListeners.remove(i);
}
}
}
}
// Now that we've cloned all of the nodes, we're ready to walk through their
// dependencies, mapping the old dependencies to the new nodes
- for (Node node : mNodes) {
- Node nodeClone = nodeCloneMap.get(node);
+ for (int n = 0; n < nodeCount; n++) {
+ final Node node = mNodes.get(n);
+ final Node clone = node.mTmpClone;
if (node.dependencies != null) {
- for (Dependency dependency : node.dependencies) {
- Node clonedDependencyNode = nodeCloneMap.get(dependency.node);
- Dependency cloneDependency = new Dependency(clonedDependencyNode,
+ clone.dependencies = new ArrayList<Dependency>(node.dependencies.size());
+ final int depSize = node.dependencies.size();
+ for (int i = 0; i < depSize; i ++) {
+ final Dependency dependency = node.dependencies.get(i);
+ Dependency cloneDependency = new Dependency(dependency.node.mTmpClone,
dependency.rule);
- nodeClone.addDependency(cloneDependency);
+ clone.dependencies.add(cloneDependency);
+ }
+ }
+ if (node.nodeDependents != null) {
+ clone.nodeDependents = new ArrayList<Node>(node.nodeDependents.size());
+ for (Node dep : node.nodeDependents) {
+ clone.nodeDependents.add(dep.mTmpClone);
+ }
+ }
+ if (node.nodeDependencies != null) {
+ clone.nodeDependencies = new ArrayList<Node>(node.nodeDependencies.size());
+ for (Node dep : node.nodeDependencies) {
+ clone.nodeDependencies.add(dep.mTmpClone);
}
}
}
-
+ for (int n = 0; n < nodeCount; n++) {
+ mNodes.get(n).mTmpClone = null;
+ }
return anim;
}
@@ -1017,6 +1042,11 @@ public final class AnimatorSet extends Animator {
public boolean done = false;
/**
+ * Temporary field to hold the clone in AnimatorSet#clone. Cleaned after clone is complete
+ */
+ private Node mTmpClone = null;
+
+ /**
* Constructs the Node with the animation that it encapsulates. A Node has no
* dependencies by default; dependencies are added via the addDependency()
* method.
diff --git a/core/java/android/animation/FloatKeyframeSet.java b/core/java/android/animation/FloatKeyframeSet.java
index 12e5862..abac246 100644
--- a/core/java/android/animation/FloatKeyframeSet.java
+++ b/core/java/android/animation/FloatKeyframeSet.java
@@ -19,6 +19,7 @@ package android.animation;
import android.animation.Keyframe.FloatKeyframe;
import java.util.ArrayList;
+import java.util.List;
/**
* This class holds a collection of FloatKeyframe objects and is called by ValueAnimator to calculate
@@ -47,8 +48,8 @@ class FloatKeyframeSet extends KeyframeSet implements Keyframes.FloatKeyframes {
@Override
public FloatKeyframeSet clone() {
- ArrayList<Keyframe> keyframes = mKeyframes;
- int numKeyframes = mKeyframes.size();
+ final List<Keyframe> keyframes = mKeyframes;
+ final int numKeyframes = mKeyframes.size();
FloatKeyframe[] newKeyframes = new FloatKeyframe[numKeyframes];
for (int i = 0; i < numKeyframes; ++i) {
newKeyframes[i] = (FloatKeyframe) keyframes.get(i).clone();
diff --git a/core/java/android/animation/IntKeyframeSet.java b/core/java/android/animation/IntKeyframeSet.java
index 7a5b0ec..0ec5138 100644
--- a/core/java/android/animation/IntKeyframeSet.java
+++ b/core/java/android/animation/IntKeyframeSet.java
@@ -19,6 +19,7 @@ package android.animation;
import android.animation.Keyframe.IntKeyframe;
import java.util.ArrayList;
+import java.util.List;
/**
* This class holds a collection of IntKeyframe objects and is called by ValueAnimator to calculate
@@ -47,7 +48,7 @@ class IntKeyframeSet extends KeyframeSet implements Keyframes.IntKeyframes {
@Override
public IntKeyframeSet clone() {
- ArrayList<Keyframe> keyframes = mKeyframes;
+ List<Keyframe> keyframes = mKeyframes;
int numKeyframes = mKeyframes.size();
IntKeyframe[] newKeyframes = new IntKeyframe[numKeyframes];
for (int i = 0; i < numKeyframes; ++i) {
diff --git a/core/java/android/animation/KeyframeSet.java b/core/java/android/animation/KeyframeSet.java
index 8d15db2..0e99bff 100644
--- a/core/java/android/animation/KeyframeSet.java
+++ b/core/java/android/animation/KeyframeSet.java
@@ -18,6 +18,8 @@ package android.animation;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
+
import android.animation.Keyframe.IntKeyframe;
import android.animation.Keyframe.FloatKeyframe;
import android.animation.Keyframe.ObjectKeyframe;
@@ -36,16 +38,16 @@ class KeyframeSet implements Keyframes {
Keyframe mFirstKeyframe;
Keyframe mLastKeyframe;
TimeInterpolator mInterpolator; // only used in the 2-keyframe case
- ArrayList<Keyframe> mKeyframes; // only used when there are not 2 keyframes
+ List<Keyframe> mKeyframes; // only used when there are not 2 keyframes
TypeEvaluator mEvaluator;
public KeyframeSet(Keyframe... keyframes) {
mNumKeyframes = keyframes.length;
- mKeyframes = new ArrayList<Keyframe>();
- mKeyframes.addAll(Arrays.asList(keyframes));
- mFirstKeyframe = mKeyframes.get(0);
- mLastKeyframe = mKeyframes.get(mNumKeyframes - 1);
+ // immutable list
+ mKeyframes = Arrays.asList(keyframes);
+ mFirstKeyframe = keyframes[0];
+ mLastKeyframe = keyframes[mNumKeyframes - 1];
mInterpolator = mLastKeyframe.getInterpolator();
}
@@ -57,7 +59,7 @@ class KeyframeSet implements Keyframes {
public void invalidateCache() {
}
- public ArrayList<Keyframe> getKeyframes() {
+ public List<Keyframe> getKeyframes() {
return mKeyframes;
}
@@ -177,9 +179,9 @@ class KeyframeSet implements Keyframes {
@Override
public KeyframeSet clone() {
- ArrayList<Keyframe> keyframes = mKeyframes;
+ List<Keyframe> keyframes = mKeyframes;
int numKeyframes = mKeyframes.size();
- Keyframe[] newKeyframes = new Keyframe[numKeyframes];
+ final Keyframe[] newKeyframes = new Keyframe[numKeyframes];
for (int i = 0; i < numKeyframes; ++i) {
newKeyframes[i] = keyframes.get(i).clone();
}
diff --git a/core/java/android/animation/Keyframes.java b/core/java/android/animation/Keyframes.java
index 6611c6c..c921466 100644
--- a/core/java/android/animation/Keyframes.java
+++ b/core/java/android/animation/Keyframes.java
@@ -16,6 +16,7 @@
package android.animation;
import java.util.ArrayList;
+import java.util.List;
/**
* This interface abstracts a collection of Keyframe objects and is called by
@@ -62,7 +63,7 @@ interface Keyframes extends Cloneable {
* @return A list of all Keyframes contained by this. This may return null if this is
* not made up of Keyframes.
*/
- ArrayList<Keyframe> getKeyframes();
+ List<Keyframe> getKeyframes();
Keyframes clone();
diff --git a/core/java/android/animation/PropertyValuesHolder.java b/core/java/android/animation/PropertyValuesHolder.java
index d372933..97426c3 100644
--- a/core/java/android/animation/PropertyValuesHolder.java
+++ b/core/java/android/animation/PropertyValuesHolder.java
@@ -27,6 +27,7 @@ import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
@@ -791,7 +792,7 @@ public class PropertyValuesHolder implements Cloneable {
// check to make sure that mProperty is on the class of target
try {
Object testValue = null;
- ArrayList<Keyframe> keyframes = mKeyframes.getKeyframes();
+ List<Keyframe> keyframes = mKeyframes.getKeyframes();
int keyframeCount = keyframes == null ? 0 : keyframes.size();
for (int i = 0; i < keyframeCount; i++) {
Keyframe kf = keyframes.get(i);
@@ -814,7 +815,7 @@ public class PropertyValuesHolder implements Cloneable {
if (mSetter == null) {
setupSetter(targetClass);
}
- ArrayList<Keyframe> keyframes = mKeyframes.getKeyframes();
+ List<Keyframe> keyframes = mKeyframes.getKeyframes();
int keyframeCount = keyframes == null ? 0 : keyframes.size();
for (int i = 0; i < keyframeCount; i++) {
Keyframe kf = keyframes.get(i);
@@ -890,7 +891,7 @@ public class PropertyValuesHolder implements Cloneable {
* @param target The object which holds the start values that should be set.
*/
void setupStartValue(Object target) {
- ArrayList<Keyframe> keyframes = mKeyframes.getKeyframes();
+ List<Keyframe> keyframes = mKeyframes.getKeyframes();
if (!keyframes.isEmpty()) {
setupValue(target, keyframes.get(0));
}
@@ -905,7 +906,7 @@ public class PropertyValuesHolder implements Cloneable {
* @param target The object which holds the start values that should be set.
*/
void setupEndValue(Object target) {
- ArrayList<Keyframe> keyframes = mKeyframes.getKeyframes();
+ List<Keyframe> keyframes = mKeyframes.getKeyframes();
if (!keyframes.isEmpty()) {
setupValue(target, keyframes.get(keyframes.size() - 1));
}
diff --git a/core/java/android/animation/StateListAnimator.java b/core/java/android/animation/StateListAnimator.java
index 7256a06..d49e914 100644
--- a/core/java/android/animation/StateListAnimator.java
+++ b/core/java/android/animation/StateListAnimator.java
@@ -16,6 +16,7 @@
package android.animation;
+import android.content.res.ConstantState;
import android.util.StateSet;
import android.view.View;
@@ -44,25 +45,31 @@ import java.util.ArrayList;
* @attr ref android.R.styleable#DrawableStates_state_pressed
* @attr ref android.R.styleable#StateListAnimatorItem_animation
*/
-public class StateListAnimator {
-
- private final ArrayList<Tuple> mTuples = new ArrayList<Tuple>();
+public class StateListAnimator implements Cloneable {
+ private ArrayList<Tuple> mTuples = new ArrayList<Tuple>();
private Tuple mLastMatch = null;
-
private Animator mRunningAnimator = null;
-
private WeakReference<View> mViewRef;
+ private StateListAnimatorConstantState mConstantState;
+ private AnimatorListenerAdapter mAnimatorListener;
+ private int mChangingConfigurations;
- private AnimatorListenerAdapter mAnimatorListener = new AnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(Animator animation) {
- animation.setTarget(null);
- if (mRunningAnimator == animation) {
- mRunningAnimator = null;
+ public StateListAnimator() {
+ initAnimatorListener();
+ }
+
+ private void initAnimatorListener() {
+ mAnimatorListener = new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ animation.setTarget(null);
+ if (mRunningAnimator == animation) {
+ mRunningAnimator = null;
+ }
}
- }
- };
+ };
+ }
/**
* Associates the given animator with the provided drawable state specs so that it will be run
@@ -75,6 +82,7 @@ public class StateListAnimator {
Tuple tuple = new Tuple(specs, animator);
tuple.mAnimator.addListener(mAnimatorListener);
mTuples.add(tuple);
+ mChangingConfigurations |= animator.getChangingConfigurations();
}
/**
@@ -118,12 +126,35 @@ public class StateListAnimator {
for (int i = 0; i < size; i++) {
mTuples.get(i).mAnimator.setTarget(null);
}
-
mViewRef = null;
mLastMatch = null;
mRunningAnimator = null;
}
+ @Override
+ public StateListAnimator clone() {
+ try {
+ StateListAnimator clone = (StateListAnimator) super.clone();
+ clone.mTuples = new ArrayList<Tuple>(mTuples.size());
+ clone.mLastMatch = null;
+ clone.mRunningAnimator = null;
+ clone.mViewRef = null;
+ clone.mAnimatorListener = null;
+ clone.initAnimatorListener();
+ final int tupleSize = mTuples.size();
+ for (int i = 0; i < tupleSize; i++) {
+ final Tuple tuple = mTuples.get(i);
+ final Animator animatorClone = tuple.mAnimator.clone();
+ animatorClone.removeListener(mAnimatorListener);
+ clone.addState(tuple.mSpecs, animatorClone);
+ }
+ clone.setChangingConfigurations(getChangingConfigurations());
+ return clone;
+ } catch (CloneNotSupportedException e) {
+ throw new AssertionError("cannot clone state list animator", e);
+ }
+ }
+
/**
* Called by View
* @hide
@@ -182,6 +213,63 @@ public class StateListAnimator {
}
/**
+ * Return a mask of the configuration parameters for which this animator may change, requiring
+ * that it be re-created. The default implementation returns whatever was provided through
+ * {@link #setChangingConfigurations(int)} or 0 by default.
+ *
+ * @return Returns a mask of the changing configuration parameters, as defined by
+ * {@link android.content.pm.ActivityInfo}.
+ *
+ * @see android.content.pm.ActivityInfo
+ * @hide
+ */
+ public int getChangingConfigurations() {
+ return mChangingConfigurations;
+ }
+
+ /**
+ * Set a mask of the configuration parameters for which this animator may change, requiring
+ * that it should be recreated from resources instead of being cloned.
+ *
+ * @param configs A mask of the changing configuration parameters, as
+ * defined by {@link android.content.pm.ActivityInfo}.
+ *
+ * @see android.content.pm.ActivityInfo
+ * @hide
+ */
+ public void setChangingConfigurations(int configs) {
+ mChangingConfigurations = configs;
+ }
+
+ /**
+ * Sets the changing configurations value to the union of the current changing configurations
+ * and the provided configs.
+ * This method is called while loading the animator.
+ * @hide
+ */
+ public void appendChangingConfigurations(int configs) {
+ mChangingConfigurations |= configs;
+ }
+
+ /**
+ * Return a {@link android.content.res.ConstantState} instance that holds the shared state of
+ * this Animator.
+ * <p>
+ * This constant state is used to create new instances of this animator when needed. Default
+ * implementation creates a new {@link StateListAnimatorConstantState}. You can override this
+ * method to provide your custom logic or return null if you don't want this animator to be
+ * cached.
+ *
+ * @return The {@link android.content.res.ConstantState} associated to this Animator.
+ * @see android.content.res.ConstantState
+ * @see #clone()
+ * @hide
+ */
+ public ConstantState<StateListAnimator> createConstantState() {
+ return new StateListAnimatorConstantState(this);
+ }
+
+ /**
* @hide
*/
public static class Tuple {
@@ -209,4 +297,36 @@ public class StateListAnimator {
return mAnimator;
}
}
+
+ /**
+ * Creates a constant state which holds changing configurations information associated with the
+ * given Animator.
+ * <p>
+ * When new instance is called, default implementation clones the Animator.
+ */
+ private static class StateListAnimatorConstantState
+ extends ConstantState<StateListAnimator> {
+
+ final StateListAnimator mAnimator;
+
+ int mChangingConf;
+
+ public StateListAnimatorConstantState(StateListAnimator animator) {
+ mAnimator = animator;
+ mAnimator.mConstantState = this;
+ mChangingConf = mAnimator.getChangingConfigurations();
+ }
+
+ @Override
+ public int getChangingConfigurations() {
+ return mChangingConf;
+ }
+
+ @Override
+ public StateListAnimator newInstance() {
+ final StateListAnimator clone = mAnimator.clone();
+ clone.mConstantState = this;
+ return clone;
+ }
+ }
}
diff --git a/core/java/android/animation/ValueAnimator.java b/core/java/android/animation/ValueAnimator.java
index 0d17d67..07f79b8 100644
--- a/core/java/android/animation/ValueAnimator.java
+++ b/core/java/android/animation/ValueAnimator.java
@@ -16,6 +16,7 @@
package android.animation;
+import android.content.res.ConfigurationBoundResourceCache;
import android.os.Looper;
import android.os.Trace;
import android.util.AndroidRuntimeException;
@@ -1289,12 +1290,7 @@ public class ValueAnimator extends Animator {
public ValueAnimator clone() {
final ValueAnimator anim = (ValueAnimator) super.clone();
if (mUpdateListeners != null) {
- ArrayList<AnimatorUpdateListener> oldListeners = mUpdateListeners;
- anim.mUpdateListeners = new ArrayList<AnimatorUpdateListener>();
- int numListeners = oldListeners.size();
- for (int i = 0; i < numListeners; ++i) {
- anim.mUpdateListeners.add(oldListeners.get(i));
- }
+ anim.mUpdateListeners = new ArrayList<AnimatorUpdateListener>(mUpdateListeners);
}
anim.mSeekTime = -1;
anim.mPlayingBackwards = false;
diff --git a/core/java/android/content/res/ConfigurationBoundResourceCache.java b/core/java/android/content/res/ConfigurationBoundResourceCache.java
new file mode 100644
index 0000000..cde7e84
--- /dev/null
+++ b/core/java/android/content/res/ConfigurationBoundResourceCache.java
@@ -0,0 +1,138 @@
+/*
+* 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;
+
+import android.util.ArrayMap;
+import android.util.LongSparseArray;
+import java.lang.ref.WeakReference;
+
+/**
+ * A Cache class which can be used to cache resource objects that are easy to clone but more
+ * expensive to inflate.
+ * @hide
+ */
+public class ConfigurationBoundResourceCache<T> {
+
+ private final ArrayMap<String, LongSparseArray<WeakReference<ConstantState<T>>>> mCache =
+ new ArrayMap<String, LongSparseArray<WeakReference<ConstantState<T>>>>();
+
+ final Resources mResources;
+
+ /**
+ * Creates a Resource cache for the given Resources instance.
+ *
+ * @param resources The Resource which can be used when creating new instances.
+ */
+ public ConfigurationBoundResourceCache(Resources resources) {
+ mResources = resources;
+ }
+
+ /**
+ * Adds a new item to the cache.
+ *
+ * @param key A custom key that uniquely identifies the resource.
+ * @param theme The Theme instance where this resource was loaded.
+ * @param constantState The constant state that can create new instances of the resource.
+ *
+ */
+ public void put(long key, Resources.Theme theme, ConstantState<T> constantState) {
+ if (constantState == null) {
+ return;
+ }
+ final String themeKey = theme == null ? "" : theme.getKey();
+ LongSparseArray<WeakReference<ConstantState<T>>> themedCache;
+ synchronized (this) {
+ themedCache = mCache.get(themeKey);
+ if (themedCache == null) {
+ themedCache = new LongSparseArray<WeakReference<ConstantState<T>>>(1);
+ mCache.put(themeKey, themedCache);
+ }
+ themedCache.put(key, new WeakReference<ConstantState<T>>(constantState));
+ }
+ }
+
+ /**
+ * If the resource is cached, creates a new instance of it and returns.
+ *
+ * @param key The long key which can be used to uniquely identify the resource.
+ * @param theme The The Theme instance where we want to load this resource.
+ *
+ * @return If this resources was loaded before, returns a new instance of it. Otherwise, returns
+ * null.
+ */
+ public T get(long key, Resources.Theme theme) {
+ final String themeKey = theme != null ? theme.getKey() : "";
+ final LongSparseArray<WeakReference<ConstantState<T>>> themedCache;
+ final WeakReference<ConstantState<T>> wr;
+ synchronized (this) {
+ themedCache = mCache.get(themeKey);
+ if (themedCache == null) {
+ return null;
+ }
+ wr = themedCache.get(key);
+ }
+ if (wr == null) {
+ return null;
+ }
+ final ConstantState entry = wr.get();
+ if (entry != null) {
+ return (T) entry.newInstance(mResources, theme);
+ } else { // our entry has been purged
+ synchronized (this) {
+ // there is a potential race condition here where this entry may be put in
+ // another thread. But we prefer it to minimize lock duration
+ themedCache.delete(key);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Users of ConfigurationBoundResourceCache must call this method whenever a configuration
+ * change happens. On this callback, the cache invalidates all resources that are not valid
+ * anymore.
+ *
+ * @param configChanges The configuration changes
+ */
+ public void onConfigurationChange(final int configChanges) {
+ synchronized (this) {
+ final int size = mCache.size();
+ for (int i = size - 1; i >= 0; i--) {
+ final LongSparseArray<WeakReference<ConstantState<T>>>
+ themeCache = mCache.valueAt(i);
+ onConfigurationChangeInt(themeCache, configChanges);
+ if (themeCache.size() == 0) {
+ mCache.removeAt(i);
+ }
+ }
+ }
+ }
+
+ private void onConfigurationChangeInt(
+ final LongSparseArray<WeakReference<ConstantState<T>>> themeCache,
+ final int configChanges) {
+ final int size = themeCache.size();
+ for (int i = size - 1; i >= 0; i--) {
+ final WeakReference<ConstantState<T>> wr = themeCache.valueAt(i);
+ final ConstantState<T> constantState = wr.get();
+ if (constantState == null || Configuration.needNewResources(
+ configChanges, constantState.getChangingConfigurations())) {
+ themeCache.removeAt(i);
+ }
+ }
+ }
+
+}
diff --git a/core/java/android/content/res/ConstantState.java b/core/java/android/content/res/ConstantState.java
new file mode 100644
index 0000000..ee609df
--- /dev/null
+++ b/core/java/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}.
+ * <p>
+ * 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<T> {
+
+ /**
+ * 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/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index e34ce3e..6e9efe1 100644
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -16,6 +16,8 @@
package android.content.res;
+import android.animation.Animator;
+import android.animation.StateListAnimator;
import android.util.Pools.SynchronizedPool;
import android.view.ViewDebug;
import com.android.internal.util.XmlUtils;
@@ -115,6 +117,10 @@ public class Resources {
new ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>>();
private final LongSparseArray<WeakReference<ColorStateList>> mColorStateListCache =
new LongSparseArray<WeakReference<ColorStateList>>();
+ private final ConfigurationBoundResourceCache<Animator> mAnimatorCache =
+ new ConfigurationBoundResourceCache<Animator>(this);
+ private final ConfigurationBoundResourceCache<StateListAnimator> mStateListAnimatorCache =
+ new ConfigurationBoundResourceCache<StateListAnimator>(this);
private TypedValue mTmpValue = new TypedValue();
private boolean mPreloading;
@@ -183,6 +189,24 @@ public class Resources {
}
/**
+ * Used by AnimatorInflater.
+ *
+ * @hide
+ */
+ public ConfigurationBoundResourceCache<Animator> getAnimatorCache() {
+ return mAnimatorCache;
+ }
+
+ /**
+ * Used by AnimatorInflater.
+ *
+ * @hide
+ */
+ public ConfigurationBoundResourceCache<StateListAnimator> getStateListAnimatorCache() {
+ return mStateListAnimatorCache;
+ }
+
+ /**
* This exception is thrown by the resource APIs when a requested resource
* can not be found.
*/
@@ -1761,23 +1785,7 @@ public class Resources {
// the framework.
mCompatibilityInfo.applyToDisplayMetrics(mMetrics);
- int configChanges = 0xfffffff;
- if (config != null) {
- mTmpConfig.setTo(config);
- int density = config.densityDpi;
- if (density == Configuration.DENSITY_DPI_UNDEFINED) {
- density = mMetrics.noncompatDensityDpi;
- }
-
- mCompatibilityInfo.applyToConfiguration(density, mTmpConfig);
-
- if (mTmpConfig.locale == null) {
- mTmpConfig.locale = Locale.getDefault();
- mTmpConfig.setLayoutDirection(mTmpConfig.locale);
- }
- configChanges = mConfiguration.updateFrom(mTmpConfig);
- configChanges = ActivityInfo.activityInfoConfigToNative(configChanges);
- }
+ int configChanges = calcConfigChanges(config);
if (mConfiguration.locale == null) {
mConfiguration.locale = Locale.getDefault();
mConfiguration.setLayoutDirection(mConfiguration.locale);
@@ -1825,6 +1833,8 @@ public class Resources {
clearDrawableCachesLocked(mDrawableCache, configChanges);
clearDrawableCachesLocked(mColorDrawableCache, configChanges);
+ mAnimatorCache.onConfigurationChange(configChanges);
+ mStateListAnimatorCache.onConfigurationChange(configChanges);
mColorStateListCache.clear();
@@ -1837,6 +1847,30 @@ public class Resources {
}
}
+ /**
+ * Called by ConfigurationBoundResourceCacheTest via reflection.
+ */
+ private int calcConfigChanges(Configuration config) {
+ int configChanges = 0xfffffff;
+ if (config != null) {
+ mTmpConfig.setTo(config);
+ int density = config.densityDpi;
+ if (density == Configuration.DENSITY_DPI_UNDEFINED) {
+ density = mMetrics.noncompatDensityDpi;
+ }
+
+ mCompatibilityInfo.applyToConfiguration(density, mTmpConfig);
+
+ if (mTmpConfig.locale == null) {
+ mTmpConfig.locale = Locale.getDefault();
+ mTmpConfig.setLayoutDirection(mTmpConfig.locale);
+ }
+ configChanges = mConfiguration.updateFrom(mTmpConfig);
+ configChanges = ActivityInfo.activityInfoConfigToNative(configChanges);
+ }
+ return configChanges;
+ }
+
private void clearDrawableCachesLocked(
ArrayMap<String, LongSparseArray<WeakReference<ConstantState>>> caches,
int configChanges) {
diff --git a/core/java/android/view/animation/AccelerateDecelerateInterpolator.java b/core/java/android/view/animation/AccelerateDecelerateInterpolator.java
index ed6949a..21d5a5b 100644
--- a/core/java/android/view/animation/AccelerateDecelerateInterpolator.java
+++ b/core/java/android/view/animation/AccelerateDecelerateInterpolator.java
@@ -26,17 +26,17 @@ import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
/**
* An interpolator where the rate of change starts and ends slowly but
* accelerates through the middle.
- *
*/
@HasNativeInterpolator
-public class AccelerateDecelerateInterpolator implements Interpolator, NativeInterpolatorFactory {
+public class AccelerateDecelerateInterpolator extends BaseInterpolator
+ implements NativeInterpolatorFactory {
public AccelerateDecelerateInterpolator() {
}
-
+
@SuppressWarnings({"UnusedDeclaration"})
public AccelerateDecelerateInterpolator(Context context, AttributeSet attrs) {
}
-
+
public float getInterpolation(float input) {
return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
}
diff --git a/core/java/android/view/animation/AccelerateInterpolator.java b/core/java/android/view/animation/AccelerateInterpolator.java
index 1c75f16..6c8d7b1 100644
--- a/core/java/android/view/animation/AccelerateInterpolator.java
+++ b/core/java/android/view/animation/AccelerateInterpolator.java
@@ -33,7 +33,7 @@ import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
*
*/
@HasNativeInterpolator
-public class AccelerateInterpolator implements Interpolator, NativeInterpolatorFactory {
+public class AccelerateInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
private final float mFactor;
private final double mDoubleFactor;
@@ -70,7 +70,7 @@ public class AccelerateInterpolator implements Interpolator, NativeInterpolatorF
mFactor = a.getFloat(R.styleable.AccelerateInterpolator_factor, 1.0f);
mDoubleFactor = 2 * mFactor;
-
+ setChangingConfiguration(a.getChangingConfigurations());
a.recycle();
}
diff --git a/core/java/android/view/animation/AnimationUtils.java b/core/java/android/view/animation/AnimationUtils.java
index af4e04f..606c83e 100644
--- a/core/java/android/view/animation/AnimationUtils.java
+++ b/core/java/android/view/animation/AnimationUtils.java
@@ -321,7 +321,7 @@ public class AnimationUtils {
private static Interpolator createInterpolatorFromXml(Resources res, Theme theme, XmlPullParser parser)
throws XmlPullParserException, IOException {
- Interpolator interpolator = null;
+ BaseInterpolator interpolator = null;
// Make sure we are on a start tag.
int type;
@@ -361,10 +361,7 @@ public class AnimationUtils {
} else {
throw new RuntimeException("Unknown interpolator name: " + parser.getName());
}
-
}
-
return interpolator;
-
}
}
diff --git a/core/java/android/view/animation/AnticipateInterpolator.java b/core/java/android/view/animation/AnticipateInterpolator.java
index fe756bd..fb66c31 100644
--- a/core/java/android/view/animation/AnticipateInterpolator.java
+++ b/core/java/android/view/animation/AnticipateInterpolator.java
@@ -31,7 +31,7 @@ import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
* An interpolator where the change starts backward then flings forward.
*/
@HasNativeInterpolator
-public class AnticipateInterpolator implements Interpolator, NativeInterpolatorFactory {
+public class AnticipateInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
private final float mTension;
public AnticipateInterpolator() {
@@ -60,9 +60,8 @@ public class AnticipateInterpolator implements Interpolator, NativeInterpolatorF
a = res.obtainAttributes(attrs, R.styleable.AnticipateInterpolator);
}
- mTension =
- a.getFloat(R.styleable.AnticipateInterpolator_tension, 2.0f);
-
+ mTension = a.getFloat(R.styleable.AnticipateInterpolator_tension, 2.0f);
+ setChangingConfiguration(a.getChangingConfigurations());
a.recycle();
}
diff --git a/core/java/android/view/animation/AnticipateOvershootInterpolator.java b/core/java/android/view/animation/AnticipateOvershootInterpolator.java
index 78e5acf..1af72da 100644
--- a/core/java/android/view/animation/AnticipateOvershootInterpolator.java
+++ b/core/java/android/view/animation/AnticipateOvershootInterpolator.java
@@ -35,7 +35,8 @@ import static com.android.internal.R.styleable.AnticipateOvershootInterpolator;
* the target value and finally goes back to the final value.
*/
@HasNativeInterpolator
-public class AnticipateOvershootInterpolator implements Interpolator, NativeInterpolatorFactory {
+public class AnticipateOvershootInterpolator extends BaseInterpolator
+ implements NativeInterpolatorFactory {
private final float mTension;
public AnticipateOvershootInterpolator() {
@@ -78,7 +79,7 @@ public class AnticipateOvershootInterpolator implements Interpolator, NativeInte
mTension = a.getFloat(AnticipateOvershootInterpolator_tension, 2.0f) *
a.getFloat(AnticipateOvershootInterpolator_extraTension, 1.5f);
-
+ setChangingConfiguration(a.getChangingConfigurations());
a.recycle();
}
diff --git a/core/java/android/view/animation/BaseInterpolator.java b/core/java/android/view/animation/BaseInterpolator.java
new file mode 100644
index 0000000..9c0014c
--- /dev/null
+++ b/core/java/android/view/animation/BaseInterpolator.java
@@ -0,0 +1,37 @@
+/*
+ * 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.view.animation;
+
+/**
+ * An abstract class which is extended by default interpolators.
+ */
+abstract public class BaseInterpolator implements Interpolator {
+ private int mChangingConfiguration;
+ /**
+ * @hide
+ */
+ public int getChangingConfiguration() {
+ return mChangingConfiguration;
+ }
+
+ /**
+ * @hide
+ */
+ void setChangingConfiguration(int changingConfiguration) {
+ mChangingConfiguration = changingConfiguration;
+ }
+}
diff --git a/core/java/android/view/animation/BounceInterpolator.java b/core/java/android/view/animation/BounceInterpolator.java
index 9d8ca90..909eaa4 100644
--- a/core/java/android/view/animation/BounceInterpolator.java
+++ b/core/java/android/view/animation/BounceInterpolator.java
@@ -27,7 +27,7 @@ import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
* An interpolator where the change bounces at the end.
*/
@HasNativeInterpolator
-public class BounceInterpolator implements Interpolator, NativeInterpolatorFactory {
+public class BounceInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
public BounceInterpolator() {
}
diff --git a/core/java/android/view/animation/CycleInterpolator.java b/core/java/android/view/animation/CycleInterpolator.java
index 3114aa3..663c109 100644
--- a/core/java/android/view/animation/CycleInterpolator.java
+++ b/core/java/android/view/animation/CycleInterpolator.java
@@ -33,7 +33,7 @@ import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
*
*/
@HasNativeInterpolator
-public class CycleInterpolator implements Interpolator, NativeInterpolatorFactory {
+public class CycleInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
public CycleInterpolator(float cycles) {
mCycles = cycles;
}
@@ -52,7 +52,7 @@ public class CycleInterpolator implements Interpolator, NativeInterpolatorFactor
}
mCycles = a.getFloat(R.styleable.CycleInterpolator_cycles, 1.0f);
-
+ setChangingConfiguration(a.getChangingConfigurations());
a.recycle();
}
diff --git a/core/java/android/view/animation/DecelerateInterpolator.java b/core/java/android/view/animation/DecelerateInterpolator.java
index 674207c..f426f60 100644
--- a/core/java/android/view/animation/DecelerateInterpolator.java
+++ b/core/java/android/view/animation/DecelerateInterpolator.java
@@ -33,7 +33,7 @@ import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
*
*/
@HasNativeInterpolator
-public class DecelerateInterpolator implements Interpolator, NativeInterpolatorFactory {
+public class DecelerateInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
public DecelerateInterpolator() {
}
@@ -62,7 +62,7 @@ public class DecelerateInterpolator implements Interpolator, NativeInterpolatorF
}
mFactor = a.getFloat(R.styleable.DecelerateInterpolator_factor, 1.0f);
-
+ setChangingConfiguration(a.getChangingConfigurations());
a.recycle();
}
diff --git a/core/java/android/view/animation/LinearInterpolator.java b/core/java/android/view/animation/LinearInterpolator.java
index 552c611..2a047b4 100644
--- a/core/java/android/view/animation/LinearInterpolator.java
+++ b/core/java/android/view/animation/LinearInterpolator.java
@@ -25,17 +25,16 @@ import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
/**
* An interpolator where the rate of change is constant
- *
*/
@HasNativeInterpolator
-public class LinearInterpolator implements Interpolator, NativeInterpolatorFactory {
+public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
public LinearInterpolator() {
}
-
+
public LinearInterpolator(Context context, AttributeSet attrs) {
}
-
+
public float getInterpolation(float input) {
return input;
}
diff --git a/core/java/android/view/animation/OvershootInterpolator.java b/core/java/android/view/animation/OvershootInterpolator.java
index d6c2808..306688a 100644
--- a/core/java/android/view/animation/OvershootInterpolator.java
+++ b/core/java/android/view/animation/OvershootInterpolator.java
@@ -32,7 +32,7 @@ import com.android.internal.view.animation.NativeInterpolatorFactoryHelper;
* then comes back.
*/
@HasNativeInterpolator
-public class OvershootInterpolator implements Interpolator, NativeInterpolatorFactory {
+public class OvershootInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
private final float mTension;
public OvershootInterpolator() {
@@ -61,9 +61,8 @@ public class OvershootInterpolator implements Interpolator, NativeInterpolatorFa
a = res.obtainAttributes(attrs, R.styleable.OvershootInterpolator);
}
- mTension =
- a.getFloat(R.styleable.OvershootInterpolator_tension, 2.0f);
-
+ mTension = a.getFloat(R.styleable.OvershootInterpolator_tension, 2.0f);
+ setChangingConfiguration(a.getChangingConfigurations());
a.recycle();
}
diff --git a/core/java/android/view/animation/PathInterpolator.java b/core/java/android/view/animation/PathInterpolator.java
index 945ecf0..eec5555 100644
--- a/core/java/android/view/animation/PathInterpolator.java
+++ b/core/java/android/view/animation/PathInterpolator.java
@@ -42,7 +42,7 @@ import com.android.internal.R;
* path.lineTo(1f, 1f);
* </pre></blockquote></p>
*/
-public class PathInterpolator implements Interpolator {
+public class PathInterpolator extends BaseInterpolator {
// This governs how accurate the approximation of the Path is.
private static final float PRECISION = 0.002f;
@@ -98,7 +98,7 @@ public class PathInterpolator implements Interpolator {
a = res.obtainAttributes(attrs, R.styleable.PathInterpolator);
}
parseInterpolatorFromTypeArray(a);
-
+ setChangingConfiguration(a.getChangingConfigurations());
a.recycle();
}
diff --git a/core/tests/coretests/AndroidManifest.xml b/core/tests/coretests/AndroidManifest.xml
index b524177..226717e 100644
--- a/core/tests/coretests/AndroidManifest.xml
+++ b/core/tests/coretests/AndroidManifest.xml
@@ -1252,6 +1252,13 @@
</intent-filter>
</activity>
+ <activity android:name="android.content.res.ResourceCacheActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
</application>
<instrumentation android:name="android.test.InstrumentationTestRunner"
diff --git a/core/tests/coretests/res/anim/reset_state_anim.xml b/core/tests/coretests/res/anim/reset_state_anim.xml
index 918d0a3..4bbbe62 100644
--- a/core/tests/coretests/res/anim/reset_state_anim.xml
+++ b/core/tests/coretests/res/anim/reset_state_anim.xml
@@ -1,4 +1,18 @@
<?xml version="1.0"?>
+<!-- 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.
+-->
<set xmlns:android="http://schemas.android.com/apk/res/android">
<objectAnimator android:propertyName="x" android:duration="100" android:valueTo="0" android:valueType="floatType"/>
<objectAnimator android:propertyName="y" android:duration="100" android:valueTo="0" android:valueType="floatType"/>
diff --git a/core/tests/coretests/res/anim/test_animator.xml b/core/tests/coretests/res/anim/test_animator.xml
new file mode 100644
index 0000000..49afc3f
--- /dev/null
+++ b/core/tests/coretests/res/anim/test_animator.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0"?>
+<!-- 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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- if you change this, you should also change AnimatorInflaterTest#testLoadAnimator-->
+ <objectAnimator android:propertyName="x" android:duration="100" android:valueTo="0" android:valueType="floatType"/>
+ <objectAnimator android:propertyName="y" android:duration="100" android:valueTo="1" android:valueType="floatType"/>
+ <objectAnimator android:propertyName="left" android:duration="100" android:valueTo="2" android:valueType="intType"/>
+</set> \ No newline at end of file
diff --git a/core/tests/coretests/res/anim/test_state_anim.xml b/core/tests/coretests/res/anim/test_state_anim.xml
index 9e08f68..b6a4822 100644
--- a/core/tests/coretests/res/anim/test_state_anim.xml
+++ b/core/tests/coretests/res/anim/test_state_anim.xml
@@ -1,4 +1,18 @@
<?xml version="1.0"?>
+<!-- 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.
+-->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true">
<set>
diff --git a/core/tests/coretests/res/values-land/dimens.xml b/core/tests/coretests/res/values-land/dimens.xml
new file mode 100644
index 0000000..1ee9f1d
--- /dev/null
+++ b/core/tests/coretests/res/values-land/dimens.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<resources>
+ <dimen name="resource_cache_test_orientation_dependent">3dp</dimen>
+</resources> \ No newline at end of file
diff --git a/core/tests/coretests/res/values/dimens.xml b/core/tests/coretests/res/values/dimens.xml
new file mode 100644
index 0000000..00fc414
--- /dev/null
+++ b/core/tests/coretests/res/values/dimens.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- 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.
+-->
+<resources>
+ <dimen name="resource_cache_test_generic">10dp</dimen>
+ <dimen name="resource_cache_test_orientation_dependent">20dp</dimen>
+</resources> \ No newline at end of file
diff --git a/core/tests/coretests/src/android/animation/AnimatorInflaterTest.java b/core/tests/coretests/src/android/animation/AnimatorInflaterTest.java
new file mode 100644
index 0000000..3c81853
--- /dev/null
+++ b/core/tests/coretests/src/android/animation/AnimatorInflaterTest.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.animation;
+
+import android.test.ActivityInstrumentationTestCase2;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import com.android.frameworks.coretests.R;
+
+public class AnimatorInflaterTest extends ActivityInstrumentationTestCase2<BasicAnimatorActivity> {
+ Set<Integer> identityHashes = new HashSet<Integer>();
+
+ public AnimatorInflaterTest() {
+ super(BasicAnimatorActivity.class);
+ }
+
+ private void assertUnique(Object object) {
+ assertUnique(object, "");
+ }
+
+ private void assertUnique(Object object, String msg) {
+ final int code = System.identityHashCode(object);
+ assertTrue("object should be unique " + msg + ", obj:" + object, identityHashes.add(code));
+
+ }
+
+ public void testLoadStateListAnimator() {
+ StateListAnimator sla1 = AnimatorInflater.loadStateListAnimator(getActivity(),
+ R.anim.test_state_anim);
+ sla1.setTarget(getActivity().mAnimatingButton);
+ StateListAnimator sla2 = AnimatorInflater.loadStateListAnimator(getActivity(),
+ R.anim.test_state_anim);
+ assertNull(sla2.getTarget());
+ for (StateListAnimator sla : new StateListAnimator[]{sla1, sla2}) {
+ assertUnique(sla);
+ assertEquals(3, sla.getTuples().size());
+ for (StateListAnimator.Tuple tuple : sla.getTuples()) {
+ assertUnique(tuple);
+ assertUnique(tuple.getAnimator());
+ }
+ }
+ }
+
+}
diff --git a/core/tests/coretests/src/android/animation/BasicAnimatorActivity.java b/core/tests/coretests/src/android/animation/BasicAnimatorActivity.java
index 93808d9..6bcf8fc 100644
--- a/core/tests/coretests/src/android/animation/BasicAnimatorActivity.java
+++ b/core/tests/coretests/src/android/animation/BasicAnimatorActivity.java
@@ -19,11 +19,14 @@ import com.android.frameworks.coretests.R;
import android.app.Activity;
import android.os.Bundle;
+import android.widget.Button;
public class BasicAnimatorActivity extends Activity {
+ public Button mAnimatingButton;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.animator_basic);
+ mAnimatingButton = (Button) findViewById(R.id.animatingButton);
}
}
diff --git a/core/tests/coretests/src/android/content/res/ConfigurationBoundResourceCacheTest.java b/core/tests/coretests/src/android/content/res/ConfigurationBoundResourceCacheTest.java
new file mode 100644
index 0000000..e9fd5fb
--- /dev/null
+++ b/core/tests/coretests/src/android/content/res/ConfigurationBoundResourceCacheTest.java
@@ -0,0 +1,224 @@
+/*
+ * 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;
+
+import android.test.ActivityInstrumentationTestCase2;
+import android.util.TypedValue;
+
+import com.android.frameworks.coretests.R;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+public class ConfigurationBoundResourceCacheTest
+ extends ActivityInstrumentationTestCase2<ResourceCacheActivity> {
+
+ ConfigurationBoundResourceCache<Float> mCache;
+
+ Method mCalcConfigChanges;
+
+ public ConfigurationBoundResourceCacheTest() {
+ super(ResourceCacheActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mCache = new ConfigurationBoundResourceCache<Float>(getActivity().getResources());
+ }
+
+ public void testGetEmpty() {
+ assertNull(mCache.get(-1, null));
+ }
+
+ public void testSetGet() {
+ mCache.put(1, null, new DummyFloatConstantState(5f));
+ assertEquals(5f, mCache.get(1, null));
+ assertNotSame(5f, mCache.get(1, null));
+ assertEquals(null, mCache.get(1, getActivity().getTheme()));
+ }
+
+ public void testSetGetThemed() {
+ mCache.put(1, getActivity().getTheme(), new DummyFloatConstantState(5f));
+ assertEquals(null, mCache.get(1, null));
+ assertEquals(5f, mCache.get(1, getActivity().getTheme()));
+ assertNotSame(5f, mCache.get(1, getActivity().getTheme()));
+ }
+
+ public void testMultiThreadPutGet() {
+ mCache.put(1, getActivity().getTheme(), new DummyFloatConstantState(5f));
+ mCache.put(1, null, new DummyFloatConstantState(10f));
+ assertEquals(10f, mCache.get(1, null));
+ assertNotSame(10f, mCache.get(1, null));
+ assertEquals(5f, mCache.get(1, getActivity().getTheme()));
+ assertNotSame(5f, mCache.get(1, getActivity().getTheme()));
+ }
+
+ public void testVoidConfigChange()
+ throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
+ TypedValue staticValue = new TypedValue();
+ long key = 3L;
+ final Resources res = getActivity().getResources();
+ res.getValue(R.dimen.resource_cache_test_generic, staticValue, true);
+ float staticDim = TypedValue.complexToDimension(staticValue.data, res.getDisplayMetrics());
+ mCache.put(key, getActivity().getTheme(),
+ new DummyFloatConstantState(staticDim, staticValue.changingConfigurations));
+ final Configuration cfg = res.getConfiguration();
+ Configuration newCnf = new Configuration(cfg);
+ newCnf.orientation = cfg.orientation == Configuration.ORIENTATION_LANDSCAPE ?
+ Configuration.ORIENTATION_PORTRAIT
+ : Configuration.ORIENTATION_LANDSCAPE;
+ int changes = calcConfigChanges(res, newCnf);
+ assertEquals(staticDim, mCache.get(key, getActivity().getTheme()));
+ mCache.onConfigurationChange(changes);
+ assertEquals(staticDim, mCache.get(key, getActivity().getTheme()));
+ }
+
+ public void testEffectiveConfigChange()
+ throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
+ TypedValue changingValue = new TypedValue();
+ long key = 4L;
+ final Resources res = getActivity().getResources();
+ res.getValue(R.dimen.resource_cache_test_orientation_dependent, changingValue, true);
+ float changingDim = TypedValue.complexToDimension(changingValue.data,
+ res.getDisplayMetrics());
+ mCache.put(key, getActivity().getTheme(),
+ new DummyFloatConstantState(changingDim, changingValue.changingConfigurations));
+
+ final Configuration cfg = res.getConfiguration();
+ Configuration newCnf = new Configuration(cfg);
+ newCnf.orientation = cfg.orientation == Configuration.ORIENTATION_LANDSCAPE ?
+ Configuration.ORIENTATION_PORTRAIT
+ : Configuration.ORIENTATION_LANDSCAPE;
+ int changes = calcConfigChanges(res, newCnf);
+ assertEquals(changingDim, mCache.get(key, getActivity().getTheme()));
+ mCache.onConfigurationChange(changes);
+ assertNull(mCache.get(key, getActivity().getTheme()));
+ }
+
+ public void testConfigChangeMultipleResources()
+ throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
+ TypedValue staticValue = new TypedValue();
+ TypedValue changingValue = new TypedValue();
+ final Resources res = getActivity().getResources();
+ res.getValue(R.dimen.resource_cache_test_generic, staticValue, true);
+ res.getValue(R.dimen.resource_cache_test_orientation_dependent, changingValue, true);
+ float staticDim = TypedValue.complexToDimension(staticValue.data, res.getDisplayMetrics());
+ float changingDim = TypedValue.complexToDimension(changingValue.data,
+ res.getDisplayMetrics());
+ mCache.put(R.dimen.resource_cache_test_generic, getActivity().getTheme(),
+ new DummyFloatConstantState(staticDim, staticValue.changingConfigurations));
+ mCache.put(R.dimen.resource_cache_test_orientation_dependent, getActivity().getTheme(),
+ new DummyFloatConstantState(changingDim, changingValue.changingConfigurations));
+ final Configuration cfg = res.getConfiguration();
+ Configuration newCnf = new Configuration(cfg);
+ newCnf.orientation = cfg.orientation == Configuration.ORIENTATION_LANDSCAPE ?
+ Configuration.ORIENTATION_PORTRAIT
+ : Configuration.ORIENTATION_LANDSCAPE;
+ int changes = calcConfigChanges(res, newCnf);
+ assertEquals(staticDim, mCache.get(R.dimen.resource_cache_test_generic,
+ getActivity().getTheme()));
+ assertEquals(changingDim, mCache.get(R.dimen.resource_cache_test_orientation_dependent,
+ getActivity().getTheme()));
+ mCache.onConfigurationChange(changes);
+ assertEquals(staticDim, mCache.get(R.dimen.resource_cache_test_generic,
+ getActivity().getTheme()));
+ assertNull(mCache.get(R.dimen.resource_cache_test_orientation_dependent,
+ getActivity().getTheme()));
+ }
+
+ public void testConfigChangeMultipleThemes()
+ throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
+ TypedValue[] staticValues = new TypedValue[]{new TypedValue(), new TypedValue()};
+ TypedValue[] changingValues = new TypedValue[]{new TypedValue(), new TypedValue()};
+ float staticDim = 0;
+ float changingDim = 0;
+ final Resources res = getActivity().getResources();
+ for (int i = 0; i < 2; i++) {
+ res.getValue(R.dimen.resource_cache_test_generic, staticValues[i], true);
+ staticDim = TypedValue
+ .complexToDimension(staticValues[i].data, res.getDisplayMetrics());
+
+ res.getValue(R.dimen.resource_cache_test_orientation_dependent, changingValues[i],
+ true);
+ changingDim = TypedValue.complexToDimension(changingValues[i].data,
+ res.getDisplayMetrics());
+ final Resources.Theme theme = i == 0 ? getActivity().getTheme() : null;
+ mCache.put(R.dimen.resource_cache_test_generic, theme,
+ new DummyFloatConstantState(staticDim, staticValues[i].changingConfigurations));
+ mCache.put(R.dimen.resource_cache_test_orientation_dependent, theme,
+ new DummyFloatConstantState(changingDim,
+ changingValues[i].changingConfigurations));
+ }
+ final Configuration cfg = res.getConfiguration();
+ Configuration newCnf = new Configuration(cfg);
+ newCnf.orientation = cfg.orientation == Configuration.ORIENTATION_LANDSCAPE ?
+ Configuration.ORIENTATION_PORTRAIT
+ : Configuration.ORIENTATION_LANDSCAPE;
+ int changes = calcConfigChanges(res, newCnf);
+ for (int i = 0; i < 2; i++) {
+ final Resources.Theme theme = i == 0 ? getActivity().getTheme() : null;
+ assertEquals(staticDim, mCache.get(R.dimen.resource_cache_test_generic, theme));
+ assertEquals(changingDim,
+ mCache.get(R.dimen.resource_cache_test_orientation_dependent, theme));
+ }
+ mCache.onConfigurationChange(changes);
+ for (int i = 0; i < 2; i++) {
+ final Resources.Theme theme = i == 0 ? getActivity().getTheme() : null;
+ assertEquals(staticDim, mCache.get(R.dimen.resource_cache_test_generic, theme));
+ assertNull(mCache.get(R.dimen.resource_cache_test_orientation_dependent, theme));
+ }
+ }
+
+ private int calcConfigChanges(Resources resources, Configuration configuration)
+ throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+ if (mCalcConfigChanges == null) {
+ mCalcConfigChanges = Resources.class.getDeclaredMethod("calcConfigChanges",
+ Configuration.class);
+ mCalcConfigChanges.setAccessible(true);
+ }
+ return (Integer) mCalcConfigChanges.invoke(resources, configuration);
+
+ }
+
+ static class DummyFloatConstantState extends
+ ConstantState<Float> {
+
+ final Float mObj;
+
+ int mChangingConf = 0;
+
+ DummyFloatConstantState(Float obj) {
+ mObj = obj;
+ }
+
+ DummyFloatConstantState(Float obj, int changingConf) {
+ mObj = obj;
+ mChangingConf = changingConf;
+ }
+
+ @Override
+ public int getChangingConfigurations() {
+ return mChangingConf;
+ }
+
+ @Override
+ public Float newInstance() {
+ return new Float(mObj);
+ }
+ }
+}
diff --git a/core/tests/coretests/src/android/content/res/ResourceCacheActivity.java b/core/tests/coretests/src/android/content/res/ResourceCacheActivity.java
new file mode 100644
index 0000000..f37e549
--- /dev/null
+++ b/core/tests/coretests/src/android/content/res/ResourceCacheActivity.java
@@ -0,0 +1,37 @@
+/*
+* 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;
+
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.os.Bundle;
+
+import java.lang.ref.WeakReference;
+
+public class ResourceCacheActivity extends Activity {
+ static WeakReference<ResourceCacheActivity> lastCreatedInstance;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ lastCreatedInstance = new WeakReference<ResourceCacheActivity>(this);
+ }
+
+ public static ResourceCacheActivity getLastCreatedInstance() {
+ return lastCreatedInstance == null ? null : lastCreatedInstance.get();
+ }
+}
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index bec1d38..1fac5b6 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -1058,54 +1058,72 @@ public abstract class Drawable {
final Drawable drawable;
final String name = parser.getName();
- if (name.equals("selector")) {
- drawable = new StateListDrawable();
- } else if (name.equals("animated-selector")) {
- drawable = new AnimatedStateListDrawable();
- } else if (name.equals("level-list")) {
- drawable = new LevelListDrawable();
- } else if (name.equals("layer-list")) {
- drawable = new LayerDrawable();
- } else if (name.equals("transition")) {
- drawable = new TransitionDrawable();
- } else if (name.equals("ripple")) {
- drawable = new RippleDrawable();
- } else if (name.equals("color")) {
- drawable = new ColorDrawable();
- } else if (name.equals("shape")) {
- drawable = new GradientDrawable();
- } else if (name.equals("vector")) {
- drawable = new VectorDrawable();
- } else if (name.equals("animated-vector")) {
- drawable = new AnimatedVectorDrawable();
- } else if (name.equals("scale")) {
- drawable = new ScaleDrawable();
- } else if (name.equals("clip")) {
- drawable = new ClipDrawable();
- } else if (name.equals("rotate")) {
- drawable = new RotateDrawable();
- } else if (name.equals("animated-rotate")) {
- drawable = new AnimatedRotateDrawable();
- } else if (name.equals("animation-list")) {
- drawable = new AnimationDrawable();
- } else if (name.equals("inset")) {
- drawable = new InsetDrawable();
- } else if (name.equals("bitmap")) {
- //noinspection deprecation
- drawable = new BitmapDrawable(r);
- if (r != null) {
- ((BitmapDrawable) drawable).setTargetDensity(r.getDisplayMetrics());
- }
- } else if (name.equals("nine-patch")) {
- drawable = new NinePatchDrawable();
- if (r != null) {
- ((NinePatchDrawable) drawable).setTargetDensity(r.getDisplayMetrics());
- }
- } else {
- throw new XmlPullParserException(parser.getPositionDescription() +
- ": invalid drawable tag " + name);
- }
+ switch (name) {
+ case "selector":
+ drawable = new StateListDrawable();
+ break;
+ case "animated-selector":
+ drawable = new AnimatedStateListDrawable();
+ break;
+ case "level-list":
+ drawable = new LevelListDrawable();
+ break;
+ case "layer-list":
+ drawable = new LayerDrawable();
+ break;
+ case "transition":
+ drawable = new TransitionDrawable();
+ break;
+ case "ripple":
+ drawable = new RippleDrawable();
+ break;
+ case "color":
+ drawable = new ColorDrawable();
+ break;
+ case "shape":
+ drawable = new GradientDrawable();
+ break;
+ case "vector":
+ drawable = new VectorDrawable();
+ break;
+ case "animated-vector":
+ drawable = new AnimatedVectorDrawable();
+ break;
+ case "scale":
+ drawable = new ScaleDrawable();
+ break;
+ case "clip":
+ drawable = new ClipDrawable();
+ break;
+ case "rotate":
+ drawable = new RotateDrawable();
+ break;
+ case "animated-rotate":
+ drawable = new AnimatedRotateDrawable();
+ break;
+ case "animation-list":
+ drawable = new AnimationDrawable();
+ break;
+ case "inset":
+ drawable = new InsetDrawable();
+ break;
+ case "bitmap":
+ drawable = new BitmapDrawable(r);
+ if (r != null) {
+ ((BitmapDrawable) drawable).setTargetDensity(r.getDisplayMetrics());
+ }
+ break;
+ case "nine-patch":
+ drawable = new NinePatchDrawable();
+ if (r != null) {
+ ((NinePatchDrawable) drawable).setTargetDensity(r.getDisplayMetrics());
+ }
+ break;
+ default:
+ throw new XmlPullParserException(parser.getPositionDescription() +
+ ": invalid drawable tag " + name);
+ }
drawable.inflate(r, parser, attrs, theme);
return drawable;
}