am 6fd0ef7f: Merge "Add Transitions useful for Activity transitions."

* commit '6fd0ef7f7367ce6e2272e9c517793d502fe7f8da':
  Add Transitions useful for Activity transitions.
diff --git a/api/current.txt b/api/current.txt
index 2435d5c..c6c8e421 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -1020,6 +1020,7 @@
     field public static final int shrinkColumns = 16843082; // 0x101014a
     field public static final deprecated int singleLine = 16843101; // 0x101015d
     field public static final int singleUser = 16843711; // 0x10103bf
+    field public static final int slideEdge = 16843835; // 0x101043b
     field public static final int smallIcon = 16843422; // 0x101029e
     field public static final int smallScreens = 16843396; // 0x1010284
     field public static final int smoothScrollbar = 16843313; // 0x1010231
@@ -27407,6 +27408,16 @@
     method public void setResizeClip(boolean);
   }
 
+  public class CircularPropagation extends android.transition.VisibilityPropagation {
+    ctor public CircularPropagation();
+    method public long getStartDelay(android.view.ViewGroup, android.transition.Transition, android.transition.TransitionValues, android.transition.TransitionValues);
+    method public void setPropagationSpeed(float);
+  }
+
+  public class Explode extends android.transition.Visibility {
+    ctor public Explode();
+  }
+
   public class Fade extends android.transition.Visibility {
     ctor public Fade();
     ctor public Fade(int);
@@ -27414,6 +27425,12 @@
     field public static final int OUT = 2; // 0x2
   }
 
+  public class MoveImage extends android.transition.Transition {
+    ctor public MoveImage();
+    method public void captureEndValues(android.transition.TransitionValues);
+    method public void captureStartValues(android.transition.TransitionValues);
+  }
+
   public final class Scene {
     ctor public Scene(android.view.ViewGroup);
     ctor public Scene(android.view.ViewGroup, android.view.View);
@@ -27426,6 +27443,27 @@
     method public void setExitAction(java.lang.Runnable);
   }
 
+  public class SidePropagation extends android.transition.VisibilityPropagation {
+    ctor public SidePropagation();
+    method public long getStartDelay(android.view.ViewGroup, android.transition.Transition, android.transition.TransitionValues, android.transition.TransitionValues);
+    method public void setPropagationSpeed(float);
+    method public void setSide(int);
+    field public static final int BOTTOM = 3; // 0x3
+    field public static final int LEFT = 0; // 0x0
+    field public static final int RIGHT = 2; // 0x2
+    field public static final int TOP = 1; // 0x1
+  }
+
+  public class Slide extends android.transition.Visibility {
+    ctor public Slide();
+    ctor public Slide(int);
+    method public void setSlideEdge(int);
+    field public static final int BOTTOM = 3; // 0x3
+    field public static final int LEFT = 0; // 0x0
+    field public static final int RIGHT = 2; // 0x2
+    field public static final int TOP = 1; // 0x1
+  }
+
   public abstract class Transition implements java.lang.Cloneable {
     ctor public Transition();
     method public android.transition.Transition addListener(android.transition.Transition.TransitionListener);
@@ -27443,8 +27481,11 @@
     method public android.transition.Transition excludeTarget(android.view.View, boolean);
     method public android.transition.Transition excludeTarget(java.lang.Class, boolean);
     method public long getDuration();
+    method public android.graphics.Rect getEpicenter();
+    method public android.transition.Transition.EpicenterCallback getEpicenterCallback();
     method public android.animation.TimeInterpolator getInterpolator();
     method public java.lang.String getName();
+    method public android.transition.TransitionPropagation getPropagation();
     method public long getStartDelay();
     method public java.util.List<java.lang.Integer> getTargetIds();
     method public java.util.List<android.view.View> getTargets();
@@ -27454,10 +27495,17 @@
     method public android.transition.Transition removeTarget(int);
     method public android.transition.Transition removeTarget(android.view.View);
     method public android.transition.Transition setDuration(long);
+    method public void setEpicenterCallback(android.transition.Transition.EpicenterCallback);
     method public android.transition.Transition setInterpolator(android.animation.TimeInterpolator);
+    method public void setPropagation(android.transition.TransitionPropagation);
     method public android.transition.Transition setStartDelay(long);
   }
 
+  public static abstract class Transition.EpicenterCallback {
+    ctor public Transition.EpicenterCallback();
+    method public abstract android.graphics.Rect getEpicenter(android.transition.Transition);
+  }
+
   public static abstract interface Transition.TransitionListener {
     method public abstract void onTransitionCancel(android.transition.Transition);
     method public abstract void onTransitionEnd(android.transition.Transition);
@@ -27484,6 +27532,13 @@
     method public void transitionTo(android.transition.Scene);
   }
 
+  public abstract class TransitionPropagation {
+    ctor public TransitionPropagation();
+    method public abstract void captureValues(android.transition.TransitionValues);
+    method public abstract java.lang.String[] getPropagationProperties();
+    method public abstract long getStartDelay(android.view.ViewGroup, android.transition.Transition, android.transition.TransitionValues, android.transition.TransitionValues);
+  }
+
   public class TransitionSet extends android.transition.Transition {
     ctor public TransitionSet();
     method public android.transition.TransitionSet addTransition(android.transition.Transition);
@@ -27508,7 +27563,18 @@
     method public void captureStartValues(android.transition.TransitionValues);
     method public boolean isVisible(android.transition.TransitionValues);
     method public android.animation.Animator onAppear(android.view.ViewGroup, android.transition.TransitionValues, int, android.transition.TransitionValues, int);
+    method public android.animation.Animator onAppear(android.view.ViewGroup, android.view.View, android.transition.TransitionValues, android.transition.TransitionValues);
     method public android.animation.Animator onDisappear(android.view.ViewGroup, android.transition.TransitionValues, int, android.transition.TransitionValues, int);
+    method public android.animation.Animator onDisappear(android.view.ViewGroup, android.view.View, android.transition.TransitionValues, android.transition.TransitionValues);
+  }
+
+  public abstract class VisibilityPropagation extends android.transition.TransitionPropagation {
+    ctor public VisibilityPropagation();
+    method public void captureValues(android.transition.TransitionValues);
+    method public java.lang.String[] getPropagationProperties();
+    method public int getViewVisibility(android.transition.TransitionValues);
+    method public int getViewX(android.transition.TransitionValues);
+    method public int getViewY(android.transition.TransitionValues);
   }
 
 }
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 8839ee6..4384580 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -722,8 +722,6 @@
     private static class ExitTransitionListener extends ResultReceiver
             implements Transition.TransitionListener {
         private boolean mSharedElementNotified;
-        private Transition mExitTransition;
-        private Transition mSharedElementTransition;
         private IRemoteCallback mTransitionCompleteCallback;
         private boolean mExitComplete;
         private boolean mSharedElementComplete;
@@ -733,10 +731,15 @@
                 SharedElementSource sharedElementSource) {
             super(null);
             mSharedElementSource = sharedElementSource;
-            mExitTransition = exitTransition;
-            mExitTransition.addListener(this);
-            mSharedElementTransition = sharedElementTransition;
-            mSharedElementTransition.addListener(this);
+            exitTransition.addListener(this);
+            sharedElementTransition.addListener(new Transition.TransitionListenerAdapter() {
+                @Override
+                public void onTransitionEnd(Transition transition) {
+                    mSharedElementComplete = true;
+                    notifySharedElement();
+                    transition.removeListener(this);
+                }
+            });
         }
 
         @Override
@@ -769,15 +772,9 @@
 
         @Override
         public void onTransitionEnd(Transition transition) {
-            if (transition == mExitTransition) {
-                mExitComplete = true;
-                notifyExit();
-                mExitTransition.removeListener(this);
-            } else {
-                mSharedElementComplete = true;
-                notifySharedElement();
-                mSharedElementTransition.removeListener(this);
-            }
+            mExitComplete = true;
+            notifyExit();
+            transition.removeListener(this);
         }
 
         @Override
