Merge "Add ability to transition parent hierarchy in layout transitions"
diff --git a/api/current.txt b/api/current.txt
index 678e454..a51b649 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -1978,6 +1978,7 @@
     method public boolean isRunning();
     method public void removeChild(android.view.ViewGroup, android.view.View);
     method public void removeTransitionListener(android.animation.LayoutTransition.TransitionListener);
+    method public void setAnimateParentHierarchy(boolean);
     method public void setAnimator(int, android.animation.Animator);
     method public void setDuration(long);
     method public void setDuration(int, long);
@@ -21441,6 +21442,8 @@
     method public void setScaleY(float);
     method public void setScrollBarStyle(int);
     method public void setScrollContainer(boolean);
+    method public void setScrollX(int);
+    method public void setScrollY(int);
     method public void setScrollbarFadingEnabled(boolean);
     method public void setSelected(boolean);
     method public void setSoundEffectsEnabled(boolean);
diff --git a/core/java/android/animation/LayoutTransition.java b/core/java/android/animation/LayoutTransition.java
index adfda8e..d25de97 100644
--- a/core/java/android/animation/LayoutTransition.java
+++ b/core/java/android/animation/LayoutTransition.java
@@ -18,6 +18,7 @@
 
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.ViewParent;
 import android.view.ViewTreeObserver;
 import android.view.animation.AccelerateDecelerateInterpolator;
 import android.view.animation.DecelerateInterpolator;
@@ -70,8 +71,9 @@
  * moving as a result of the layout event) as well as the values that are changing (such as the
  * position and size of that object). The actual values that are pushed to each animation
  * depends on what properties are specified for the animation. For example, the default
- * CHANGE_APPEARING animation animates <code>left</code>, <code>top</code>, <code>right</code>,
- * and <code>bottom</code>. Values for these properties are updated with the pre- and post-layout
+ * CHANGE_APPEARING animation animates the <code>left</code>, <code>top</code>, <code>right</code>,
+ * <code>bottom</code>, <code>scrollX</code>, and <code>scrollY</code> properties.
+ * Values for these properties are updated with the pre- and post-layout
  * values when the transition begins. Custom animations will be similarly populated with
  * the target and values being animated, assuming they use ObjectAnimator objects with
  * property names that are known on the target object.</p>
@@ -210,6 +212,14 @@
      */
     private ArrayList<TransitionListener> mListeners;
 
