diff options
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; } |