diff --git a/core/java/android/transition/CircularPropagation.java b/core/java/android/transition/CircularPropagation.java
new file mode 100644
index 0000000..18a3d22
--- /dev/null
+++ b/core/java/android/transition/CircularPropagation.java
@@ -0,0 +1,103 @@
+/*
+ * 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.transition;
+
+import android.graphics.Rect;
+import android.util.FloatMath;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A propagation that varies with the distance to the epicenter of the Transition
+ * or center of the scene if no epicenter exists. When a View is visible in the
+ * start of the transition, Views farther from the epicenter will transition
+ * sooner than Views closer to the epicenter. When a View is not in the start
+ * of the transition or is not visible at the start of the transition, it will
+ * transition sooner when closer to the epicenter and later when farther from
+ * the epicenter. This is the default TransitionPropagation used with
+ * {@link android.transition.Explode}.
+ */
+public class CircularPropagation extends VisibilityPropagation {
+    private static final String TAG = "CircularPropagation";
+
+    private float mPropagationSpeed = 4.0f;
+
+    /**
+     * Sets the speed at which transition propagation happens, relative to the duration of the
+     * Transition. A <code>propagationSpeed</code> of 1 means that a View centered farthest from
+     * the epicenter and View centered at the epicenter will have a difference
+     * in start delay of approximately the duration of the Transition. A speed of 2 means the
+     * start delay difference will be approximately half of the duration of the transition. A
+     * value of 0 is illegal, but negative values will invert the propagation.
+     *
+     * @param propagationSpeed The speed at which propagation occurs, relative to the duration
+     *                         of the transition. A speed of 4 means it works 4 times as fast
+     *                         as the duration of the transition. May not be 0.
+     */
+    public void setPropagationSpeed(float propagationSpeed) {
+        if (propagationSpeed == 0) {
+            throw new IllegalArgumentException("propagationSpeed may not be 0");
+        }
+        mPropagationSpeed = propagationSpeed;
+    }
+
+    @Override
+    public long getStartDelay(ViewGroup sceneRoot, Transition transition,
+            TransitionValues startValues, TransitionValues endValues) {
+        if (startValues == null && endValues == null) {
+            return 0;
+        }
+        int directionMultiplier = 1;
+        TransitionValues positionValues;
+        if (endValues == null || getViewVisibility(startValues) == View.VISIBLE) {
+            positionValues = startValues;
+            directionMultiplier = -1;
+        } else {
+            positionValues = endValues;
+        }
+
+        int viewCenterX = getViewX(positionValues);
+        int viewCenterY = getViewY(positionValues);
+
+        Rect epicenter = transition.getEpicenter();
+        int epicenterX;
+        int epicenterY;
+        if (epicenter != null) {
+            epicenterX = epicenter.centerX();
+            epicenterY = epicenter.centerY();
+        } else {
+            int[] loc = new int[2];
+            sceneRoot.getLocationOnScreen(loc);
+            epicenterX = Math.round(loc[0] + (sceneRoot.getWidth() / 2)
+                    + sceneRoot.getTranslationX());
+            epicenterY = Math.round(loc[1] + (sceneRoot.getHeight() / 2)
+                    + sceneRoot.getTranslationY());
+        }
+        float distance = distance(viewCenterX, viewCenterY, epicenterX, epicenterY);
+        float maxDistance = distance(0, 0, sceneRoot.getWidth(), sceneRoot.getHeight());
+        float distanceFraction = distance/maxDistance;
+
+        return Math.round(transition.getDuration() * directionMultiplier / mPropagationSpeed
+                * distanceFraction);
+    }
+
+    private static float distance(float x1, float y1, float x2, float y2) {
+        float x = x2 - x1;
+        float y = y2 - y1;
+        return FloatMath.sqrt((x * x) + (y * y));
+    }
+}
diff --git a/core/java/android/transition/Explode.java b/core/java/android/transition/Explode.java
new file mode 100644
index 0000000..fae527c
--- /dev/null
+++ b/core/java/android/transition/Explode.java
@@ -0,0 +1,228 @@
+/*
+ * 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.transition;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.graphics.Path;
+import android.graphics.Rect;
+import android.util.FloatMath;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+
+/**
+ * This transition tracks changes to the visibility of target views in the
+ * start and end scenes and moves views in or out from the edges of the
+ * scene. Visibility is determined by both the
+ * {@link View#setVisibility(int)} state of the view as well as whether it
+ * is parented in the current view hierarchy. Disappearing Views are
+ * limited as described in {@link Visibility#onDisappear(android.view.ViewGroup,
+ * TransitionValues, int, TransitionValues, int)}.
+ * <p>Views move away from the focal View or the center of the Scene if
+ * no epicenter was provided.</p>
+ */
+public class Explode extends Visibility {
+    private static final TimeInterpolator sDecelerate = new DecelerateInterpolator();
+    private static final TimeInterpolator sAccelerate = new AccelerateInterpolator();
+    private static final String TAG = "Explode";
+
+    private static final String PROPNAME_SCREEN_BOUNDS = "android:out:screenBounds";
+
+    private int[] mTempLoc = new int[2];
+
+    public Explode() {
+        setPropagation(new CircularPropagation());
+    }
+
+    private void captureValues(TransitionValues transitionValues) {
+        View view = transitionValues.view;
+        view.getLocationOnScreen(mTempLoc);
+        int left = mTempLoc[0] + Math.round(view.getTranslationX());
+        int top = mTempLoc[1] + Math.round(view.getTranslationY());
+        int right = left + view.getWidth();
+        int bottom = top + view.getHeight();
+        transitionValues.values.put(PROPNAME_SCREEN_BOUNDS, new Rect(left, top, right, bottom));
+    }
+
+    @Override
+    public void captureStartValues(TransitionValues transitionValues) {
+        super.captureStartValues(transitionValues);
+        captureValues(transitionValues);
+    }
+
+    @Override
+    public void captureEndValues(TransitionValues transitionValues) {
+        super.captureEndValues(transitionValues);
+        captureValues(transitionValues);
+    }
+
+    private Animator createAnimation(final View view, float startX, float startY, float endX,
+            float endY, float terminalX, float terminalY, TimeInterpolator interpolator) {
+        view.setTranslationX(startX);
+        view.setTranslationY(startY);
+        if (startY == endY && startX == endX) {
+            return null;
+        }
+        Path path = new Path();
+        path.moveTo(startX, startY);
+        path.lineTo(endX, endY);
+        ObjectAnimator pathAnimator = ObjectAnimator.ofFloat(view, View.TRANSLATION_X,
+                View.TRANSLATION_Y, path);
+        pathAnimator.setInterpolator(interpolator);
+        OutAnimatorListener listener = new OutAnimatorListener(view, terminalX, terminalY,
+                endX, endY);
+        pathAnimator.addListener(listener);
+        pathAnimator.addPauseListener(listener);
+
+        return pathAnimator;
+    }
+
+    @Override
+    public Animator onAppear(ViewGroup sceneRoot, View view,
+            TransitionValues startValues, TransitionValues endValues) {
+        if (endValues == null) {
+            return null;
+        }
+        Rect bounds = (Rect) endValues.values.get(PROPNAME_SCREEN_BOUNDS);
+        calculateOut(sceneRoot, bounds, mTempLoc);
+
+        final float endX = view.getTranslationX();
+        final float startX = endX + mTempLoc[0];
+        final float endY = view.getTranslationY();
+        final float startY = endY + mTempLoc[1];
+
+        return createAnimation(view, startX, startY, endX, endY, endX, endY, sDecelerate);
+    }
+
+    @Override
+    public Animator onDisappear(ViewGroup sceneRoot, View view,
+            TransitionValues startValues, TransitionValues endValues) {
+        Rect bounds = (Rect) startValues.values.get(PROPNAME_SCREEN_BOUNDS);
+        calculateOut(sceneRoot, bounds, mTempLoc);
+
+        final float startX = view.getTranslationX();
+        final float endX = startX + mTempLoc[0];
+        final float startY = view.getTranslationY();
+        final float endY = startY + mTempLoc[1];
+
+        return createAnimation(view, startX, startY, endX, endY, startX, startY,
+                sAccelerate);
+    }
+
+    private void calculateOut(View sceneRoot, Rect bounds, int[] outVector) {
+        sceneRoot.getLocationOnScreen(mTempLoc);
+        int sceneRootX = mTempLoc[0];
+        int sceneRootY = mTempLoc[1];
+        int focalX;
+        int focalY;
+
+        Rect epicenter = getEpicenter();
+        if (epicenter == null) {
+            focalX = sceneRootX + (sceneRoot.getWidth() / 2)
+                    + Math.round(sceneRoot.getTranslationX());
+            focalY = sceneRootY + (sceneRoot.getHeight() / 2)
+                    + Math.round(sceneRoot.getTranslationY());
+        } else {
+            focalX = epicenter.centerX();
+            focalY = epicenter.centerY();
+        }
+
+        int centerX = bounds.centerX();
+        int centerY = bounds.centerY();
+        float xVector = centerX - focalX;
+        float yVector = centerY - focalY;
+
+        if (xVector == 0 && yVector == 0) {
+            // Random direction when View is centered on focal View.
+            xVector = (float)(Math.random() * 2) - 1;
+            yVector = (float)(Math.random() * 2) - 1;
+        }
+        float vectorSize = calculateDistance(xVector, yVector);
+        xVector /= vectorSize;
+        yVector /= vectorSize;
+
+        float maxDistance =
+                calculateMaxDistance(sceneRoot, focalX - sceneRootX, focalY - sceneRootY);
+
+        outVector[0] = Math.round(maxDistance * xVector);
+        outVector[1] = Math.round(maxDistance * yVector);
+    }
+
+    private static float calculateMaxDistance(View sceneRoot, int focalX, int focalY) {
+        int maxX = Math.max(focalX, sceneRoot.getWidth() - focalX);
+        int maxY = Math.max(focalY, sceneRoot.getHeight() - focalY);
+        return calculateDistance(maxX, maxY);
+    }
+
+    private static float calculateDistance(float x, float y) {
+        return FloatMath.sqrt((x * x) + (y * y));
+    }
+
+    private static class OutAnimatorListener extends AnimatorListenerAdapter {
+        private final View mView;
+        private boolean mCanceled = false;
+        private float mPausedX;
+        private float mPausedY;
+        private final float mTerminalX;
+        private final float mTerminalY;
+        private final float mEndX;
+        private final float mEndY;
+
+        public OutAnimatorListener(View view, float terminalX, float terminalY,
+                float endX, float endY) {
+            mView = view;
+            mTerminalX = terminalX;
+            mTerminalY = terminalY;
+            mEndX = endX;
+            mEndY = endY;
+        }
+
+        @Override
+        public void onAnimationCancel(Animator animator) {
+            mView.setTranslationX(mTerminalX);
+            mView.setTranslationY(mTerminalY);
+            mCanceled = true;
+        }
+
+        @Override
+        public void onAnimationEnd(Animator animator) {
+            if (!mCanceled) {
+                mView.setTranslationX(mTerminalX);
+                mView.setTranslationY(mTerminalY);
+            }
+        }
+
+        @Override
+        public void onAnimationPause(Animator animator) {
+            mPausedX = mView.getTranslationX();
+            mPausedY = mView.getTranslationY();
+            mView.setTranslationY(mEndX);
+            mView.setTranslationY(mEndY);
+        }
+
+        @Override
+        public void onAnimationResume(Animator animator) {
+            mView.setTranslationX(mPausedX);
+            mView.setTranslationY(mPausedY);
+        }
+    }
+}
diff --git a/core/java/android/transition/Fade.java b/core/java/android/transition/Fade.java
index 8edb1ff..08e27d3 100644
--- a/core/java/android/transition/Fade.java
+++ b/core/java/android/transition/Fade.java
@@ -59,8 +59,6 @@
     private static boolean DBG = Transition.DBG && false;
 
     private static final String LOG_TAG = "Fade";
-    private static final String PROPNAME_SCREEN_X = "android:fade:screenX";
-    private static final String PROPNAME_SCREEN_Y = "android:fade:screenY";
 
     /**
      * Fading mode used in {@link #Fade(int)} to make the transition
@@ -98,245 +96,81 @@
     /**
      * Utility method to handle creating and running the Animator.
      */
-    private Animator createAnimation(View view, float startAlpha, float endAlpha,
-            AnimatorListenerAdapter listener) {
+    private Animator createAnimation(View view, float startAlpha, float endAlpha) {
         if (startAlpha == endAlpha) {
-            // run listener if we're noop'ing the animation, to get the end-state results now
-            if (listener != null) {
-                listener.onAnimationEnd(null);
-            }
             return null;
         }
-        final ObjectAnimator anim = ObjectAnimator.ofFloat(view, "transitionAlpha", startAlpha,
-                endAlpha);
+        view.setTransitionAlpha(startAlpha);
+        final ObjectAnimator anim = ObjectAnimator.ofFloat(view, "transitionAlpha", endAlpha);
         if (DBG) {
             Log.d(LOG_TAG, "Created animator " + anim);
         }
-        if (listener != null) {
-            anim.addListener(listener);
-            anim.addPauseListener(listener);
-        }
+        FadeAnimatorListener listener = new FadeAnimatorListener(view, endAlpha);
+        anim.addListener(listener);
+        anim.addPauseListener(listener);
         return anim;
     }
 
-    private void captureValues(TransitionValues transitionValues) {
-        int[] loc = new int[2];
-        transitionValues.view.getLocationOnScreen(loc);
-        transitionValues.values.put(PROPNAME_SCREEN_X, loc[0]);
-        transitionValues.values.put(PROPNAME_SCREEN_Y, loc[1]);
-    }
-
     @Override
-    public void captureStartValues(TransitionValues transitionValues) {
-        super.captureStartValues(transitionValues);
-        captureValues(transitionValues);
-    }
-
-    @Override
-    public Animator onAppear(ViewGroup sceneRoot,
-            TransitionValues startValues, int startVisibility,
-            TransitionValues endValues, int endVisibility) {
+    public Animator onAppear(ViewGroup sceneRoot, View view,
+            TransitionValues startValues,
+            TransitionValues endValues) {
         if ((mFadingMode & IN) != IN || endValues == null) {
             return null;
         }
-        final View endView = endValues.view;
         if (DBG) {
             View startView = (startValues != null) ? startValues.view : null;
             Log.d(LOG_TAG, "Fade.onAppear: startView, startVis, endView, endVis = " +
-                    startView + ", " + startVisibility + ", " + endView + ", " + endVisibility);
+                    startView + ", " + view);
         }
-        endView.setTransitionAlpha(0);
-        TransitionListener transitionListener = new TransitionListenerAdapter() {
-            boolean mCanceled = false;
-            float mPausedAlpha;
-
-            @Override
-            public void onTransitionCancel(Transition transition) {
-                endView.setTransitionAlpha(1);
-                mCanceled = true;
-            }
-
-            @Override
-            public void onTransitionEnd(Transition transition) {
-                if (!mCanceled) {
-                    endView.setTransitionAlpha(1);
-                }
-            }
-
-            @Override
-            public void onTransitionPause(Transition transition) {
-                mPausedAlpha = endView.getTransitionAlpha();
-                endView.setTransitionAlpha(1);
-            }
-
-            @Override
-            public void onTransitionResume(Transition transition) {
-                endView.setTransitionAlpha(mPausedAlpha);
-            }
-        };
-        addListener(transitionListener);
-        return createAnimation(endView, 0, 1, null);
+        return createAnimation(view, 0, 1);
     }
 
     @Override
-    public Animator onDisappear(ViewGroup sceneRoot,
-            TransitionValues startValues, int startVisibility,
-            TransitionValues endValues, int endVisibility) {
+    public Animator onDisappear(ViewGroup sceneRoot, final View view, TransitionValues startValues,
+            TransitionValues endValues) {
         if ((mFadingMode & OUT) != OUT) {
             return null;
         }
-        View view = null;
-        View startView = (startValues != null) ? startValues.view : null;
-        View endView = (endValues != null) ? endValues.view : null;
-        if (DBG) {
-            Log.d(LOG_TAG, "Fade.onDisappear: startView, startVis, endView, endVis = " +
-                        startView + ", " + startVisibility + ", " + endView + ", " + endVisibility);
-        }
-        View overlayView = null;
-        View viewToKeep = null;
-        if (endView == null || endView.getParent() == null) {
-            if (endView != null) {
-                // endView was removed from its parent - add it to the overlay
-                view = overlayView = endView;
-            } else if (startView != null) {
-                // endView does not exist. Use startView only under certain
-                // conditions, because placing a view in an overlay necessitates
-                // it being removed from its current parent
-                if (startView.getParent() == null) {
-                    // no parent - safe to use
-                    view = overlayView = startView;
-                } else if (startView.getParent() instanceof View &&
-                        startView.getParent().getParent() == null) {
-                    View startParent = (View) startView.getParent();
-                    int id = startParent.getId();
-                    if (id != View.NO_ID && sceneRoot.findViewById(id) != null && mCanRemoveViews) {
-                        // no parent, but its parent is unparented  but the parent
-                        // hierarchy has been replaced by a new hierarchy with the same id
-                        // and it is safe to un-parent startView
-                        view = overlayView = startView;
-                    }
-                }
-            }
-        } else {
-            // visibility change
-            if (endVisibility == View.INVISIBLE) {
-                view = endView;
-                viewToKeep = view;
-            } else {
-                // Becoming GONE
-                if (startView == endView) {
-                    view = endView;
-                    viewToKeep = view;
-                } else {
-                    view = startView;
-                    overlayView = view;
-                }
-            }
-        }
-        final int finalVisibility = endVisibility;
-        // TODO: add automatic facility to Visibility superclass for keeping views around
-        if (overlayView != null) {
-            // TODO: Need to do this for general case of adding to overlay
-            int screenX = (Integer) startValues.values.get(PROPNAME_SCREEN_X);
-            int screenY = (Integer) startValues.values.get(PROPNAME_SCREEN_Y);
-            int[] loc = new int[2];
-            sceneRoot.getLocationOnScreen(loc);
-            overlayView.offsetLeftAndRight((screenX - loc[0]) - overlayView.getLeft());
-            overlayView.offsetTopAndBottom((screenY - loc[1]) - overlayView.getTop());
-            sceneRoot.getOverlay().add(overlayView);
-            // TODO: add automatic facility to Visibility superclass for keeping views around
-            final float startAlpha = 1;
-            float endAlpha = 0;
-            final View finalView = view;
-            final View finalOverlayView = overlayView;
-            final View finalViewToKeep = viewToKeep;
-            final ViewGroup finalSceneRoot = sceneRoot;
-            final AnimatorListenerAdapter endListener = new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    finalView.setTransitionAlpha(startAlpha);
-                    // TODO: restore view offset from overlay repositioning
-                    if (finalViewToKeep != null) {
-                        finalViewToKeep.setVisibility(finalVisibility);
-                    }
-                    if (finalOverlayView != null) {
-                        finalSceneRoot.getOverlay().remove(finalOverlayView);
-                    }
-                }
 
-                @Override
-                public void onAnimationPause(Animator animation) {
-                    if (finalOverlayView != null) {
-                        finalSceneRoot.getOverlay().remove(finalOverlayView);
-                    }
-                }
-
-                @Override
-                public void onAnimationResume(Animator animation) {
-                    if (finalOverlayView != null) {
-                        finalSceneRoot.getOverlay().add(finalOverlayView);
-                    }
-                }
-            };
-            return createAnimation(view, startAlpha, endAlpha, endListener);
-        }
-        if (viewToKeep != null) {
-            // TODO: find a different way to do this, like just changing the view to be
-            // VISIBLE for the duration of the transition
-            viewToKeep.setVisibility((View.VISIBLE));
-            // TODO: add automatic facility to Visibility superclass for keeping views around
-            final float startAlpha = 1;
-            float endAlpha = 0;
-            final View finalView = view;
-            final View finalOverlayView = overlayView;
-            final View finalViewToKeep = viewToKeep;
-            final ViewGroup finalSceneRoot = sceneRoot;
-            final AnimatorListenerAdapter endListener = new AnimatorListenerAdapter() {
-                boolean mCanceled = false;
-                float mPausedAlpha = -1;
-
-                @Override
-                public void onAnimationPause(Animator animation) {
-                    if (finalViewToKeep != null && !mCanceled) {
-                        finalViewToKeep.setVisibility(finalVisibility);
-                    }
-                    mPausedAlpha = finalView.getTransitionAlpha();
-                    finalView.setTransitionAlpha(startAlpha);
-                }
-
-                @Override
-                public void onAnimationResume(Animator animation) {
-                    if (finalViewToKeep != null && !mCanceled) {
-                        finalViewToKeep.setVisibility(View.VISIBLE);
-                    }
-                    finalView.setTransitionAlpha(mPausedAlpha);
-                }
-
-                @Override
-                public void onAnimationCancel(Animator animation) {
-                    mCanceled = true;
-                    if (mPausedAlpha >= 0) {
-                        finalView.setTransitionAlpha(mPausedAlpha);
-                    }
-                }
-
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    if (!mCanceled) {
-                        finalView.setTransitionAlpha(startAlpha);
-                    }
-                    // TODO: restore view offset from overlay repositioning
-                    if (finalViewToKeep != null && !mCanceled) {
-                        finalViewToKeep.setVisibility(finalVisibility);
-                    }
-                    if (finalOverlayView != null) {
-                        finalSceneRoot.getOverlay().remove(finalOverlayView);
-                    }
-                }
-            };
-            return createAnimation(view, startAlpha, endAlpha, endListener);
-        }
-        return null;
+        return createAnimation(view, 1, 0);
     }
 