+    /**
+     * Controls whether changing animations automatically animate the parent hierarchy as well.
+     * This behavior prevents artifacts when wrap_content layouts snap to the end state as the
+     * transition begins, causing visual glitches and clipping.
+     * Default value is true.
+     */
+    private boolean mAnimateParentHierarchy = true;
+
 
     /**
      * Constructs a LayoutTransition object. By default, the object will listen to layout
@@ -223,14 +233,17 @@
             PropertyValuesHolder pvhTop = PropertyValuesHolder.ofInt("top", 0, 1);
             PropertyValuesHolder pvhRight = PropertyValuesHolder.ofInt("right", 0, 1);
             PropertyValuesHolder pvhBottom = PropertyValuesHolder.ofInt("bottom", 0, 1);
+            PropertyValuesHolder pvhScrollX = PropertyValuesHolder.ofInt("scrollX", 0, 1);
+            PropertyValuesHolder pvhScrollY = PropertyValuesHolder.ofInt("scrollY", 0, 1);
             defaultChangeIn = ObjectAnimator.ofPropertyValuesHolder(this,
-                    pvhLeft, pvhTop, pvhRight, pvhBottom);
+                    pvhLeft, pvhTop, pvhRight, pvhBottom, pvhScrollX, pvhScrollY);
             defaultChangeIn.setDuration(DEFAULT_DURATION);
             defaultChangeIn.setStartDelay(mChangingAppearingDelay);
             defaultChangeIn.setInterpolator(mChangingAppearingInterpolator);
             defaultChangeOut = defaultChangeIn.clone();
             defaultChangeOut.setStartDelay(mChangingDisappearingDelay);
             defaultChangeOut.setInterpolator(mChangingDisappearingInterpolator);
+
             defaultFadeIn = ObjectAnimator.ofFloat(this, "alpha", 0f, 1f);
             defaultFadeIn.setDuration(DEFAULT_DURATION);
             defaultFadeIn.setStartDelay(mAppearingDelay);
@@ -572,122 +585,24 @@
 
             // only animate the views not being added or removed
             if (child != newView) {
-
-
-                // Make a copy of the appropriate animation
-                final Animator anim = baseAnimator.clone();
-
-                // Set the target object for the animation
-                anim.setTarget(child);
-
-                // A ObjectAnimator (or AnimatorSet of them) can extract start values from
-                // its target object
-                anim.setupStartValues();
-
-                // If there's an animation running on this view already, cancel it
-                Animator currentAnimation = pendingAnimations.get(child);
-                if (currentAnimation != null) {
-                    currentAnimation.cancel();
-                    pendingAnimations.remove(child);
-                }
-                // Cache the animation in case we need to cancel it later
-                pendingAnimations.put(child, anim);
-
-                // For the animations which don't get started, we have to have a means of
-                // removing them from the cache, lest we leak them and their target objects.
-                // We run an animator for the default duration+100 (an arbitrary time, but one
-                // which should far surpass the delay between setting them up here and
-                // handling layout events which start them.
-                ValueAnimator pendingAnimRemover = ValueAnimator.ofFloat(0f, 1f).
-                        setDuration(duration+100);
-                pendingAnimRemover.addListener(new AnimatorListenerAdapter() {
-                    @Override
-                    public void onAnimationEnd(Animator animation) {
-                        pendingAnimations.remove(child);
-                    }
-                });
-                pendingAnimRemover.start();
-
-                // Add a listener to track layout changes on this view. If we don't get a callback,
-                // then there's nothing to animate.
-                final View.OnLayoutChangeListener listener = new View.OnLayoutChangeListener() {
-                    public void onLayoutChange(View v, int left, int top, int right, int bottom,
-                            int oldLeft, int oldTop, int oldRight, int oldBottom) {
-
-                        // Tell the animation to extract end values from the changed object
-                        anim.setupEndValues();
-
-                        long startDelay;
-                        if (changeReason == APPEARING) {
-                            startDelay = mChangingAppearingDelay + staggerDelay;
-                            staggerDelay += mChangingAppearingStagger;
-                        } else {
-                            startDelay = mChangingDisappearingDelay + staggerDelay;
-                            staggerDelay += mChangingDisappearingStagger;
-                        }
-                        anim.setStartDelay(startDelay);
-                        anim.setDuration(duration);
-
-                        Animator prevAnimation = currentChangingAnimations.get(child);
-                        if (prevAnimation != null) {
-                            prevAnimation.cancel();
-                        }
-                        Animator pendingAnimation = pendingAnimations.get(child);
-                        if (pendingAnimation != null) {
-                            pendingAnimations.remove(child);
-                        }
-                        // Cache the animation in case we need to cancel it later
-                        currentChangingAnimations.put(child, anim);
-
-                        if (anim instanceof ObjectAnimator) {
-                            ((ObjectAnimator) anim).setCurrentPlayTime(0);
-                        }
-                        anim.start();
-
-                        // this only removes listeners whose views changed - must clear the
-                        // other listeners later
-                        child.removeOnLayoutChangeListener(this);
-                        layoutChangeListenerMap.remove(child);
-                    }
-                };
-                // Remove the animation from the cache when it ends
-                anim.addListener(new AnimatorListenerAdapter() {
-
-                    @Override
-                    public void onAnimationStart(Animator animator) {
-                        if (mListeners != null) {
-                            for (TransitionListener listener : mListeners) {
-                                listener.startTransition(LayoutTransition.this, parent, child,
-                                        changeReason == APPEARING ?
-                                                CHANGE_APPEARING : CHANGE_DISAPPEARING);
-                            }
-                        }
-                    }
-
-                    @Override
-                    public void onAnimationCancel(Animator animator) {
-                        child.removeOnLayoutChangeListener(listener);
-                        layoutChangeListenerMap.remove(child);
-                    }
-
-                    @Override
-                    public void onAnimationEnd(Animator animator) {
-                        currentChangingAnimations.remove(child);
-                        if (mListeners != null) {
-                            for (TransitionListener listener : mListeners) {
-                                listener.endTransition(LayoutTransition.this, parent, child,
-                                        changeReason == APPEARING ?
-                                                CHANGE_APPEARING : CHANGE_DISAPPEARING);
-                            }
-                        }
-                    }
-                });
-
-                child.addOnLayoutChangeListener(listener);
-                // cache the listener for later removal
-                layoutChangeListenerMap.put(child, listener);
+                setupChangeAnimation(parent, changeReason, baseAnimator, duration, child);
             }
         }
+        if (mAnimateParentHierarchy) {
+            ViewGroup tempParent = parent;
+            while (tempParent != null) {
+                ViewParent parentParent = tempParent.getParent();
+                if (parentParent instanceof ViewGroup) {
+                    setupChangeAnimation((ViewGroup)parentParent, changeReason, baseAnimator,
+                            duration, tempParent);
+                    tempParent = (ViewGroup) parentParent;
+                } else {
+                    tempParent = null;
+                }
+
+            }
+        }
+
         // This is the cleanup step. When we get this rendering event, we know that all of
         // the appropriate animations have been set up and run. Now we can clear out the
         // layout listeners.
@@ -706,6 +621,175 @@
     }
 
     /**
+     * This flag controls whether CHANGE_APPEARING or CHANGE_DISAPPEARING animations will
+     * cause the same changing animation to be run on the parent hierarchy as well. This allows
+     * containers of transitioning views to also transition, which may be necessary in situations
+     * where the containers bounds change between the before/after states and may clip their
+     * children during the transition animations. For example, layouts with wrap_content will
+     * adjust their bounds according to the dimensions of their children.
+     *
+     * @param animateParentHierarchy A boolean value indicating whether the parents of
+     * transitioning views should also be animated during the transition. Default value is true.
+     */
+    public void setAnimateParentHierarchy(boolean animateParentHierarchy) {
+        mAnimateParentHierarchy = animateParentHierarchy;
+    }
+
+    /**
+     * Utility function called by runChangingTransition for both the children and the parent
+     * hierarchy.
+     */
+    private void setupChangeAnimation(final ViewGroup parent, final int changeReason,
+            Animator baseAnimator, final long duration, final View child) {
+        // Make a copy of the appropriate animation
+        final Animator anim = baseAnimator.clone();
+
+        // Set the target object for the animation
+        anim.setTarget(child);
+
+        // A ObjectAnimator (or AnimatorSet of them) can extract start values from
+        // its target object
+        anim.setupStartValues();
+
+        // If there's an animation running on this view already, cancel it
+        Animator currentAnimation = pendingAnimations.get(child);
+        if (currentAnimation != null) {
+            currentAnimation.cancel();
+            pendingAnimations.remove(child);
+        }
+        // Cache the animation in case we need to cancel it later
+        pendingAnimations.put(child, anim);
+
+        // For the animations which don't get started, we have to have a means of
+        // removing them from the cache, lest we leak them and their target objects.
+        // We run an animator for the default duration+100 (an arbitrary time, but one
+        // which should far surpass the delay between setting them up here and
+        // handling layout events which start them.
+        ValueAnimator pendingAnimRemover = ValueAnimator.ofFloat(0f, 1f).
+                setDuration(duration + 100);
+        pendingAnimRemover.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                pendingAnimations.remove(child);
+            }
+        });
+        pendingAnimRemover.start();
+
+        // Add a listener to track layout changes on this view. If we don't get a callback,
+        // then there's nothing to animate.
+        final View.OnLayoutChangeListener listener = new View.OnLayoutChangeListener() {
+            public void onLayoutChange(View v, int left, int top, int right, int bottom,
+                    int oldLeft, int oldTop, int oldRight, int oldBottom) {
+
+                // Tell the animation to extract end values from the changed object
+                anim.setupEndValues();
+                if (anim instanceof ValueAnimator) {
+                    boolean valuesDiffer = false;
+                    ValueAnimator valueAnim = (ValueAnimator)anim;
+                    PropertyValuesHolder[] oldValues = valueAnim.getValues();
+                    for (int i = 0; i < oldValues.length; ++i) {
+                        PropertyValuesHolder pvh = oldValues[i];
+                        KeyframeSet keyframeSet = pvh.mKeyframeSet;
+                        if (keyframeSet.mFirstKeyframe == null ||
+                                keyframeSet.mLastKeyframe == null ||
+                                !keyframeSet.mFirstKeyframe.getValue().equals(
+                                keyframeSet.mLastKeyframe.getValue())) {
+                            valuesDiffer = true;
+                        }
+                    }
+                    if (!valuesDiffer) {
+                        return;
+                    }
+                }
+
+                long startDelay;
+                if (changeReason == APPEARING) {
+                    startDelay = mChangingAppearingDelay + staggerDelay;
+                    staggerDelay += mChangingAppearingStagger;
+                } else {
+                    startDelay = mChangingDisappearingDelay + staggerDelay;
+                    staggerDelay += mChangingDisappearingStagger;
+                }
+                anim.setStartDelay(startDelay);
+                anim.setDuration(duration);
+
+                Animator prevAnimation = currentChangingAnimations.get(child);
+                if (prevAnimation != null) {
+                    prevAnimation.cancel();
+                }
+                Animator pendingAnimation = pendingAnimations.get(child);
+                if (pendingAnimation != null) {
+                    pendingAnimations.remove(child);
+                }
+                // Cache the animation in case we need to cancel it later
+                currentChangingAnimations.put(child, anim);
+
+                parent.requestTransitionStart(LayoutTransition.this);
+
+                // this only removes listeners whose views changed - must clear the
+                // other listeners later
+                child.removeOnLayoutChangeListener(this);
+                layoutChangeListenerMap.remove(child);
+            }
+        };
+        // Remove the animation from the cache when it ends
+        anim.addListener(new AnimatorListenerAdapter() {
+
+            @Override
+            public void onAnimationStart(Animator animator) {
+                if (mListeners != null) {
+                    for (TransitionListener listener : mListeners) {
+                        listener.startTransition(LayoutTransition.this, parent, child,
+                                changeReason == APPEARING ?
+                                        CHANGE_APPEARING : CHANGE_DISAPPEARING);
+                    }
+                }
+            }
+
+            @Override
+            public void onAnimationCancel(Animator animator) {
+                child.removeOnLayoutChangeListener(listener);
+                layoutChangeListenerMap.remove(child);
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animator) {
+                currentChangingAnimations.remove(child);
+                if (mListeners != null) {
+                    for (TransitionListener listener : mListeners) {
+                        listener.endTransition(LayoutTransition.this, parent, child,
+                                changeReason == APPEARING ?
+                                        CHANGE_APPEARING : CHANGE_DISAPPEARING);
+                    }
+                }
+            }
+        });
+
+        child.addOnLayoutChangeListener(listener);
+        // cache the listener for later removal
+        layoutChangeListenerMap.put(child, listener);
+    }
+
+    /**
+     * Starts the animations set up for a CHANGING transition. We separate the setup of these
+     * animations from actually starting them, to avoid side-effects that starting the animations
+     * may have on the properties of the affected objects. After setup, we tell the affected parent
+     * that this transition should be started. The parent informs its ViewAncestor, which then
+     * starts the transition after the current layout/measurement phase, just prior to drawing
+     * the view hierarchy.
+     *
+     * @hide
+     */
+    public void startChangingAnimations() {
+        for (Animator anim : currentChangingAnimations.values()) {
+            if (anim instanceof ObjectAnimator) {
+                ((ObjectAnimator) anim).setCurrentPlayTime(0);
+            }
+            anim.start();
+        }
+    }
+
+    /**
      * Returns true if animations are running which animate layout-related properties. This
      * essentially means that either CHANGE_APPEARING or CHANGE_DISAPPEARING animations
      * are running, since these animations operate on layout-related properties.
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index d5f573c..98d07c4 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -6048,6 +6048,26 @@
     }
 
     /**
+     * Set the horizontal scrolled position of your view. This will cause a call to
+     * {@link #onScrollChanged(int, int, int, int)} and the view will be
+     * invalidated.
+     * @param value the x position to scroll to
+     */
+    public void setScrollX(int value) {
+        scrollTo(value, mScrollY);
+    }
+
+    /**
+     * Set the vertical scrolled position of your view. This will cause a call to
+     * {@link #onScrollChanged(int, int, int, int)} and the view will be
+     * invalidated.
+     * @param value the y position to scroll to
+     */
+    public void setScrollY(int value) {
+        scrollTo(mScrollX, value);
+    }
+
+    /**
      * Return the scrolled left position of this view. This is the left edge of
      * the displayed part of your view. You do not need to draw any pixels
      * farther left, since those are outside of the frame of your view on
diff --git a/core/java/android/view/ViewAncestor.java b/core/java/android/view/ViewAncestor.java
index cf9a1c8..ccba894 100644
--- a/core/java/android/view/ViewAncestor.java
+++ b/core/java/android/view/ViewAncestor.java
@@ -17,6 +17,7 @@
 package android.view;
 
 import android.Manifest;
+import android.animation.LayoutTransition;
 import android.app.ActivityManagerNative;
 import android.content.ClipDescription;
 import android.content.ComponentCallbacks;
@@ -232,6 +233,7 @@
     long mResizeBitmapStartTime;
     int mResizeBitmapDuration;
     static final Interpolator mResizeInterpolator = new AccelerateDecelerateInterpolator();
+    private ArrayList<LayoutTransition> mPendingTransitions;
 
     final ViewConfiguration mViewConfiguration;
 
@@ -700,6 +702,28 @@
         }
     }
 
+    /**
+     * Add LayoutTransition to the list of transitions to be started in the next traversal.
+     * This list will be cleared after the transitions on the list are start()'ed. These
+     * transitionsa re added by LayoutTransition itself when it sets up animations. The setup
+     * happens during the layout phase of traversal, which we want to complete before any of the
+     * animations are started (because those animations may side-effect properties that layout
+     * depends upon, like the bounding rectangles of the affected views). So we add the transition
+     * to the list and it is started just prior to starting the drawing phase of traversal.
+     *
+     * @param transition The LayoutTransition to be started on the next traversal.
+     *
+     * @hide
+     */
+    public void requestTransitionStart(LayoutTransition transition) {
+        if (mPendingTransitions == null || !mPendingTransitions.contains(transition)) {
+            if (mPendingTransitions == null) {
+                 mPendingTransitions = new ArrayList<LayoutTransition>();
+            }
+            mPendingTransitions.add(transition);
+        }
+    }
+
     private void performTraversals() {
         // cache mView since it is used so much below...
         final View host = mView;
@@ -1395,6 +1419,12 @@
         boolean cancelDraw = attachInfo.mTreeObserver.dispatchOnPreDraw();
 
         if (!cancelDraw && !newSurface) {
+            if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
+                for (int i = 0; i < mPendingTransitions.size(); ++i) {
+                    mPendingTransitions.get(i).startChangingAnimations();
+                }
+                mPendingTransitions.clear();
+            }
             mFullRedrawNeeded = false;
 
             final long drawStartTime;
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 6937573..f504b90 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -4838,6 +4838,20 @@
     }
 
     /**
+     * This method is called by LayoutTransition when there are 'changing' animations that need
+     * to start after the layout/setup phase. The request is forwarded to the ViewAncestor, who
+     * starts all pending transitions prior to the drawing phase in the current traversal.
+     *
+     * @param transition The LayoutTransition to be started on the next traversal.
+     *
+     * @hide
+     */
+    public void requestTransitionStart(LayoutTransition transition) {
+        ViewAncestor viewAncestor = getViewAncestor();
+        viewAncestor.requestTransitionStart(transition);
+    }
+
+    /**
      * Return true if the pressed state should be delayed for children or descendants of this
      * ViewGroup. Generally, this should be done for containers that can scroll, such as a List.
      * This prevents the pressed state from appearing when the user is actually trying to scroll