Merge "Added a generic configuration and theme based cache" into lmp-mr1-dev
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 @@
     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.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 @@
     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 @@
     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 @@
     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 @@
     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 @@
         }
     }
 
+    /**
+     * 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 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.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 @@
 
     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 @@
     /** @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 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 @@
                         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 @@
                         }
                         stateListAnimator
                                 .addState(StateSet.trimStateSet(states, stateIndex), animator);
-
                     }
                     break;
             }
@@ -508,7 +555,6 @@
     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 @@
                 } 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 @@
                 parent.playSequentially(animsArray);
             }
         }
-
         return anim;
 
     }
@@ -591,7 +636,6 @@
     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 @@
             } 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 @@
     }
 
     /**
+     * @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 @@
          * 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 @@
             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 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 @@
 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 @@
 
     @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 @@
 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 @@
 
     @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 @@
 
 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 @@
     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 @@
     public void invalidateCache() {
     }
 
-    public ArrayList<Keyframe> getKeyframes() {
+    public List<Keyframe> getKeyframes() {
         return mKeyframes;
     }
 
@@ -177,9 +179,9 @@
 
     @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 @@
      * @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.Method;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 
 /**
@@ -791,7 +792,7 @@
             // 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 @@
         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 @@
      * @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 @@
      * @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 @@
  * @attr ref android.R.styleable#DrawableStates_state_pressed
  * @attr ref android.R.styleable#StateListAnimatorItem_animation
  */
-public class StateListAnimator {
+public class StateListAnimator implements Cloneable {
 
-    private final ArrayList<Tuple> mTuples = new ArrayList<Tuple>();
-
+    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 @@
         Tuple tuple = new Tuple(specs, animator);
         tuple.mAnimator.addListener(mAnimatorListener);
         mTuples.add(tuple);
+        mChangingConfigurations |= animator.getChangingConfigurations();
     }
 
     /**
@@ -118,12 +126,35 @@
         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 @@
     }
 
     /**
+     * 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 @@
             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 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 @@
             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 @@
     }
 
     /**
+     * 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 @@
             // 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 @@
 
             clearDrawableCachesLocked(mDrawableCache, configChanges);
             clearDrawableCachesLocked(mColorDrawableCache, configChanges);
+            mAnimatorCache.onConfigurationChange(configChanges);
+            mStateListAnimatorCache.onConfigurationChange(configChanges);
 
             mColorStateListCache.clear();
 
@@ -1837,6 +1847,30 @@
         }
     }
 
+    /**
+     * 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 @@
 /**
  * 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 @@
  *
  */
 @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 @@
 
         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 @@
     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 @@
             } 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 @@
  * 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 @@
             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 @@
  * 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 @@
 
         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 @@
  * 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 @@
  *
  */
 @HasNativeInterpolator
-public class CycleInterpolator implements Interpolator, NativeInterpolatorFactory {
+public class CycleInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
     public CycleInterpolator(float cycles) {
         mCycles = cycles;
     }
@@ -52,7 +52,7 @@
         }
 
         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 @@
  *
  */
 @HasNativeInterpolator
-public class DecelerateInterpolator implements Interpolator, NativeInterpolatorFactory {
+public class DecelerateInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
     public DecelerateInterpolator() {
     }
 
@@ -62,7 +62,7 @@
         }
 
         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 @@
 
 /**
  * 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 @@
  * 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 @@
             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 @@
  *     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 @@
             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 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 @@
         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;
     }