-}
\ No newline at end of file
+    private static class FadeAnimatorListener extends AnimatorListenerAdapter {
+        private final View mView;
+        private final float mEndAlpha;
+        private boolean mCanceled = false;
+        private float mPausedAlpha;
+
+        public FadeAnimatorListener(View view, float endAlpha) {
+            mView = view;
+            mEndAlpha = endAlpha;
+        }
+
+        @Override
+        public void onAnimationCancel(Animator animator) {
+            mCanceled = true;
+            if (mPausedAlpha >= 0) {
+                mView.setTransitionAlpha(mPausedAlpha);
+            }
+        }
+
+        @Override
+        public void onAnimationEnd(Animator animator) {
+            if (!mCanceled) {
+                mView.setTransitionAlpha(mEndAlpha);
+            }
+        }
+
+        @Override
+        public void onAnimationPause(Animator animator) {
+            mPausedAlpha = mView.getTransitionAlpha();
+            mView.setTransitionAlpha(mEndAlpha);
+        }
+
+        @Override
+        public void onAnimationResume(Animator animator) {
+            mView.setTransitionAlpha(mPausedAlpha);
+        }
+    }
+}
diff --git a/core/java/android/transition/MatrixClippedDrawable.java b/core/java/android/transition/MatrixClippedDrawable.java
new file mode 100644
index 0000000..ebaad59
--- /dev/null
+++ b/core/java/android/transition/MatrixClippedDrawable.java
@@ -0,0 +1,300 @@
+/*
+ * 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.transition;
+
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.Property;
+
+/**
+ * Used in MoveImage to mock an ImageView as a Drawable to be scaled in the scene root Overlay.
+ * @hide
+ */
+class MatrixClippedDrawable extends Drawable implements Drawable.Callback {
+    private static final String TAG = "MatrixClippedDrawable";
+
+    private ClippedMatrixState mClippedMatrixState;
+
+    public static final Property<MatrixClippedDrawable, Rect> CLIP_PROPERTY
+            = new Property<MatrixClippedDrawable, Rect>(Rect.class, "clipRect") {
+
+        @Override
+        public Rect get(MatrixClippedDrawable object) {
+            return object.getClipRect();
+        }
+
+        @Override
+        public void set(MatrixClippedDrawable object, Rect value) {
+            object.setClipRect(value);
+        }
+    };
+
+    public static final Property<MatrixClippedDrawable, Matrix> MATRIX_PROPERTY
+            = new Property<MatrixClippedDrawable, Matrix>(Matrix.class, "matrix") {
+        @Override
+        public void set(MatrixClippedDrawable object, Matrix value) {
+            object.setMatrix(value);
+        }
+
+        @Override
+        public Matrix get(MatrixClippedDrawable object) {
+            return object.getMatrix();
+        }
+    };
+
+    public MatrixClippedDrawable(Drawable drawable) {
+        this(null, null);
+
+        mClippedMatrixState.mDrawable = drawable;
+
+        if (drawable != null) {
+            drawable.setCallback(this);
+        }
+    }
+
+    public void setMatrix(Matrix matrix) {
+        if (matrix == null) {
+            mClippedMatrixState.mMatrix = null;
+        } else {
+            if (mClippedMatrixState.mMatrix == null) {
+                mClippedMatrixState.mMatrix = new Matrix();
+            }
+            mClippedMatrixState.mMatrix.set(matrix);
+        }
+        invalidateSelf();
+    }
+
+    public Matrix getMatrix() {
+        return mClippedMatrixState.mMatrix;
+    }
+
+    public Rect getClipRect() {
+        return mClippedMatrixState.mClipRect;
+    }
+
+    public void setClipRect(Rect clipRect) {
+        if (clipRect == null) {
+            if (mClippedMatrixState.mClipRect != null) {
+                mClippedMatrixState.mClipRect = null;
+                invalidateSelf();
+            }
+        } else {
+            if (mClippedMatrixState.mClipRect == null) {
+                mClippedMatrixState.mClipRect = new Rect(clipRect);
+            } else {
+                mClippedMatrixState.mClipRect.set(clipRect);
+            }
+            invalidateSelf();
+        }
+    }
+
+    // overrides from Drawable.Callback
+
+    public void invalidateDrawable(Drawable who) {
+        final Drawable.Callback callback = getCallback();
+        if (callback != null) {
+            callback.invalidateDrawable(this);
+        }
+    }
+
+    public void scheduleDrawable(Drawable who, Runnable what, long when) {
+        final Drawable.Callback callback = getCallback();
+        if (callback != null) {
+            callback.scheduleDrawable(this, what, when);
+        }
+    }
+
+    public void unscheduleDrawable(Drawable who, Runnable what) {
+        final Drawable.Callback callback = getCallback();
+        if (callback != null) {
+            callback.unscheduleDrawable(this, what);
+        }
+    }
+
+    // overrides from Drawable
+
+    @Override
+    public int getChangingConfigurations() {
+        return super.getChangingConfigurations()
+                | mClippedMatrixState.mChangingConfigurations
+                | mClippedMatrixState.mDrawable.getChangingConfigurations();
+    }
+
+    @Override
+    public boolean getPadding(Rect padding) {
+        // XXX need to adjust padding!
+        return mClippedMatrixState.mDrawable.getPadding(padding);
+    }
+
+    @Override
+    public boolean setVisible(boolean visible, boolean restart) {
+        mClippedMatrixState.mDrawable.setVisible(visible, restart);
+        return super.setVisible(visible, restart);
+    }
+
+    @Override
+    public void setAlpha(int alpha) {
+        mClippedMatrixState.mDrawable.setAlpha(alpha);
+    }
+
+    @Override
+    public int getAlpha() {
+        return mClippedMatrixState.mDrawable.getAlpha();
+    }
+
+    @Override
+    public void setColorFilter(ColorFilter cf) {
+        mClippedMatrixState.mDrawable.setColorFilter(cf);
+    }
+
+    @Override
+    public int getOpacity() {
+        return mClippedMatrixState.mDrawable.getOpacity();
+    }
+
+    @Override
+    public boolean isStateful() {
+        return mClippedMatrixState.mDrawable.isStateful();
+    }
+
+    @Override
+    protected boolean onStateChange(int[] state) {
+        return mClippedMatrixState.mDrawable.setState(state);
+    }
+
+    @Override
+    protected boolean onLevelChange(int level) {
+        mClippedMatrixState.mDrawable.setLevel(level);
+        invalidateSelf();
+        return true;
+    }
+
+    @Override
+    protected void onBoundsChange(Rect bounds) {
+        super.setBounds(bounds);
+        if (mClippedMatrixState.mMatrix == null) {
+            mClippedMatrixState.mDrawable.setBounds(bounds);
+        } else {
+            int drawableWidth = mClippedMatrixState.mDrawable.getIntrinsicWidth();
+            int drawableHeight = mClippedMatrixState.mDrawable.getIntrinsicHeight();
+            mClippedMatrixState.mDrawable.setBounds(bounds.left, bounds.top,
+                    drawableWidth + bounds.left, drawableHeight + bounds.top);
+        }
+        invalidateSelf();
+    }
+
+    @Override
+    public void draw(Canvas canvas) {
+        Rect bounds = getBounds();
+        int left = bounds.left;
+        int top = bounds.top;
+        int saveCount = canvas.getSaveCount();
+        canvas.save();
+        if (mClippedMatrixState.mClipRect != null) {
+            canvas.clipRect(mClippedMatrixState.mClipRect);
+        } else {
+            canvas.clipRect(bounds);
+        }
+
+        if (mClippedMatrixState != null && !mClippedMatrixState.mMatrix.isIdentity()) {
+            canvas.translate(left, top);
+            canvas.concat(mClippedMatrixState.mMatrix);
+            canvas.translate(-left, -top);
+        }
+        mClippedMatrixState.mDrawable.draw(canvas);
+        canvas.restoreToCount(saveCount);
+    }
+
+    @Override
+    public int getIntrinsicWidth() {
+        return mClippedMatrixState.mDrawable.getIntrinsicWidth();
+    }
+
+    @Override
+    public int getIntrinsicHeight() {
+        return mClippedMatrixState.mDrawable.getIntrinsicHeight();
+    }
+
+    @Override
+    public Drawable.ConstantState getConstantState() {
+        if (mClippedMatrixState.canConstantState()) {
+            mClippedMatrixState.mChangingConfigurations = getChangingConfigurations();
+            return mClippedMatrixState;
+        }
+        return null;
+    }
+
+    final static class ClippedMatrixState extends Drawable.ConstantState {
+        Drawable mDrawable;
+        Matrix mMatrix;
+        Rect mClipRect;
+
+        private boolean mCheckedConstantState;
+        private boolean mCanConstantState;
+        int mChangingConfigurations;
+
+        ClippedMatrixState(ClippedMatrixState orig, MatrixClippedDrawable owner, Resources res) {
+            if (orig != null) {
+                if (res != null) {
+                    mDrawable = orig.mDrawable.getConstantState().newDrawable(res);
+                } else {
+                    mDrawable = orig.mDrawable.getConstantState().newDrawable();
+                }
+                mDrawable.setCallback(owner);
+                mCheckedConstantState = mCanConstantState = true;
+                if (orig.mMatrix != null) {
+                    mMatrix = new Matrix(orig.mMatrix);
+                }
+                if (orig.mClipRect != null) {
+                    mClipRect = new Rect(orig.mClipRect);
+                }
+            }
+        }
+
+        @Override
+        public Drawable newDrawable() {
+            return new MatrixClippedDrawable(this, null);
+        }
+
+        @Override
+        public Drawable newDrawable(Resources res) {
+            return new MatrixClippedDrawable(this, res);
+        }
+
+        @Override
+        public int getChangingConfigurations() {
+            return mChangingConfigurations;
+        }
+
+        boolean canConstantState() {
+            if (!mCheckedConstantState) {
+                mCanConstantState = mDrawable.getConstantState() != null;
+                mCheckedConstantState = true;
+            }
+
+            return mCanConstantState;
+        }
+    }
+
+    private MatrixClippedDrawable(ClippedMatrixState state, Resources res) {
+        mClippedMatrixState = new ClippedMatrixState(state, this, res);
+    }
+
+}
diff --git a/core/java/android/transition/MoveImage.java b/core/java/android/transition/MoveImage.java
new file mode 100644
index 0000000..d68e971
--- /dev/null
+++ b/core/java/android/transition/MoveImage.java
@@ -0,0 +1,326 @@
+/*
+ * 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.transition;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.animation.PropertyValuesHolder;
+import android.animation.RectEvaluator;
+import android.animation.TypeEvaluator;
+import android.animation.ValueAnimator;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.graphics.drawable.Drawable;
+import android.util.FloatMath;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroupOverlay;
+import android.view.ViewParent;
+import android.widget.ImageView;
+
+import java.util.ArrayList;
+import java.util.Map;
+
+/**
+ * Transitions ImageViews, including size, scaleType, and matrix. The ImageView drawable
+ * must remain the same between both start and end states, but the
+ * {@link ImageView#setScaleType(android.widget.ImageView.ScaleType)} may
+ * differ.
+ */
+public class MoveImage extends Transition {
+    private static final String TAG = "MoveImage";
+    private static final String PROPNAME_MATRIX = "android:moveImage:matrix";
+    private static final String PROPNAME_BOUNDS = "android:moveImage:bounds";
+    private static final String PROPNAME_CLIP = "android:moveImage:clip";
+    private static final String PROPNAME_DRAWABLE = "android:moveImage:drawable";
+
+    private int[] mTempLoc = new int[2];
+
+    private static final String[] sTransitionProperties = {
+            PROPNAME_MATRIX,
+            PROPNAME_BOUNDS,
+            PROPNAME_CLIP,
+            PROPNAME_DRAWABLE,
+    };
+
+    private void captureValues(TransitionValues transitionValues) {
+        View view = transitionValues.view;
+        if (!(view instanceof ImageView) || view.getVisibility() != View.VISIBLE) {
+            return;
+        }
+        Map<String, Object> values = transitionValues.values;
+
+        ViewGroup parent = (ViewGroup) view.getParent();
+        parent.getLocationInWindow(mTempLoc);
+        int paddingLeft = view.getPaddingLeft();
+        int paddingTop = view.getPaddingTop();
+        int paddingRight = view.getPaddingRight();
+        int paddingBottom = view.getPaddingBottom();
+        int left = mTempLoc[0] + paddingLeft + view.getLeft() + Math.round(view.getTranslationX());
+        int top = mTempLoc[1] + paddingTop + view.getTop() + Math.round(view.getTranslationY());
+        int right = left + view.getWidth() - paddingRight - paddingLeft;
+        int bottom = top + view.getHeight() - paddingTop - paddingBottom;
+
+        Rect bounds = new Rect(left, top, right, bottom);
+        values.put(PROPNAME_BOUNDS, bounds);
+        ImageView imageView = (ImageView) view;
+        Matrix matrix = getMatrix(imageView);
+        values.put(PROPNAME_MATRIX, matrix);
+        values.put(PROPNAME_CLIP, findClip(imageView));
+        values.put(PROPNAME_DRAWABLE, imageView.getDrawable());
+    }
+
+    @Override
+    public void captureStartValues(TransitionValues transitionValues) {
+        captureValues(transitionValues);
+    }
+
+    @Override
+    public void captureEndValues(TransitionValues transitionValues) {
+        captureValues(transitionValues);
+    }
+
+    @Override
+    public String[] getTransitionProperties() {
+        return sTransitionProperties;
+    }
+
+    /**
+     * Creates an Animator for ImageViews moving, changing dimensions, and/or changing
+     * {@link android.widget.ImageView.ScaleType}.
+     * @param sceneRoot The root of the transition hierarchy.
+     * @param startValues The values for a specific target in the start scene.
+     * @param endValues The values for the target in the end scene.
+     * @return An Animator to move an ImageView or null if the View is not an ImageView,
+     * the Drawable changed, the View is not VISIBLE, or there was no change.
+     */
+    @Override
+    public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
+            TransitionValues endValues) {
+        if (startValues == null || endValues == null
+                || startValues.values.get(PROPNAME_BOUNDS) == null
+                || endValues.values.get(PROPNAME_BOUNDS) == null
+                || startValues.values.get(PROPNAME_DRAWABLE)
+                        != endValues.values.get(PROPNAME_DRAWABLE)) {
+            return null;
+        }
+        ArrayList<PropertyValuesHolder> changes = new ArrayList<PropertyValuesHolder>();
+
+        Matrix startMatrix = (Matrix) startValues.values.get(PROPNAME_MATRIX);
+        Matrix endMatrix = (Matrix) endValues.values.get(PROPNAME_MATRIX);
+
+        if (!startMatrix.equals(endMatrix)) {
+            changes.add(PropertyValuesHolder.ofObject(MatrixClippedDrawable.MATRIX_PROPERTY,
+                    new MatrixEvaluator(), startMatrix, endMatrix));
+        }
+
+        sceneRoot.getLocationInWindow(mTempLoc);
+        int rootX = mTempLoc[0];
+        int rootY = mTempLoc[1];
+        final ImageView imageView = (ImageView) endValues.view;
+
+        Drawable drawable = imageView.getDrawable();
+
+        Rect startBounds = new Rect((Rect) startValues.values.get(PROPNAME_BOUNDS));
+        Rect endBounds = new Rect((Rect) endValues.values.get(PROPNAME_BOUNDS));
+        startBounds.offset(-rootX, -rootY);
+        endBounds.offset(-rootX, -rootY);
+
+        if (!startBounds.equals(endBounds)) {
+            changes.add(PropertyValuesHolder.ofObject("bounds", new RectEvaluator(new Rect()),
+                    startBounds, endBounds));
+        }
+
+        Rect startClip = (Rect) startValues.values.get(PROPNAME_CLIP);
+        Rect endClip = (Rect) endValues.values.get(PROPNAME_CLIP);
+        if (startClip != null || endClip != null) {
+            startClip = nonNullClip(startClip, sceneRoot, rootX, rootY);
+            endClip = nonNullClip(endClip, sceneRoot, rootX, rootY);
+
+            expandClip(startBounds, startMatrix, startClip, endClip);
+            expandClip(endBounds, endMatrix, endClip, startClip);
+            boolean clipped = !startClip.contains(startBounds) || !endClip.contains(endBounds);
+            if (!clipped) {
+                startClip = null;
+            } else if (!startClip.equals(endClip)) {
+                changes.add(PropertyValuesHolder.ofObject(MatrixClippedDrawable.CLIP_PROPERTY,
+                        new RectEvaluator(), startClip, endClip));
+            }
+        }
+
+        if (changes.isEmpty()) {
+            return null;
+        }
+
+        drawable = drawable.getConstantState().newDrawable();
+        final MatrixClippedDrawable matrixClippedDrawable = new MatrixClippedDrawable(drawable);
+        matrixClippedDrawable.setMatrix(startMatrix);
+        matrixClippedDrawable.setBounds(startBounds);
+        matrixClippedDrawable.setClipRect(startClip);
+
+        imageView.setVisibility(View.INVISIBLE);
+        final ViewGroupOverlay overlay = sceneRoot.getOverlay();
+        overlay.add(matrixClippedDrawable);
+        ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(matrixClippedDrawable,
+                changes.toArray(new PropertyValuesHolder[changes.size()]));
+
+        AnimatorListenerAdapter listener = new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                imageView.setVisibility(View.VISIBLE);
+                overlay.remove(matrixClippedDrawable);
+            }
+
+            @Override
+            public void onAnimationPause(Animator animation) {
+                imageView.setVisibility(View.VISIBLE);
+                overlay.remove(matrixClippedDrawable);
+            }
+
+            @Override
+            public void onAnimationResume(Animator animation) {
+                imageView.setVisibility(View.INVISIBLE);
+                overlay.add(matrixClippedDrawable);
+            }
+        };
+
+        animator.addListener(listener);
+        animator.addPauseListener(listener);
+
+        return animator;
+    }
+
+    private static Rect nonNullClip(Rect clip, ViewGroup sceneRoot, int rootX, int rootY) {
+        if (clip != null) {
+            clip = new Rect(clip);
+            clip.offset(-rootX, -rootY);
+        } else {
+            clip = new Rect(0, 0, sceneRoot.getWidth(), sceneRoot.getHeight());
+        }
+        return clip;
+    }
+
+    private static void expandClip(Rect bounds, Matrix matrix, Rect clip, Rect otherClip) {
+        RectF boundsF = new RectF(bounds);
+        matrix.mapRect(boundsF);
+        clip.left = expandMinDimension(boundsF.left, clip.left, otherClip.left);
+        clip.top = expandMinDimension(boundsF.top, clip.top, otherClip.top);
+        clip.right = expandMaxDimension(boundsF.right, clip.right, otherClip.right);
+        clip.bottom = expandMaxDimension(boundsF.bottom, clip.bottom, otherClip.bottom);
+    }
+
+    private static int expandMinDimension(float boundsDimension, int clipDimension,
+            int otherClipDimension) {
+        if (clipDimension > boundsDimension) {
+            // Already clipped in that dimension, return the clipped value
+            return clipDimension;
+        }
+        return Math.min(clipDimension, otherClipDimension);
+    }
+
+    private static int expandMaxDimension(float boundsDimension, int clipDimension,
+            int otherClipDimension) {
+        return -expandMinDimension(-boundsDimension, -clipDimension, -otherClipDimension);
+    }
+
+    private static Matrix getMatrix(ImageView imageView) {
+        Drawable drawable = imageView.getDrawable();
+        int drawableWidth = drawable.getIntrinsicWidth();
+        int drawableHeight = drawable.getIntrinsicHeight();
+        ImageView.ScaleType scaleType = imageView.getScaleType();
+        if (drawableWidth <= 0 || drawableHeight <= 0 || scaleType == ImageView.ScaleType.FIT_XY) {
+            return null;
+        }
+        return new Matrix(imageView.getImageMatrix());
+    }
+
+    private Rect findClip(ImageView imageView) {
+        if (imageView.getCropToPadding()) {
+            Rect clip = getClip(imageView);
+            clip.left += imageView.getPaddingLeft();
+            clip.right -= imageView.getPaddingRight();
+            clip.top += imageView.getPaddingTop();
+            clip.bottom -= imageView.getPaddingBottom();
+            return clip;
+        } else {
+            View view = imageView;
+            ViewParent viewParent;
+            while ((viewParent = view.getParent()) instanceof ViewGroup) {
+                ViewGroup viewGroup = (ViewGroup) viewParent;
+                if (viewGroup.getClipChildren()) {
+                    Rect clip = getClip(view);
+                    return clip;
+                }
+                view = viewGroup;
+            }
+        }
+        return null;
+    }
+
+    private Rect getClip(View clipView) {
+        Rect clipBounds = clipView.getClipBounds();
+        if (clipBounds == null) {
+            clipBounds = new Rect(clipView.getLeft(), clipView.getTop(),
+                    clipView.getRight(), clipView.getBottom());
+        }
+
+        ViewParent parent = clipView.getParent();
+        if (parent instanceof ViewGroup) {
+            ViewGroup parentViewGroup = (ViewGroup) parent;
+            parentViewGroup.getLocationInWindow(mTempLoc);
+            clipBounds.offset(mTempLoc[0], mTempLoc[1]);
+        }
+
+        return clipBounds;
+    }
+
+    @Override
+    public Transition clone() {
+        MoveImage clone = (MoveImage) super.clone();
+        clone.mTempLoc = new int[2];
+        return clone;
+    }
+
+    private static class MatrixEvaluator implements TypeEvaluator<Matrix> {
+        static final Matrix sIdentity = new Matrix();
+        float[] mTempStartValues = new float[9];
+        float[] mTempEndValues = new float[9];
+        Matrix mTempMatrix = new Matrix();
+
+        @Override
+        public Matrix evaluate(float fraction, Matrix startValue, Matrix endValue) {
+            if (startValue == null && endValue == null) {
+                return null;
+            }
+            if (startValue == null) {
+                startValue = sIdentity;
+            } else if (endValue == null) {
+                endValue = sIdentity;
+            }
+            startValue.getValues(mTempStartValues);
+            endValue.getValues(mTempEndValues);
+            for (int i = 0; i < 9; i++) {
+                float diff = mTempEndValues[i] - mTempStartValues[i];
+                mTempEndValues[i] = mTempStartValues[i] + (fraction * diff);
+            }
+            mTempMatrix.setValues(mTempEndValues);
+            return mTempMatrix;
+        }
+    }
+}
diff --git a/core/java/android/transition/SidePropagation.java b/core/java/android/transition/SidePropagation.java
new file mode 100644
index 0000000..c331945
--- /dev/null
+++ b/core/java/android/transition/SidePropagation.java
@@ -0,0 +1,165 @@
+/*
+ * 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.transition;
+
+import android.graphics.Rect;
+import android.util.FloatMath;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+/**
+ * A <code>TransitionPropagation</code> that propagates based on the distance to the side
+ * and, orthogonally, the distance to epicenter. If the transitioning View is visible in
+ * the start of the transition, then it will transition sooner when closer to the side and
+ * later when farther. If the view is not visible in the start of the transition, then
+ * it will transition later when closer to the side and sooner when farther from the edge.
+ * This is the default TransitionPropagation used with {@link android.transition.Slide}.
+ */
+public class SidePropagation extends VisibilityPropagation {
+    private static final String TAG = "SlidePropagation";
+
+    /**
+     * Transition propagates relative to the distance of the left side of the scene.
+     */
+    public static final int LEFT = Slide.LEFT;
+
+    /**
+     * Transition propagates relative to the distance of the top of the scene.
+     */
+    public static final int TOP = Slide.TOP;
+
+    /**
+     * Transition propagates relative to the distance of the right side of the scene.
+     */
+    public static final int RIGHT = Slide.RIGHT;
+
+    /**
+     * Transition propagates relative to the distance of the bottom of the scene.
+     */
+    public static final int BOTTOM = Slide.BOTTOM;
+
+    private float mPropagationSpeed = 4.0f;
+    private int mSide = BOTTOM;
+
+    /**
+     * Sets the side that is used to calculate the transition propagation. If the transitioning
+     * View is visible in the start of the transition, then it will transition sooner when
+     * closer to the side and later when farther. If the view is not visible in the start of
+     * the transition, then it will transition later when closer to the side and sooner when
+     * farther from the edge. The default is {@link #BOTTOM}.
+     *
+     * @param side The side that is used to calculate the transition propagation. Must be one of
+     *             {@link #LEFT}, {@link #TOP}, {@link #RIGHT}, or {@link #BOTTOM}.
+     */
+    public void setSide(int side) {
+        mSide = side;
+    }
+
+    /**
+     * Sets the speed at which transition propagation happens, relative to the duration of the
+     * Transition. A <code>propagationSpeed</code> of 1 means that a View centered at the side
+     * set in {@link #setSide(int)} and View centered at the opposite edge will have a difference
+     * in start delay of approximately the duration of the Transition. A speed of 2 means the
+     * start delay difference will be approximately half of the duration of the transition. A
+     * value of 0 is illegal, but negative values will invert the propagation.
+     *
+     * @param propagationSpeed The speed at which propagation occurs, relative to the duration
+     *                         of the transition. A speed of 4 means it works 4 times as fast
+     *                         as the duration of the transition. May not be 0.
+     */
+    public void setPropagationSpeed(float propagationSpeed) {
+        if (propagationSpeed == 0) {
+            throw new IllegalArgumentException("propagationSpeed may not be 0");
+        }
+        mPropagationSpeed = propagationSpeed;
+    }
+
+    @Override
+    public long getStartDelay(ViewGroup sceneRoot, Transition transition,
+            TransitionValues startValues, TransitionValues endValues) {
+        if (startValues == null && endValues == null) {
+            return 0;
+        }
+        int directionMultiplier = 1;
+        Rect epicenter = transition.getEpicenter();
+        TransitionValues positionValues;
+        if (endValues == null || getViewVisibility(startValues) == View.VISIBLE) {
+            positionValues = startValues;
+            directionMultiplier = -1;
+        } else {
+            positionValues = endValues;
+        }
+
+        int viewCenterX = getViewX(positionValues);
+        int viewCenterY = getViewY(positionValues);
+
+        int[] loc = new int[2];
+        sceneRoot.getLocationOnScreen(loc);
+        int left = loc[0] + Math.round(sceneRoot.getTranslationX());
+        int top = loc[1] + Math.round(sceneRoot.getTranslationY());
+        int right = left + sceneRoot.getWidth();
+        int bottom = top + sceneRoot.getHeight();
+
+        int epicenterX;
+        int epicenterY;
+        if (epicenter != null) {
+            epicenterX = epicenter.centerX();
+            epicenterY = epicenter.centerY();
+        } else {
+            epicenterX = (left + right) / 2;
+            epicenterY = (top + bottom) / 2;
+        }
+
+        float distance = distance(viewCenterX, viewCenterY, epicenterX, epicenterY,
+                left, top, right, bottom);
+        float maxDistance = getMaxDistance(sceneRoot);
+        float distanceFraction = distance/maxDistance;
+
+        return Math.round(transition.getDuration() * directionMultiplier / mPropagationSpeed
+                * distanceFraction);
+    }
+
+    private int distance(int viewX, int viewY, int epicenterX, int epicenterY,
+            int left, int top, int right, int bottom) {
+        int distance = 0;
+        switch (mSide) {
+            case LEFT:
+                distance = right - viewX + Math.abs(epicenterY - viewY);
+                break;
+            case TOP:
+                distance = bottom - viewY + Math.abs(epicenterX - viewX);
+                break;
+            case RIGHT:
+                distance = viewX - left + Math.abs(epicenterY - viewY);
+                break;
+            case BOTTOM:
+                distance = viewY - top + Math.abs(epicenterX - viewX);
+                break;
+        }
+        return distance;
+    }
+
+    private int getMaxDistance(ViewGroup sceneRoot) {
+        switch (mSide) {
+            case LEFT:
+            case RIGHT:
+                return sceneRoot.getWidth();
+            default:
+                return sceneRoot.getHeight();
+        }
+    }
+}
diff --git a/core/java/android/transition/Slide.java b/core/java/android/transition/Slide.java
index b38973c..0ff8ddd 100644
--- a/core/java/android/transition/Slide.java
+++ b/core/java/android/transition/Slide.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 The Android Open Source Project
+ * 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.
@@ -13,53 +13,240 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
 package android.transition;
 
 import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
 import android.animation.TimeInterpolator;
+import android.animation.ValueAnimator;
+import android.graphics.Rect;
+import android.util.Log;
+import android.util.Property;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.animation.AccelerateInterpolator;
 import android.view.animation.DecelerateInterpolator;
 
 /**
- * This transition captures the visibility of target objects before and
- * after a scene change and animates any changes by sliding the target
- * objects into or out of place.
- *
- * @hide
+ * This transition tracks changes to the visibility of target views in the
+ * start and end scenes and moves views in or out from one of the edges of the
+ * scene. Visibility is determined by both the
+ * {@link View#setVisibility(int)} state of the view as well as whether it
+ * is parented in the current view hierarchy. Disappearing Views are
+ * limited as described in {@link Visibility#onDisappear(android.view.ViewGroup,
+ * TransitionValues, int, TransitionValues, int)}.
  */
 public class Slide extends Visibility {
+    private static final String TAG = "Slide";
 
-    // TODO: Add parameter for sliding factor - it's hard-coded below
+    /**
+     * Move Views in or out of the left edge of the scene.
+     * @see #setSlideEdge(int)
+     */
+    public static final int LEFT = 0;
 
-    private static final TimeInterpolator sAccelerator = new AccelerateInterpolator();
-    private static final TimeInterpolator sDecelerator = new DecelerateInterpolator();
+    /**
+     * Move Views in or out of the top edge of the scene.
+     * @see #setSlideEdge(int)
+     */
+    public static final int TOP = 1;
 
-    @Override
-    public Animator onAppear(ViewGroup sceneRoot,
-            TransitionValues startValues, int startVisibility,
-            TransitionValues endValues, int endVisibility) {
-        View endView = (endValues != null) ? endValues.view : null;
-        endView.setTranslationY(-2 * endView.getHeight());
-        ObjectAnimator anim = ObjectAnimator.ofFloat(endView, View.TRANSLATION_Y,
-                -2 * endView.getHeight(), 0);
-        anim.setInterpolator(sDecelerator);
+    /**
+     * Move Views in or out of the right edge of the scene.
+     * @see #setSlideEdge(int)
+     */
+    public static final int RIGHT = 2;
+
+    /**
+     * Move Views in or out of the bottom edge of the scene. This is the
+     * default slide direction.
+     * @see #setSlideEdge(int)
+     */
+    public static final int BOTTOM = 3;
+
+    private static final TimeInterpolator sDecelerate = new DecelerateInterpolator();
+    private static final TimeInterpolator sAccelerate = new AccelerateInterpolator();
+
+    private int[] mTempLoc = new int[2];
+    private CalculateSlide mSlideCalculator = sCalculateBottom;
+
+    private interface CalculateSlide {
+        /** Returns the translation value for view when it out of the scene */
+        float getGone(ViewGroup sceneRoot, View view);
+
+        /** Returns the translation value for view when it is in the scene */
+        float getHere(View view);
+
+        /** Returns the property to animate translation */
+        Property<View, Float> getProperty();
+    }
+
+    private static abstract class CalculateSlideHorizontal implements CalculateSlide {
+        @Override
+        public float getHere(View view) {
+            return view.getTranslationX();
+        }
+
+        @Override
+        public Property<View, Float> getProperty() {
+            return View.TRANSLATION_X;
+        }
+    }
+
+    private static abstract class CalculateSlideVertical implements CalculateSlide {
+        @Override
+        public float getHere(View view) {
+            return view.getTranslationY();
+        }
+
+        @Override
+        public Property<View, Float> getProperty() {
+            return View.TRANSLATION_Y;
+        }
+    }
+
+    private static final CalculateSlide sCalculateLeft = new CalculateSlideHorizontal() {
+        @Override
+        public float getGone(ViewGroup sceneRoot, View view) {
+            return view.getTranslationX() - sceneRoot.getWidth();
+        }
+    };
+
+    private static final CalculateSlide sCalculateTop = new CalculateSlideVertical() {
+        @Override
+        public float getGone(ViewGroup sceneRoot, View view) {
+            return view.getTranslationY() - sceneRoot.getHeight();
+        }
+    };
+
+    private static final CalculateSlide sCalculateRight = new CalculateSlideHorizontal() {
+        @Override
+        public float getGone(ViewGroup sceneRoot, View view) {
+            return view.getTranslationX() + sceneRoot.getWidth();
+        }
+    };
+
+    private static final CalculateSlide sCalculateBottom = new CalculateSlideVertical() {
+        @Override
+        public float getGone(ViewGroup sceneRoot, View view) {
+            return view.getTranslationY() + sceneRoot.getHeight();
+        }
+    };
+
+    /**
+     * Constructor using the default {@link android.transition.Slide#BOTTOM}
+     * slide edge direction.
+     */
+    public Slide() {
+        setSlideEdge(BOTTOM);
+    }
+
+    /**
+     * Constructor using the provided slide edge direction.
+     */
+    public Slide(int slideEdge) {
+        setSlideEdge(slideEdge);
+    }
+
+    /**
+     * Change the edge that Views appear and disappear from.
+     * @param slideEdge The edge of the scene to use for Views appearing and disappearing.
+     */
+    public void setSlideEdge(int slideEdge) {
+        switch (slideEdge) {
+            case LEFT:
+                mSlideCalculator = sCalculateLeft;
+                break;
+            case TOP:
+                mSlideCalculator = sCalculateTop;
+                break;
+            case RIGHT:
+                mSlideCalculator = sCalculateRight;
+                break;
+            case BOTTOM:
+                mSlideCalculator = sCalculateBottom;
+                break;
+            default:
+                throw new IllegalArgumentException("Invalid slide direction");
+        }
+        SidePropagation propagation = new SidePropagation();
+        propagation.setSide(slideEdge);
+        setPropagation(propagation);
+    }
+
+    private Animator createAnimation(final View view, Property<View, Float> property,
+            float start, float end, float terminalValue, TimeInterpolator interpolator) {
+        view.setTranslationY(start);
+        if (start == end) {
+            return null;
+        }
+        final ObjectAnimator anim = ObjectAnimator.ofFloat(view, property, start, end);
+
+        SlideAnimatorListener listener = new SlideAnimatorListener(view, terminalValue, end);
+        anim.addListener(listener);
+        anim.addPauseListener(listener);
+        anim.setInterpolator(interpolator);
         return anim;
     }
 
     @Override
-    public Animator onDisappear(ViewGroup sceneRoot,
-            TransitionValues startValues, int startVisibility,
-            TransitionValues endValues, int endVisibility) {
-        View startView = (startValues != null) ? startValues.view : null;
-        startView.setTranslationY(0);
-        ObjectAnimator anim = ObjectAnimator.ofFloat(startView, View.TRANSLATION_Y, 0,
-                -2 * startView.getHeight());
-        anim.setInterpolator(sAccelerator);
-        return anim;
+    public Animator onAppear(ViewGroup sceneRoot, View view,
+            TransitionValues startValues, TransitionValues endValues) {
+        if (endValues == null) {
+            return null;
+        }
+        float end = mSlideCalculator.getHere(view);
+        float start = mSlideCalculator.getGone(sceneRoot, view);
+        return createAnimation(view, mSlideCalculator.getProperty(), start, end, end, sDecelerate);
     }
 
+    @Override
+    public Animator onDisappear(ViewGroup sceneRoot, View view,
+            TransitionValues startValues, TransitionValues endValues) {
+        float start = mSlideCalculator.getHere(view);
+        float end = mSlideCalculator.getGone(sceneRoot, view);
+
+        return createAnimation(view, mSlideCalculator.getProperty(), start, end, start,
+                sAccelerate);
+    }
+
+    private static class SlideAnimatorListener extends AnimatorListenerAdapter {
+        private boolean mCanceled = false;
+        private float mPausedY;
+        private final View mView;
+        private final float mEndY;
+        private final float mTerminalY;
+
+        public SlideAnimatorListener(View view, float terminalY, float endY) {
+            mView = view;
+            mTerminalY = terminalY;
+            mEndY = endY;
+        }
+
+        @Override
+        public void onAnimationCancel(Animator animator) {
+            mView.setTranslationY(mTerminalY);
+            mCanceled = true;
+        }
+
+        @Override
+        public void onAnimationEnd(Animator animator) {
+            if (!mCanceled) {
+                mView.setTranslationY(mTerminalY);
+            }
+        }
+
+        @Override
+        public void onAnimationPause(Animator animator) {
+            mPausedY = mView.getTranslationY();
+            mView.setTranslationY(mEndY);
+        }
+
+        @Override
+        public void onAnimationResume(Animator animator) {
+            mView.setTranslationY(mPausedY);
+        }
+    }
 }
diff --git a/core/java/android/transition/Transition.java b/core/java/android/transition/Transition.java
index c88b4c0..b7ae31e 100644
--- a/core/java/android/transition/Transition.java
+++ b/core/java/android/transition/Transition.java
@@ -19,10 +19,12 @@
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
 import android.animation.TimeInterpolator;
+import android.graphics.Rect;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.LongSparseArray;
 import android.util.SparseArray;
+import android.util.SparseLongArray;
 import android.view.SurfaceView;
 import android.view.TextureView;
 import android.view.View;
@@ -60,10 +62,18 @@
  * <p>Transitions can be declared in XML resource files inside the <code>res/transition</code>
  * directory. Transition resources consist of a tag name for one of the Transition
  * subclasses along with attributes to define some of the attributes of that transition.
- * For example, here is a minimal resource file that declares a {@link ChangeBounds} transition:</p>
+ * For example, here is a minimal resource file that declares a {@link ChangeBounds} transition:
  *
  * {@sample development/samples/ApiDemos/res/transition/changebounds.xml ChangeBounds}
  *
+ * <p>{@link android.transition.Explode} transition:</p>
+ *
+ * {@sample development/samples/ApiDemos/res/transition/explode.xml Explode}
+ *
+ * <p>{@link android.transition.MoveImage} transition:</p>
+ *
+ * {@sample development/samples/ApiDemos/res/transition/move_image.xml MoveImage}
+ *
  * <p>Note that attributes for the transition are not required, just as they are
  * optional when declared in code; Transitions created from XML resources will use
  * the same defaults as their code-created equivalents. Here is a slightly more
@@ -87,7 +97,8 @@
  *
  * Further information on XML resource descriptions for transitions can be found for
  * {@link android.R.styleable#Transition}, {@link android.R.styleable#TransitionSet},
- * {@link android.R.styleable#TransitionTarget}, and {@link android.R.styleable#Fade}.
+ * {@link android.R.styleable#TransitionTarget}, {@link android.R.styleable#Fade}, and
+ * {@link android.R.styleable#Slide}.
  *
  */
 public abstract class Transition implements Cloneable {
@@ -149,6 +160,13 @@
     // to be run in runAnimators()
     ArrayList<Animator> mAnimators = new ArrayList<Animator>();
 
+    // The function for calculating the Animation start delay.
+    TransitionPropagation mPropagation;
+
+    // The rectangular region for Transitions like Explode and TransitionPropagations
+    // like CircularPropagation
+    EpicenterCallback mEpicenterCallback;
+
     /**
      * Constructs a Transition object with no target objects. A transition with
      * no targets defaults to running on all target objects in the scene hierarchy
@@ -435,6 +453,9 @@
             endValuesList.add(end);
         }
         ArrayMap<Animator, AnimationInfo> runningAnimators = getRunningAnimators();
+        long minStartDelay = Long.MAX_VALUE;
+        int minAnimator = mAnimators.size();
+        SparseLongArray startDelays = new SparseLongArray();
         for (int i = 0; i < startValuesList.size(); ++i) {
             TransitionValues start = startValuesList.get(i);
             TransitionValues end = endValuesList.get(i);
@@ -497,6 +518,12 @@
                             view = (start != null) ? start.view : null;
                         }
                         if (animator != null) {
+                            if (mPropagation != null) {
+                                long delay = mPropagation
+                                        .getStartDelay(sceneRoot, this, start, end);
+                                startDelays.put(mAnimators.size(), delay);
+                                minStartDelay = Math.min(delay, minStartDelay);
+                            }
                             AnimationInfo info = new AnimationInfo(view, getName(),
                                     sceneRoot.getWindowId(), infoValues);
                             runningAnimators.put(animator, info);
@@ -506,6 +533,14 @@
                 }
             }
         }
+        if (minStartDelay != 0) {
+            for (int i = 0; i < startDelays.size(); i++) {
+                int index = startDelays.keyAt(i);
+                Animator animator = mAnimators.get(index);
+                long delay = startDelays.valueAt(i) - minStartDelay + animator.getStartDelay();
+                animator.setStartDelay(delay);
+            }
+        }
     }
 
     /**
@@ -565,7 +600,7 @@
 
     /**
      * This is called internally once all animations have been set up by the
-     * transition hierarchy. \
+     * transition hierarchy.
      *
      * @hide
      */
@@ -1010,6 +1045,7 @@
                         } else {
                             captureEndValues(values);
                         }
+                        capturePropagationValues(values);
                         if (start) {
                             mStartValues.viewValues.put(view, values);
                             if (id >= 0) {
@@ -1035,6 +1071,7 @@
                         } else {
                             captureEndValues(values);
                         }
+                        capturePropagationValues(values);
                         if (start) {
                             mStartValues.viewValues.put(view, values);
                         } else {
@@ -1122,6 +1159,7 @@
             } else {
                 captureEndValues(values);
             }
+            capturePropagationValues(values);
             if (start) {
                 if (!isListViewItem) {
                     mStartValues.viewValues.put(view, values);
@@ -1340,7 +1378,7 @@
                 animator.setDuration(getDuration());
             }
             if (getStartDelay() >= 0) {
-                animator.setStartDelay(getStartDelay());
+                animator.setStartDelay(getStartDelay() + animator.getStartDelay());
             }
             if (getInterpolator() != null) {
                 animator.setInterpolator(getInterpolator());
@@ -1473,6 +1511,98 @@
         return this;
     }
 
+    /**
+     * Sets the callback to use to find the epicenter of a Transition. A null value indicates
+     * that there is no epicenter in the Transition and getEpicenter() will return null.
+     * Transitions like {@link android.transition.Explode} use a point or Rect to orient
+     * the direction of travel. This is called the epicenter of the Transition and is
+     * typically centered on a touched View. The
+     * {@link android.transition.Transition.EpicenterCallback} allows a Transition to
+     * dynamically retrieve the epicenter during a Transition.
+     * @param epicenterCallback The callback to use to find the epicenter of the Transition.
+     */
+    public void setEpicenterCallback(EpicenterCallback epicenterCallback) {
+        mEpicenterCallback = epicenterCallback;
+    }
+
+    /**
+     * Returns the callback used to find the epicenter of the Transition.
+     * Transitions like {@link android.transition.Explode} use a point or Rect to orient
+     * the direction of travel. This is called the epicenter of the Transition and is
+     * typically centered on a touched View. The
+     * {@link android.transition.Transition.EpicenterCallback} allows a Transition to
+     * dynamically retrieve the epicenter during a Transition.
+     * @return the callback used to find the epicenter of the Transition.
+     */
+    public EpicenterCallback getEpicenterCallback() {
+        return mEpicenterCallback;
+    }
+
+    /**
+     * Returns the epicenter as specified by the
+     * {@link android.transition.Transition.EpicenterCallback} or null if no callback exists.
+     * @return the epicenter as specified by the
+     * {@link android.transition.Transition.EpicenterCallback} or null if no callback exists.
+     * @see #setEpicenterCallback(android.transition.Transition.EpicenterCallback)
+     */
+    public Rect getEpicenter() {
+        if (mEpicenterCallback == null) {
+            return null;
+        }
+        return mEpicenterCallback.getEpicenter(this);
+    }
+
+    /**
+     * Sets the method for determining Animator start delays.
+     * When a Transition affects several Views like {@link android.transition.Explode} or
+     * {@link android.transition.Slide}, there may be a desire to have a "wave-front" effect
+     * such that the Animator start delay depends on position of the View. The
+     * TransitionPropagation specifies how the start delays are calculated.
+     * @param transitionPropagation The class used to determine the start delay of
+     *                              Animators created by this Transition. A null value
+     *                              indicates that no delay should be used.
+     */
+    public void setPropagation(TransitionPropagation transitionPropagation) {
+        mPropagation = transitionPropagation;
+    }
+
+    /**
+     * Returns the {@link android.transition.TransitionPropagation} used to calculate Animator start
+     * delays.
+     * When a Transition affects several Views like {@link android.transition.Explode} or
+     * {@link android.transition.Slide}, there may be a desire to have a "wave-front" effect
+     * such that the Animator start delay depends on position of the View. The
+     * TransitionPropagation specifies how the start delays are calculated.
+     * @return the {@link android.transition.TransitionPropagation} used to calculate Animator start
+     * delays. This is null by default.
+     */
+    public TransitionPropagation getPropagation() {
+        return mPropagation;
+    }
+
+    /**
+     * Captures TransitionPropagation values for the given view and the
+     * hierarchy underneath it.
+     */
+    void capturePropagationValues(TransitionValues transitionValues) {
+        if (mPropagation != null) {
+            String[] propertyNames = mPropagation.getPropagationProperties();
+            if (propertyNames == null) {
+                return;
+            }
+            boolean containsAll = true;
+            for (int i = 0; i < propertyNames.length; i++) {
+                if (!transitionValues.values.containsKey(propertyNames[i])) {
+                    containsAll = false;
+                    break;
+                }
+            }
+            if (!containsAll) {
+                mPropagation.captureValues(transitionValues);
+            }
+        }
+    }
+
     Transition setSceneRoot(ViewGroup sceneRoot) {
         mSceneRoot = sceneRoot;
         return this;
@@ -1710,4 +1840,28 @@
         }
     }
 
+    /**
+     * Class to get the epicenter of Transition. Use
+     * {@link #setEpicenterCallback(android.transition.Transition.EpicenterCallback)} to
+     * set the callback used to calculate the epicenter of the Transition. Override
+     * {@link #getEpicenter()} to return the rectangular region in screen coordinates of
+     * the epicenter of the transition.
+     * @see #setEpicenterCallback(android.transition.Transition.EpicenterCallback)
+     */
+    public static abstract class EpicenterCallback {
+
+        /**
+         * Implementers must override to return the epicenter of the Transition in screen
+         * coordinates. Transitions like {@link android.transition.Explode} depend upon
+         * an epicenter for the Transition. In Explode, Views move toward or away from the
+         * center of the epicenter Rect along the vector between the epicenter and the center
+         * of the View appearing and disappearing. Some Transitions, such as
+         * {@link android.transition.Fade} pay no attention to the epicenter.
+         *
+         * @param transition The transition for which the epicenter applies.
+         * @return The Rect region of the epicenter of <code>transition</code> or null if
+         * there is no epicenter.
+         */
+        public abstract Rect getEpicenter(Transition transition);
+    }
 }
diff --git a/core/java/android/transition/TransitionInflater.java b/core/java/android/transition/TransitionInflater.java
index 912f2ed..f675c6a 100644
--- a/core/java/android/transition/TransitionInflater.java
+++ b/core/java/android/transition/TransitionInflater.java
@@ -20,7 +20,6 @@
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.content.res.XmlResourceParser;
-import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.Xml;
 import android.view.InflateException;
@@ -146,7 +145,13 @@
                 transition = new ChangeBounds();
                 newTransition = true;
             } else if ("slide".equals(name)) {
-                transition = new Slide();
+                transition = createSlideTransition(attrs);
+                newTransition = true;
+            } else if ("explode".equals(name)) {
+                transition = new Explode();
+                newTransition = true;
+            } else if ("moveImage".equals(name)) {
+                transition = new MoveImage();
                 newTransition = true;
             } else if ("autoTransition".equals(name)) {
                 transition = new AutoTransition();
@@ -189,6 +194,15 @@
         return transition;
     }
 
+    private Slide createSlideTransition(AttributeSet attrs) {
+        TypedArray a = mContext.obtainStyledAttributes(attrs,
+                com.android.internal.R.styleable.Slide);
+        int edge = a.getInt(com.android.internal.R.styleable.Slide_slideEdge, Slide.BOTTOM);
+        Slide slide = new Slide(edge);
+        a.recycle();
+        return slide;
+    }
+
     private void getTargetIds(XmlPullParser parser,
             AttributeSet attrs, Transition transition) throws XmlPullParserException, IOException {
 
diff --git a/core/java/android/transition/TransitionPropagation.java b/core/java/android/transition/TransitionPropagation.java
new file mode 100644
index 0000000..9a481c2
--- /dev/null
+++ b/core/java/android/transition/TransitionPropagation.java
@@ -0,0 +1,88 @@
+/*
+ * 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.transition;
+
+import android.graphics.Rect;
+import android.view.ViewGroup;
+
+/**
+ * Extend <code>TransitionPropagation</code> to customize start delays for Animators created
+ * in {@link android.transition.Transition#createAnimator(ViewGroup,
+ * TransitionValues, TransitionValues)}. A Transition such as {@link android.transition.Explode}
+ * defaults to using {@link android.transition.CircularPropagation} and Views closer to the
+ * epicenter will move out of the scene later and into the scene sooner than Views farther
+ * from the epicenter, giving the appearance of inertia. With no TransitionPropagation, all
+ * Views will react simultaneously to the start of the transition.
+ *
+ * @see Transition#setPropagation(TransitionPropagation)
+ * @see Transition#getEpicenter()
+ */
+public abstract class TransitionPropagation {
+    /**
+     * Called by Transition to alter the Animator start delay. All start delays will be adjusted
+     * such that the minimum becomes zero.
+     * @param sceneRoot The root of the View hierarchy running the transition.
+     * @param transition The transition that created the Animator
+     * @param startValues The values for a specific target in the start scene.
+     * @param endValues The values for the target in the end scene.
+     * @return A start delay to use with the Animator created by <code>transition</code>. The
+     * delay will be offset by the minimum delay of all <code>TransitionPropagation</code>s
+     * used in the Transition so that the smallest delay will be 0. Returned values may be
+     * negative.
+     */
+    public abstract long getStartDelay(ViewGroup sceneRoot, Transition transition,
+            TransitionValues startValues, TransitionValues endValues);
+
+    /**
+     * Captures the values in the start or end scene for the properties that this
+     * transition propagation monitors. These values are then passed as the startValues
+     * or endValues structure in a later call to
+     * {@link #getStartDelay(ViewGroup, Transition, TransitionValues, TransitionValues)}.
+     * The main concern for an implementation is what the
+     * properties are that the transition cares about and what the values are
+     * for all of those properties. The start and end values will be compared
+     * later during the
+     * {@link #getStartDelay(ViewGroup, Transition, TransitionValues, TransitionValues)}.
+     * method to determine the start delay.
+     *
+     * <p>Subclasses must implement this method. The method should only be called by the
+     * transition system; it is not intended to be called from external classes.</p>
+     *
+     * @param transitionValues The holder for any values that the Transition
+     * wishes to store. Values are stored in the <code>values</code> field
+     * of this TransitionValues object and are keyed from
+     * a String value. For example, to store a view's rotation value,
+     * a transition might call
+     * <code>transitionValues.values.put("appname:transitionname:rotation",
+     * view.getRotation())</code>. The target view will already be stored in
+     * the transitionValues structure when this method is called.
+     */
+    public abstract void captureValues(TransitionValues transitionValues);
+
+    /**
+     * Returns the set of property names stored in the {@link TransitionValues}
+     * object passed into {@link #captureValues(TransitionValues)} that
+     * this transition propagation cares about for the purposes of preventing
+     * duplicate capturing of property values.
+
+     * <p>A <code>TransitionPropagation</code> must override this method to prevent
+     * duplicate capturing of values and must contain at least one </p>
+     *
+     * @return An array of property names as described in the class documentation for
+     * {@link TransitionValues}.
+     */
+    public abstract String[] getPropagationProperties() ;
+}
diff --git a/core/java/android/transition/TransitionSet.java b/core/java/android/transition/TransitionSet.java
index 19d6b3d..966b24d 100644
--- a/core/java/android/transition/TransitionSet.java
+++ b/core/java/android/transition/TransitionSet.java
@@ -17,6 +17,7 @@
 package android.transition;
 
 import android.animation.TimeInterpolator;
+import android.graphics.Rect;
 import android.util.AndroidRuntimeException;
 import android.view.View;
 import android.view.ViewGroup;
@@ -315,6 +316,15 @@
         }
     }
 
+    @Override
+    void capturePropagationValues(TransitionValues transitionValues) {
+        super.capturePropagationValues(transitionValues);
+        int numTransitions = mTransitions.size();
+        for (int i = 0; i < numTransitions; ++i) {
+            mTransitions.get(i).capturePropagationValues(transitionValues);
+        }
+    }
+
     /** @hide */
     @Override
     public void pause(View sceneRoot) {
@@ -365,6 +375,24 @@
     }
 
     @Override
+    public void setPropagation(TransitionPropagation propagation) {
+        super.setPropagation(propagation);
+        int numTransitions = mTransitions.size();
+        for (int i = 0; i < numTransitions; ++i) {
+            mTransitions.get(i).setPropagation(propagation);
+        }
+    }
+
+    @Override
+    public void setEpicenterCallback(EpicenterCallback epicenterCallback) {
+        super.setEpicenterCallback(epicenterCallback);
+        int numTransitions = mTransitions.size();
+        for (int i = 0; i < numTransitions; ++i) {
+            mTransitions.get(i).setEpicenterCallback(epicenterCallback);
+        }
+    }
+
+    @Override
     String toString(String indent) {
         String result = super.toString(indent);
         for (int i = 0; i < mTransitions.size(); ++i) {
@@ -383,5 +411,4 @@
         }
         return clone;
     }
-
 }
diff --git a/core/java/android/transition/Visibility.java b/core/java/android/transition/Visibility.java
index 44f92cd..7783b6f 100644
--- a/core/java/android/transition/Visibility.java
+++ b/core/java/android/transition/Visibility.java
@@ -17,6 +17,7 @@
 package android.transition;
 
 import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
 import android.view.View;
 import android.view.ViewGroup;
 
@@ -29,15 +30,20 @@
  * information to determine the specific animations to run when visibility
  * changes occur. Subclasses should implement one or both of the methods
  * {@link #onAppear(ViewGroup, TransitionValues, int, TransitionValues, int)},
- * {@link #onDisappear(ViewGroup, TransitionValues, int, TransitionValues, int)},
+ * {@link #onDisappear(ViewGroup, TransitionValues, int, TransitionValues, int)} or
+ * {@link #onAppear(ViewGroup, View, TransitionValues, TransitionValues)},
+ * {@link #onDisappear(ViewGroup, View, TransitionValues, TransitionValues)}.
  */
 public abstract class Visibility extends Transition {
 
     private static final String PROPNAME_VISIBILITY = "android:visibility:visibility";
     private static final String PROPNAME_PARENT = "android:visibility:parent";
+    private static final String PROPNAME_SCREEN_LOCATION = "android:visibility:screenLocation";
+
     private static final String[] sTransitionProperties = {
             PROPNAME_VISIBILITY,
             PROPNAME_PARENT,
+            PROPNAME_SCREEN_LOCATION,
     };
 
     private static class VisibilityInfo {
@@ -58,6 +64,9 @@
         int visibility = transitionValues.view.getVisibility();
         transitionValues.values.put(PROPNAME_VISIBILITY, visibility);
         transitionValues.values.put(PROPNAME_PARENT, transitionValues.view.getParent());
+        int[] loc = new int[2];
+        transitionValues.view.getLocationOnScreen(loc);
+        transitionValues.values.put(PROPNAME_SCREEN_LOCATION, loc);
     }
 
     @Override
@@ -179,8 +188,11 @@
     }
 
     /**
-     * The default implementation of this method does nothing. Subclasses
-     * should override if they need to create an Animator when targets appear.
+     * The default implementation of this method calls
+     * {@link #onAppear(ViewGroup, View, TransitionValues, TransitionValues)}.
+     * Subclasses should override this method or
+     * {@link #onAppear(ViewGroup, View, TransitionValues, TransitionValues)}.
+     * if they need to create an Animator when targets appear.
      * The method should only be called by the Visibility class; it is
      * not intended to be called from external classes.
      *
@@ -196,15 +208,53 @@
     public Animator onAppear(ViewGroup sceneRoot,
             TransitionValues startValues, int startVisibility,
             TransitionValues endValues, int endVisibility) {
+        return onAppear(sceneRoot, endValues.view, startValues, endValues);
+    }
+
+    /**
+     * The default implementation of this method returns a null Animator. Subclasses should
+     * override this method to make targets appear with the desired transition. The
+     * method should only be called from
+     * {@link #onAppear(ViewGroup, TransitionValues, int, TransitionValues, int)}.
+     *
+     * @param sceneRoot The root of the transition hierarchy
+     * @param view The View to make appear. This will be in the target scene's View hierarchy and
+     *             will be VISIBLE.
+     * @param startValues The target values in the start scene
+     * @param endValues The target values in the end scene
+     * @return An Animator to be started at the appropriate time in the
+     * overall transition for this scene change. A null value means no animation
+     * should be run.
+     */
+    public Animator onAppear(ViewGroup sceneRoot, View view, TransitionValues startValues,
+            TransitionValues endValues) {
         return null;
     }
 
     /**
-     * The default implementation of this method does nothing. Subclasses
-     * should override if they need to create an Animator when targets disappear.
+     * Subclasses should override this method or
+     * {@link #onDisappear(ViewGroup, View, TransitionValues, TransitionValues)}
+     * if they need to create an Animator when targets disappear.
      * The method should only be called by the Visibility class; it is
      * not intended to be called from external classes.
-     *
+     * <p>
+     * The default implementation of this method attempts to find a View to use to call
+     * {@link #onDisappear(ViewGroup, View, TransitionValues, TransitionValues)},
+     * based on the situation of the View in the View hierarchy. For example,
+     * if a View was simply removed from its parent, then the View will be added
+     * into a {@link android.view.ViewGroupOverlay} and passed as the <code>view</code>
+     * parameter in {@link #onDisappear(ViewGroup, View, TransitionValues, TransitionValues)}.
+     * If a visible View is changed to be {@link View#GONE} or {@link View#INVISIBLE},
+     * then it can be used as the <code>view</code> and the visibility will be changed
+     * to {@link View#VISIBLE} for the duration of the animation. However, if a View
+     * is in a hierarchy which is also altering its visibility, the situation can be
+     * more complicated. In general, if a view that is no longer in the hierarchy in
+     * the end scene still has a parent (so its parent hierarchy was removed, but it
+     * was not removed from its parent), then it will be left alone to avoid side-effects from
+     * improperly removing it from its parent. The only exception to this is if
+     * the previous {@link Scene} was {@link Scene#getSceneForLayout(ViewGroup, int,
+     * android.content.Context) created from a layout resource file}, then it is considered
+     * safe to un-parent the starting scene view in order to make it disappear.</p>
      *
      * @param sceneRoot The root of the transition hierarchy
      * @param startValues The target values in the start scene
@@ -218,6 +268,144 @@
     public Animator onDisappear(ViewGroup sceneRoot,
             TransitionValues startValues, int startVisibility,
             TransitionValues endValues, int endVisibility) {
+        View startView = (startValues != null) ? startValues.view : null;
+        View endView = (endValues != null) ? endValues.view : null;
+        View overlayView = null;
+        View viewToKeep = null;
+        if (endView == null || endView.getParent() == null) {
+            if (endView != null) {
+                // endView was removed from its parent - add it to the overlay
+                overlayView = endView;
+            } else if (startView != null) {
+                // endView does not exist. Use startView only under certain
+                // conditions, because placing a view in an overlay necessitates
+                // it being removed from its current parent
+                if (startView.getParent() == null) {
+                    // no parent - safe to use
+                    overlayView = startView;
+                } else if (startView.getParent() instanceof View &&
+                        startView.getParent().getParent() == null) {
+                    View startParent = (View) startView.getParent();
+                    int id = startParent.getId();
+                    if (id != View.NO_ID && sceneRoot.findViewById(id) != null && mCanRemoveViews) {
+                        // no parent, but its parent is unparented  but the parent
+                        // hierarchy has been replaced by a new hierarchy with the same id
+                        // and it is safe to un-parent startView
+                        overlayView = startView;
+                    }
+                }
+            }
+        } else {
+            // visibility change
+            if (endVisibility == View.INVISIBLE) {
+                viewToKeep = endView;
+            } else {
+                // Becoming GONE
+                if (startView == endView) {
+                    viewToKeep = endView;
+                } else {
+                    overlayView = startView;
+                }
+            }
+        }
+        final int finalVisibility = endVisibility;
+        final ViewGroup finalSceneRoot = sceneRoot;
+
+        if (overlayView != null) {
+            // TODO: Need to do this for general case of adding to overlay
+            int[] screenLoc = (int[]) startValues.values.get(PROPNAME_SCREEN_LOCATION);
+            int screenX = screenLoc[0];
+            int screenY = screenLoc[1];
+            int[] loc = new int[2];
+            sceneRoot.getLocationOnScreen(loc);
+            overlayView.offsetLeftAndRight((screenX - loc[0]) - overlayView.getLeft());
+            overlayView.offsetTopAndBottom((screenY - loc[1]) - overlayView.getTop());
+            sceneRoot.getOverlay().add(overlayView);
+            Animator animator = onDisappear(sceneRoot, overlayView, startValues, endValues);
+            if (animator == null) {
+                sceneRoot.getOverlay().remove(overlayView);
+            } else {
+                final View finalOverlayView = overlayView;
+                animator.addListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        finalSceneRoot.getOverlay().remove(finalOverlayView);
+                    }
+
+                    @Override
+                    public void onAnimationPause(Animator animation) {
+                        finalSceneRoot.getOverlay().remove(finalOverlayView);
+                    }
+
+                    @Override
+                    public void onAnimationResume(Animator animation) {
+                        finalSceneRoot.getOverlay().add(finalOverlayView);
+                    }
+                });
+            }
+            return animator;
+        }
+
+        if (viewToKeep != null) {
+            viewToKeep.setVisibility(View.VISIBLE);
+            Animator animator = onDisappear(sceneRoot, viewToKeep, startValues, endValues);
+            if (animator == null) {
+                viewToKeep.setVisibility(finalVisibility);
+            } else {
+                final View finalViewToKeep = viewToKeep;
+                animator.addListener(new AnimatorListenerAdapter() {
+                    boolean mCanceled = false;
+
+                    @Override
+                    public void onAnimationPause(Animator animation) {
+                        if (!mCanceled) {
+                            finalViewToKeep.setVisibility(finalVisibility);
+                        }
+                    }
+
+                    @Override
+                    public void onAnimationResume(Animator animation) {
+                        if (!mCanceled) {
+                            finalViewToKeep.setVisibility(View.VISIBLE);
+                        }
+                    }
+
+                    @Override
+                    public void onAnimationCancel(Animator animation) {
+                        mCanceled = true;
+                    }
+
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        if (!mCanceled) {
+                            finalViewToKeep.setVisibility(finalVisibility);
+                        }
+                    }
+                });
+            }
+            return animator;
+        }
+        return null;
+    }
+
+    /**
+     * The default implementation of this method returns a null Animator. Subclasses should
+     * override this method to make targets disappear with the desired transition. The
+     * method should only be called from
+     * {@link #onDisappear(ViewGroup, TransitionValues, int, TransitionValues, int)}.
+     *
+     * @param sceneRoot The root of the transition hierarchy
+     * @param view The View to make disappear. This will be in the target scene's View
+     *             hierarchy or in an {@link android.view.ViewGroupOverlay} and will be
+     *             VISIBLE.
+     * @param startValues The target values in the start scene
+     * @param endValues The target values in the end scene
+     * @return An Animator to be started at the appropriate time in the
+     * overall transition for this scene change. A null value means no animation
+     * should be run.
+     */
+    public Animator onDisappear(ViewGroup sceneRoot, View view, TransitionValues startValues,
+            TransitionValues endValues) {
         return null;
     }
 }
diff --git a/core/java/android/transition/VisibilityPropagation.java b/core/java/android/transition/VisibilityPropagation.java
new file mode 100644
index 0000000..0326d47
--- /dev/null
+++ b/core/java/android/transition/VisibilityPropagation.java
@@ -0,0 +1,112 @@
+/*
+ * 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.transition;
+
+import android.view.View;
+
+/**
+ * Base class for <code>TransitionPropagation</code>s that care about
+ * View Visibility and the center position of the View.
+ */
+public abstract class VisibilityPropagation extends TransitionPropagation {
+
+    /**
+     * The property key used for {@link android.view.View#getVisibility()}.
+     */
+    private static final String PROPNAME_VISIBILITY = "android:visibilityPropagation:visibility";
+
+    /**
+     * The property key used for the center of the View in screen coordinates. This is an
+     * int[2] with the index 0 taking the x coordinate and index 1 taking the y coordinate.
+     */
+    private static final String PROPNAME_VIEW_CENTER = "android:visibilityPropagation:center";
+
+    private static final String[] VISIBILITY_PROPAGATION_VALUES = {
+            PROPNAME_VISIBILITY,
+            PROPNAME_VIEW_CENTER,
+    };
+
+    @Override
+    public void captureValues(TransitionValues values) {
+        View view = values.view;
+        values.values.put(PROPNAME_VISIBILITY, view.getVisibility());
+        int[] loc = new int[2];
+        view.getLocationOnScreen(loc);
+        loc[0] += Math.round(view.getTranslationX());
+        loc[0] += view.getWidth() / 2;
+        loc[1] += Math.round(view.getTranslationY());
+        loc[1] += view.getHeight() / 2;
+        values.values.put(PROPNAME_VIEW_CENTER, loc);
+    }
+
+    @Override
+    public String[] getPropagationProperties() {
+        return VISIBILITY_PROPAGATION_VALUES;
+    }
+
+    /**
+     * Returns {@link android.view.View#getVisibility()} for the View at the time the values
+     * were captured.
+     * @param values The TransitionValues captured at the start or end of the Transition.
+     * @return {@link android.view.View#getVisibility()} for the View at the time the values
+     * were captured.
+     */
+    public int getViewVisibility(TransitionValues values) {
+        if (values == null) {
+            return View.GONE;
+        }
+        Integer visibility = (Integer) values.values.get(PROPNAME_VISIBILITY);
+        if (visibility == null) {
+            return View.GONE;
+        }
+        return visibility;
+    }
+
+    /**
+     * Returns the View's center x coordinate, relative to the screen, at the time the values
+     * were captured.
+     * @param values The TransitionValues captured at the start or end of the Transition.
+     * @return the View's center x coordinate, relative to the screen, at the time the values
+     * were captured.
+     */
+    public int getViewX(TransitionValues values) {
+        return getViewCoordinate(values, 0);
+    }
+
+    /**
+     * Returns the View's center y coordinate, relative to the screen, at the time the values
+     * were captured.
+     * @param values The TransitionValues captured at the start or end of the Transition.
+     * @return the View's center y coordinate, relative to the screen, at the time the values
+     * were captured.
+     */
+    public int getViewY(TransitionValues values) {
+        return getViewCoordinate(values, 1);
+    }
+
+    private static int getViewCoordinate(TransitionValues values, int coordinateIndex) {
+        if (values == null) {
+            return -1;
+        }
+
+        int[] coordinates = (int[]) values.values.get(PROPNAME_VIEW_CENTER);
+        if (coordinates == null) {
+            return -1;
+        }
+
+        return coordinates[coordinateIndex];
+    }
+}
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 0fc198e2..362182e 100644
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4891,6 +4891,24 @@
         </attr>
     </declare-styleable>
 
+    <!-- Use <code>slide</code>as the root tag of the XML resource that
+         describes a {@link android.transition.Slide Slide} transition.
+         The attributes of the {@link android.R.styleable#Transition Transition}
+         resource are available in addition to the specific attributes of Slide
+         described here. -->
+    <declare-styleable name="Slide">
+        <attr name="slideEdge">
+            <!-- Slide to and from the bottom edge of the Scene. -->
+            <enum name="left" value="0" />
+            <!-- Slide to and from the bottom edge of the Scene. -->
+            <enum name="top" value="1" />
+            <!-- Slide to and from the bottom edge of the Scene. -->
+            <enum name="right" value="2" />
+            <!-- Slide to and from the bottom edge of the Scene. -->
+            <enum name="bottom" value="3" />
+        </attr>
+    </declare-styleable>
+
     <!-- Use <code>target</code> as the root tag of the XML resource that
      describes a {@link android.transition.Transition#addTarget(int)
      targetId} of a transition. There can be one or more targets inside
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 76a3bd7..1e7ff60 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -2145,6 +2145,7 @@
   <public type="attr" name="persistable" />
   <public type="attr" name="titleTextAppearance" />
   <public type="attr" name="subtitleTextAppearance" />
+  <public type="attr" name="slideEdge" />
 
   <public-padding type="dimen" name="l_resource_pad" end="0x01050010" />
 
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
index f87d0ba..79ed866 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
@@ -4144,7 +4144,9 @@
         transitioningViews.removeAll(sharedElements.values());
 
         mSceneTransitionListener.convertToTranslucent();
-
+        transition = transition.clone();
+        Rect epicenter = calcEpicenter(sharedElements, mActivityOptions.getSharedElementNames());
+        transition.setEpicenterCallback(new FixedEpicenterCallback(epicenter));
         ExitSceneBack exitScene =
                 new ExitSceneBack(onTransitionEnd, sharedElementArgs, sharedElements.values());
         exitScene.start(transition);
@@ -4178,8 +4180,12 @@
         transitionSet.addTransition(exitTransition);
         transitionSet.addTransition(sharedElementTransition);
 
+        Rect epicenter = calcEpicenter(sharedElements, activityOptions.getSharedElementNames());
+        FixedEpicenterCallback epicenterCallback = new FixedEpicenterCallback(epicenter);
+        transitionSet.setEpicenterCallback(epicenterCallback);
+
         updateExitActivityOptions(activityOptions, sharedElements,
-                sharedElementTransition, transitioningViews, exitTransition);
+                sharedElementTransition, transitioningViews, exitTransition, epicenterCallback);
 
         // Start exiting the Views that need to exit
         TransitionManager.beginDelayedTransition(mDecor, transitionSet);
@@ -4222,7 +4228,8 @@
 
     private void updateExitActivityOptions(ActivityOptions activityOptions,
             final Map<String, View> sharedElements, Transition sharedElementTransition,
-            final ArrayList<View> transitioningViews, Transition exitTransition) {
+            final ArrayList<View> transitioningViews, Transition exitTransition,
+            final Transition.EpicenterCallback epicenterCallback) {
 
         // Schedule capturing of the shared element state
         final Bundle sharedElementArgs = new Bundle();
@@ -4246,6 +4253,8 @@
                     @Override
                     public void run() {
                         Transition transition = mTransitionManager.getExitTransition(mContentScene);
+                        transition = transition.clone();
+                        transition.setEpicenterCallback(epicenterCallback);
                         TransitionManager.beginDelayedTransition(mDecor, transition);
                         for (String name : sharedElements.keySet()) {
                             if (!sharedElementNames.contains(name)) {
@@ -4276,6 +4285,8 @@
                     public void run() {
                         mTransitioningViews = null;
                         Transition transition = mTransitionManager.getExitTransition(mContentScene);
+                        transition = transition.clone();
+                        transition.setEpicenterCallback(epicenterCallback);
                         setSharedElementState(sharedElements, sharedElementState);
                         setViewVisibility(sharedElements.values(), View.VISIBLE);
                         if (mSceneTransitionListener != null) {
@@ -4335,7 +4346,7 @@
     private static Transition addTransitionTargets(Transition transition, Collection<View> views,
             boolean add) {
         TransitionSet set = new TransitionSet();
-        set.addTransition(transition);
+        set.addTransition(transition.clone());
         for (View view: views) {
             if (add) {
                 set.addTarget(view);
@@ -4446,6 +4457,28 @@
         }
     }
 
+    private static Rect calcEpicenter(ArrayMap<String, View> sharedElements,
+            ArrayList<String> sharedElementNames) {
+        if (sharedElementNames != null) {
+            for (String name: sharedElementNames) {
+                if (name.startsWith("android:")) {
+                    return null;
+                }
+                View view = sharedElements.get(name);
+                if (view != null) {
+                    int[] loc = new int[2];
+                    view.getLocationOnScreen(loc);
+                    int left = loc[0] + Math.round(view.getTranslationX());
+                    int top = loc[1] + Math.round(view.getTranslationY());
+                    int right = left + view.getWidth();
+                    int bottom = top + view.getHeight();
+                    return new Rect(left, top, right, bottom);
+                }
+            }
+        }
+        return null;
+    }
+
     private class ExitSceneBack extends Transition.TransitionListenerAdapter implements
             Animator.AnimatorListener {
         private boolean mExitTransitionComplete;
@@ -4546,6 +4579,7 @@
         private boolean mEnterTransitionStarted;
         private ArrayMap<String, View> mSharedElementTargets = new ArrayMap<String, View>();
         private ArrayList<View> mEnteringViews = new ArrayList<View>();
+        private Transition.EpicenterCallback mEpicenterCallback;
 
         public EnterScene() {
             mSceneTransitionListener.nullPendingTransition();
@@ -4566,6 +4600,9 @@
                 mDecor.captureTransitioningViews(mEnteringViews);
                 mapSharedElements(mSharedElementTargets);
                 mEnteringViews.removeAll(mSharedElementTargets.values());
+                Rect epicenter = calcEpicenter(mSharedElementTargets,
+                        mActivityOptions.getSharedElementNames());
+                mEpicenterCallback = new FixedEpicenterCallback(epicenter);
 
                 setViewVisibility(mEnteringViews, View.INVISIBLE);
                 setViewVisibility(mSharedElementTargets.values(), View.INVISIBLE);
@@ -4610,6 +4647,7 @@
                             transition = addTransitionTargets(transition,
                                     mSharedElementTargets.values(),
                                     true);
+                            transition.setEpicenterCallback(mEpicenterCallback);
                             if (transitionArgs == null) {
                                 TransitionManager.beginDelayedTransition(mDecor, transition);
                                 setViewVisibility(mSharedElementTargets.values(), View.VISIBLE);
@@ -4692,8 +4730,22 @@
                 transition = TransitionManager.getDefaultTransition();
             }
             transition = addTransitionTargets(transition, mEnteringViews, true);
+            transition.setEpicenterCallback(mEpicenterCallback);
             TransitionManager.beginDelayedTransition(mDecor, transition);
             setViewVisibility(mEnteringViews, View.VISIBLE);
         }
     }
+
+    private static class FixedEpicenterCallback extends Transition.EpicenterCallback {
+        private Rect mEpicenter;
+
+        public FixedEpicenterCallback(Rect epicenter) {
+            mEpicenter = epicenter;
+        }
+
+        @Override
+        public Rect getEpicenter(Transition transition) {
+            return mEpicenter;
+        }
+    };
 }