Merge "Don't auto scroll empty lists"
diff --git a/api/current.txt b/api/current.txt
index 7fed429..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
@@ -3150,6 +3151,7 @@
     method public void finishActivityFromChild(android.app.Activity, int);
     method public void finishAffinity();
     method public void finishFromChild(android.app.Activity);
+    method public void finishWithTransition();
     method public android.app.ActionBar getActionBar();
     method public final android.app.Application getApplication();
     method public android.content.ComponentName getCallingActivity();
@@ -6840,6 +6842,8 @@
     field public static final java.lang.String ACTION_INSTALL_PACKAGE = "android.intent.action.INSTALL_PACKAGE";
     field public static final java.lang.String ACTION_LOCALE_CHANGED = "android.intent.action.LOCALE_CHANGED";
     field public static final java.lang.String ACTION_MAIN = "android.intent.action.MAIN";
+    field public static final java.lang.String ACTION_MANAGED_PROFILE_ADDED = "android.intent.action.MANAGED_PROFILE_ADDED";
+    field public static final java.lang.String ACTION_MANAGED_PROFILE_REMOVED = "android.intent.action.MANAGED_PROFILE_REMOVED";
     field public static final java.lang.String ACTION_MANAGE_NETWORK_USAGE = "android.intent.action.MANAGE_NETWORK_USAGE";
     field public static final java.lang.String ACTION_MANAGE_PACKAGE_STORAGE = "android.intent.action.MANAGE_PACKAGE_STORAGE";
     field public static final java.lang.String ACTION_MEDIA_BAD_REMOVAL = "android.intent.action.MEDIA_BAD_REMOVAL";
@@ -6989,6 +6993,7 @@
     field public static final java.lang.String EXTRA_TEXT = "android.intent.extra.TEXT";
     field public static final java.lang.String EXTRA_TITLE = "android.intent.extra.TITLE";
     field public static final java.lang.String EXTRA_UID = "android.intent.extra.UID";
+    field public static final java.lang.String EXTRA_USER = "android.intent.extra.user";
     field public static final int FILL_IN_ACTION = 1; // 0x1
     field public static final int FILL_IN_CATEGORIES = 4; // 0x4
     field public static final int FILL_IN_CLIP_DATA = 128; // 0x80
@@ -27403,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);
@@ -27410,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);
@@ -27422,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);
@@ -27439,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();
@@ -27450,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);
@@ -27480,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);
@@ -27504,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);
   }
 
 }
@@ -30457,6 +30527,8 @@
     method public boolean requestFeature(int);
     method public abstract void restoreHierarchyState(android.os.Bundle);
     method public abstract android.os.Bundle saveHierarchyState();
+    method public void setAllowOverlappingEnterTransition(boolean);
+    method public void setAllowOverlappingExitTransition(boolean);
     method public void setAttributes(android.view.WindowManager.LayoutParams);
     method public abstract void setBackgroundDrawable(android.graphics.drawable.Drawable);
     method public void setBackgroundDrawableResource(int);
@@ -30485,7 +30557,6 @@
     method public abstract void setTitle(java.lang.CharSequence);
     method public abstract deprecated void setTitleColor(int);
     method public void setTransitionManager(android.transition.TransitionManager);
-    method public void setTriggerEarlyEnterTransition(boolean);
     method public void setType(int);
     method public void setUiOptions(int);
     method public void setUiOptions(int, int);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index e8fdcaf..b18eb98 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -21,7 +21,6 @@
 import android.transition.Transition;
 import android.transition.TransitionManager;
 import android.util.ArrayMap;
-import android.util.Pair;
 import android.util.SuperNotCalledException;
 import android.widget.Toolbar;
 import com.android.internal.app.WindowDecorActionBar;
@@ -774,7 +773,6 @@
 
     private Thread mUiThread;
     final Handler mHandler = new Handler();
-    private ActivityOptions mTransitionActivityOptions;
 
     /** Return the intent that started this activity. */
     public Intent getIntent() {
@@ -1400,6 +1398,9 @@
     protected void onStop() {
         if (DEBUG_LIFECYCLE) Slog.v(TAG, "onStop " + this);
         if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(false);
+        if (mWindow != null) {
+            mWindow.restoreViewVisibilityAfterTransitionToCallee();
+        }
         getApplication().dispatchActivityStopped(this);
         mTranslucentCallback = null;
         mCalled = true;
@@ -2300,7 +2301,7 @@
      */
     public void onBackPressed() {
         if (!mFragments.popBackStackImmediate()) {
-            finish();
+            finishWithTransition();
         }
     }
 
@@ -3483,8 +3484,7 @@
     public void startActivityForResult(Intent intent, int requestCode) {
         Bundle options = null;
         if (mWindow.hasFeature(Window.FEATURE_CONTENT_TRANSITIONS)) {
-            final Pair<View, String>[] noSharedElements = null;
-            options = ActivityOptions.makeSceneTransitionAnimation(noSharedElements).toBundle();
+            options = ActivityOptions.makeSceneTransitionAnimation().toBundle();
         }
         startActivityForResult(intent, requestCode, options);
     }
@@ -3532,7 +3532,7 @@
                     mActionBar.captureSharedElements(sharedElementMap);
                     activityOptions.addSharedElements(sharedElementMap);
                 }
-                options = mWindow.startExitTransition(activityOptions);
+                options = mWindow.startExitTransitionToCallee(options);
             }
         }
         if (mParent == null) {
@@ -4387,6 +4387,23 @@
     }
 
     /**
+     * Reverses the Activity Scene entry Transition and triggers the calling Activity
+     * to reverse its exit Transition. When the exit Transition completes,
+     * {@link #finish()} is called. If no entry Transition was used, finish() is called
+     * immediately and the Activity exit Transition is run.
+     * @see android.view.Window#setTriggerEarlySceneTransition(boolean, boolean)
+     * @see android.app.ActivityOptions#makeSceneTransitionAnimation(android.view.View, String)
+     */
+    public void finishWithTransition() {
+        mWindow.startExitTransitionToCaller(new Runnable() {
+            @Override
+            public void run() {
+                finish();
+            }
+        });
+    }
+
+    /**
      * Force finish another activity that you had previously started with
      * {@link #startActivityForResult}.
      * 
@@ -5396,43 +5413,34 @@
         }
         mWindowManager = mWindow.getWindowManager();
         mCurrentConfig = config;
-        mTransitionActivityOptions = null;
-        Window.SceneTransitionListener sceneTransitionListener = null;
-        if (options != null) {
-            ActivityOptions activityOptions = new ActivityOptions(options);
-            if (activityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
-                mTransitionActivityOptions = activityOptions;
-                sceneTransitionListener = new Window.SceneTransitionListener() {
-                    @Override
-                    public void nullPendingTransition() {
-                        overridePendingTransition(0, 0);
-                    }
-
-                    @Override
-                    public void convertFromTranslucent() {
-                        Activity.this.convertFromTranslucent();
-                    }
-
-                    @Override
-                    public void convertToTranslucent() {
-                        Activity.this.convertToTranslucent(null);
-                    }
-
-                    @Override
-                    public void sharedElementStart(Transition transition) {
-                        Activity.this.onCaptureSharedElementStart(transition);
-                    }
-
-                    @Override
-                    public void sharedElementEnd() {
-                        Activity.this.onCaptureSharedElementEnd();
-                    }
-                };
-
+        Window.SceneTransitionListener sceneTransitionListener
+                = new Window.SceneTransitionListener() {
+            @Override
+            public void nullPendingTransition() {
+                overridePendingTransition(0, 0);
             }
-        }
 
-        mWindow.setTransitionOptions(mTransitionActivityOptions, sceneTransitionListener);
+            @Override
+            public void convertFromTranslucent() {
+                Activity.this.convertFromTranslucent();
+            }
+
+            @Override
+            public void convertToTranslucent() {
+                Activity.this.convertToTranslucent(null);
+            }
+
+            @Override
+            public void sharedElementStart(Transition transition) {
+                Activity.this.onCaptureSharedElementStart(transition);
+            }
+
+            @Override
+            public void sharedElementEnd() {
+                Activity.this.onCaptureSharedElementEnd();
+            }
+        };
+        mWindow.setTransitionOptions(options, sceneTransitionListener);
     }
 
     /** @hide */
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 07247ff..4384580 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -20,8 +20,10 @@
 import android.graphics.Bitmap;
 import android.os.Bundle;
 import android.os.Handler;
+import android.os.IBinder;
 import android.os.IRemoteCallback;
 import android.os.RemoteException;
+import android.os.ResultReceiver;
 import android.transition.Transition;
 import android.util.Log;
 import android.util.Pair;
@@ -136,6 +138,11 @@
     /** @hide */
     public static final int ANIM_SCENE_TRANSITION = 5;
 
+    private static final int MSG_SET_LISTENER = 100;
+    private static final int MSG_HIDE_SHARED_ELEMENTS = 101;
+    private static final int MSG_PREPARE_RESTORE = 102;
+    private static final int MSG_RESTORE = 103;
+
     private String mPackageName;
     private int mAnimationType = ANIM_NONE;
     private int mCustomEnterResId;
@@ -146,7 +153,7 @@
     private int mStartWidth;
     private int mStartHeight;
     private IRemoteCallback mAnimationStartedListener;
-    private IRemoteCallback mTransitionCompleteListener;
+    private ResultReceiver mTransitionCompleteListener;
     private ArrayList<String> mSharedElementNames;
     private ArrayList<String> mLocalElementNames;
 
@@ -428,8 +435,7 @@
                 break;
 
             case ANIM_SCENE_TRANSITION:
-                mTransitionCompleteListener = IRemoteCallback.Stub.asInterface(
-                        opts.getBinder(KEY_TRANSITION_COMPLETE_LISTENER));
+                mTransitionCompleteListener = opts.getParcelable(KEY_TRANSITION_COMPLETE_LISTENER);
                 mSharedElementNames = opts.getStringArrayList(KEY_SHARED_ELEMENT_NAMES);
                 mLocalElementNames = opts.getStringArrayList(KEY_LOCAL_ELEMENT_NAMES);
                 break;
@@ -495,7 +501,6 @@
     /** @hide */
     public void dispatchSceneTransitionStarted(final ActivityTransitionTarget target,
             ArrayList<String> sharedElementNames) {
-        boolean listenerSent = false;
         if (mTransitionCompleteListener != null) {
             IRemoteCallback callback = new IRemoteCallback.Stub() {
                 @Override
@@ -510,27 +515,28 @@
             Bundle bundle = new Bundle();
             bundle.putBinder(KEY_TRANSITION_TARGET_LISTENER, callback.asBinder());
             bundle.putStringArrayList(KEY_SHARED_ELEMENT_NAMES, sharedElementNames);
-            try {
-                mTransitionCompleteListener.sendResult(bundle);
-                listenerSent = true;
-            } catch (RemoteException e) {
-                Log.w(TAG, "Couldn't retrieve transition notifications", e);
-            }
-        }
-        if (!listenerSent) {
-            target.sharedElementTransitionComplete(null);
-            target.exitTransitionComplete();
+            mTransitionCompleteListener.send(MSG_SET_LISTENER, bundle);
         }
     }
 
     /** @hide */
     public void dispatchSharedElementsReady() {
         if (mTransitionCompleteListener != null) {
-            try {
-                mTransitionCompleteListener.sendResult(null);
-            } catch (RemoteException e) {
-                Log.w(TAG, "Couldn't synchronize shared elements", e);
-            }
+            mTransitionCompleteListener.send(MSG_HIDE_SHARED_ELEMENTS, null);
+        }
+    }
+
+    /** @hide */
+    public void dispatchPrepareRestore() {
+        if (mTransitionCompleteListener != null) {
+            mTransitionCompleteListener.send(MSG_PREPARE_RESTORE, null);
+        }
+    }
+
+    /** @hide */
+    public void dispatchRestore(Bundle sharedElementsArgs) {
+        if (mTransitionCompleteListener != null) {
+            mTransitionCompleteListener.send(MSG_RESTORE, sharedElementsArgs);
         }
     }
 
@@ -658,8 +664,7 @@
             case ANIM_SCENE_TRANSITION:
                 b.putInt(KEY_ANIM_TYPE, mAnimationType);
                 if (mTransitionCompleteListener != null) {
-                    b.putBinder(KEY_TRANSITION_COMPLETE_LISTENER,
-                            mTransitionCompleteListener.asBinder());
+                    b.putParcelable(KEY_TRANSITION_COMPLETE_LISTENER, mTransitionCompleteListener);
                 }
                 b.putStringArrayList(KEY_SHARED_ELEMENT_NAMES, mSharedElementNames);
                 b.putStringArrayList(KEY_LOCAL_ELEMENT_NAMES, mLocalElementNames);
@@ -710,13 +715,13 @@
         Bundle getSharedElementExitState();
         void acceptedSharedElements(ArrayList<String> sharedElementNames);
         void hideSharedElements();
+        void restore(Bundle sharedElementState);
+        void prepareForRestore();
     }
 
-    private static class ExitTransitionListener extends IRemoteCallback.Stub
+    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;
@@ -724,25 +729,40 @@
 
         public ExitTransitionListener(Transition exitTransition, Transition sharedElementTransition,
                 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
-        public void sendResult(Bundle data) throws RemoteException {
-            if (data != null) {
-                mTransitionCompleteCallback = IRemoteCallback.Stub.asInterface(
-                        data.getBinder(KEY_TRANSITION_TARGET_LISTENER));
-                ArrayList<String> sharedElementNames
-                        = data.getStringArrayList(KEY_SHARED_ELEMENT_NAMES);
-                mSharedElementSource.acceptedSharedElements(sharedElementNames);
-                notifySharedElement();
-                notifyExit();
-            } else {
-                mSharedElementSource.hideSharedElements();
+        protected void onReceiveResult(int resultCode, Bundle resultData) {
+            switch (resultCode) {
+                case MSG_SET_LISTENER:
+                    IBinder listener = resultData.getBinder(KEY_TRANSITION_TARGET_LISTENER);
+                    mTransitionCompleteCallback = IRemoteCallback.Stub.asInterface(listener);
+                    ArrayList<String> sharedElementNames
+                            = resultData.getStringArrayList(KEY_SHARED_ELEMENT_NAMES);
+                    mSharedElementSource.acceptedSharedElements(sharedElementNames);
+                    notifySharedElement();
+                    notifyExit();
+                    break;
+                case MSG_HIDE_SHARED_ELEMENTS:
+                    mSharedElementSource.hideSharedElements();
+                    break;
+                case MSG_PREPARE_RESTORE:
+                    mSharedElementSource.prepareForRestore();
+                    break;
+                case MSG_RESTORE:
+                    mSharedElementSource.restore(resultData);
+                    break;
             }
         }
 
@@ -752,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/content/Intent.java b/core/java/android/content/Intent.java
index d189a34..9881428 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -2608,6 +2608,23 @@
             "android.intent.action.USER_INFO_CHANGED";
 
     /**
+     * Broadcast sent to the primary user when an associated managed profile is added (the profile
+     * was created and is ready to be used). Carries an extra {@link #EXTRA_USER} that specifies
+     * the UserHandle of the profile that was added. This is only sent to registered receivers,
+     * not manifest receivers.
+     */
+    public static final String ACTION_MANAGED_PROFILE_ADDED =
+            "android.intent.action.MANAGED_PROFILE_ADDED";
+
+    /**
+     * Broadcast sent to the primary user when an associated managed profile is removed. Carries an
+     * extra {@link #EXTRA_USER} that specifies the UserHandle of the profile that was removed. This
+     * is only sent to registered receivers, not manifest receivers.
+     */
+    public static final String ACTION_MANAGED_PROFILE_REMOVED =
+            "android.intent.action.MANAGED_PROFILE_REMOVED";
+
+    /**
      * Sent when the user taps on the clock widget in the system's "quick settings" area.
      */
     public static final String ACTION_QUICK_CLOCK =
@@ -3301,15 +3318,23 @@
             "android.intent.extra.ALLOW_MULTIPLE";
 
     /**
-     * The userHandle carried with broadcast intents related to addition, removal and switching of
-     * users
-     * - {@link #ACTION_USER_ADDED}, {@link #ACTION_USER_REMOVED} and {@link #ACTION_USER_SWITCHED}.
+     * The integer userHandle carried with broadcast intents related to addition, removal and
+     * switching of users and managed profiles - {@link #ACTION_USER_ADDED},
+     * {@link #ACTION_USER_REMOVED} and {@link #ACTION_USER_SWITCHED}.
+     *
      * @hide
      */
     public static final String EXTRA_USER_HANDLE =
             "android.intent.extra.user_handle";
 
     /**
+     * The UserHandle carried with broadcasts intents related to addition and removal of managed
+     * profiles - {@link #ACTION_MANAGED_PROFILE_ADDED} and {@link #ACTION_MANAGED_PROFILE_REMOVED}.
+     */
+    public static final String EXTRA_USER =
+            "android.intent.extra.user";
+
+    /**
      * Extra used in the response from a BroadcastReceiver that handles
      * {@link #ACTION_GET_RESTRICTION_ENTRIES}. The type of the extra is
      * <code>ArrayList&lt;RestrictionEntry&gt;</code>.
diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java
index 535eee1..638ef22 100644
--- a/core/java/android/text/StaticLayout.java
+++ b/core/java/android/text/StaticLayout.java
@@ -433,7 +433,7 @@
                         }
 
                         if (mLineCount >= mMaximumVisibleLineCount) {
-                            break;
+                            return;
                         }
                     }
                 }
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/java/android/view/Window.java b/core/java/android/view/Window.java
index 0cd6325..7bd1f56 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -18,7 +18,6 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.app.ActivityOptions;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.content.res.TypedArray;
@@ -1390,11 +1389,11 @@
      * @param options Options to set or null for none
      * @hide
      */
-    public void setTransitionOptions(ActivityOptions options, SceneTransitionListener listener) {
+    public void setTransitionOptions(Bundle options, SceneTransitionListener listener) {
     }
 
     /**
-     * A callback for Activity transitions to be told when the shared element is ready to be shown
+     * A callback for Window transitions to be told when the shared element is ready to be shown
      * and start the transition to its target location.
      * @hide
      */
@@ -1407,27 +1406,57 @@
     }
 
     /**
-     * Controls when the Activity enter scene is triggered and the background is faded in. If
-     * triggerEarly is true, the enter scene will begin as soon as possible and the background
-     * will fade in when all shared elements are ready to begin transitioning. If triggerEarly is
-     * false, the Activity enter scene and background fade will be triggered when the calling
-     * Activity's exit transition completes.
-     *
-     * @param triggerEarly Set to true to have the Activity enter scene transition in as early as
-     *                     possible or set to false to wait for the calling Activity to exit first.
+     * Controls how the Activity's start Scene is faded in and when the enter scene
+     * is triggered to start.
+     * <p>When allow is true, the enter Scene will begin as soon as possible
+     * and the background will fade in when all shared elements are ready to begin
+     * transitioning. If allow is false, the Activity enter Scene and
+     * background fade will be triggered when the calling Activity's exit transition
+     * completes.</p>
+     * @param allow Set to true to have the Activity enter scene transition in
+     *              as early as possible or set to false to wait for the calling
+     *              Activity to exit first. The default value is true.
      */
-    public void setTriggerEarlyEnterTransition(boolean triggerEarly) {
+    public void setAllowOverlappingEnterTransition(boolean allow) {
+    }
+
+    /**
+     * Controls how the Activity's Scene fades out and when the calling Activity's
+     * enter scene is triggered when finishing to return to a calling Activity.
+     * <p>When allow is true, the Scene will fade out quickly
+     * and inform the calling Activity to transition in when the fade completes.
+     * When allow is false, the calling Activity will transition in after
+     * the Activity's Scene has exited.
+     * </p>
+     * @param allow Set to true to have the Activity fade out as soon as possible
+     *              and transition in the calling Activity. The default value is
+     *              true.
+     */
+    public void setAllowOverlappingExitTransition(boolean allow) {
     }
 
     /**
      * Start the exit transition.
      * @hide
      */
-    public Bundle startExitTransition(ActivityOptions options) {
+    public Bundle startExitTransitionToCallee(Bundle options) {
         return null;
     }
 
     /**
+     * Starts the transition back to the calling Activity.
+     * onTransitionEnd will be called on the current thread if there is no exit transition.
+     * @hide
+     */
+    public void startExitTransitionToCaller(Runnable onTransitionEnd) {
+        onTransitionEnd.run();
+    }
+
+    /** @hide */
+    public void restoreViewVisibilityAfterTransitionToCallee() {
+    }
+
+    /**
      * On entering Activity Scene transitions, shared element names may be mapped from a
      * source Activity's specified name to a unique shared element name in the View hierarchy.
      * Under most circumstances, mapping is not necessary - a single View will have the
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/docs/html/training/game-controllers/controller-input.jd b/docs/html/training/game-controllers/controller-input.jd
index 2c50ae1..c9517ba 100644
--- a/docs/html/training/game-controllers/controller-input.jd
+++ b/docs/html/training/game-controllers/controller-input.jd
@@ -236,26 +236,33 @@
   </tr>
   <tr>
     <td>Start game in main menu, or pause/unpause during game</td>
-    <td>{@link android.view.KeyEvent#KEYCODE_BUTTON_START BUTTON_START}</td>
+    <td>{@link android.view.KeyEvent#KEYCODE_BUTTON_START BUTTON_START}<sup>*</sup></td>
   </tr>
   <tr>
     <td>Display menu</td>
-    <td>{@link android.view.KeyEvent#KEYCODE_BUTTON_SELECT BUTTON_SELECT} and
-{@link android.view.KeyEvent#KEYCODE_MENU}</td>
+    <td>{@link android.view.KeyEvent#KEYCODE_BUTTON_SELECT BUTTON_SELECT}<sup>*</sup>
+      and {@link android.view.KeyEvent#KEYCODE_MENU}<sup>*</sup></td>
   </tr>
   <tr>
     <td>Same as Android <em>Back</em></td>
-    <td>{@link android.view.KeyEvent#KEYCODE_BUTTON_B BUTTON_B}<sup>*</sup> and
-{@link android.view.KeyEvent#KEYCODE_BACK KEYCODE_BACK}</td>
+    <td>{@link android.view.KeyEvent#KEYCODE_BACK KEYCODE_BACK}</td>
+  </tr>
+  <tr>
+    <td>Navigate back to a previous item in a menu</td>
+    <td>{@link android.view.KeyEvent#KEYCODE_BUTTON_B BUTTON_B}<sup>**</sup></td>
   </tr>
   <tr>
     <td>Confirm selection, or perform primary game action</td>
-    <td>{@link android.view.KeyEvent#KEYCODE_BUTTON_A BUTTON_A}<sup>*</sup> and
+    <td>{@link android.view.KeyEvent#KEYCODE_BUTTON_A BUTTON_A}<sup>**</sup> and
 {@link android.view.KeyEvent#KEYCODE_DPAD_CENTER DPAD_CENTER}</td>
   </tr>
 </table>
 <p>
-<em>* This could be the opposite button (A/B), depending on the locale that
+<em>* Your game should not rely on the presence of the Start, Select, or Menu
+  buttons.</em>
+</p>
+<p>
+<em>** This could be the opposite button (A/B), depending on the locale that
 you are supporting.</em>
 </p>
 
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentTasksLoader.java b/packages/SystemUI/src/com/android/systemui/recent/RecentTasksLoader.java
index ab2ad96..eb09335 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentTasksLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentTasksLoader.java
@@ -156,7 +156,7 @@
 
     // Create an TaskDescription, returning null if the title or icon is null
     TaskDescription createTaskDescription(int taskId, int persistentTaskId, Intent baseIntent,
-            ComponentName origActivity, CharSequence description) {
+            ComponentName origActivity, CharSequence description, int userId) {
         Intent intent = new Intent(baseIntent);
         if (origActivity != null) {
             intent.setComponent(origActivity);
@@ -175,7 +175,7 @@
 
                 TaskDescription item = new TaskDescription(taskId,
                         persistentTaskId, resolveInfo, baseIntent, info.packageName,
-                        description);
+                        description, userId);
                 item.setLabel(title);
 
                 return item;
@@ -391,7 +391,8 @@
 
             item = createTaskDescription(recentInfo.id,
                     recentInfo.persistentId, recentInfo.baseIntent,
-                    recentInfo.origActivity, recentInfo.description);
+                    recentInfo.origActivity, recentInfo.description,
+                    recentInfo.userId);
             if (item != null) {
                 loadThumbnailAndIcon(item);
             }
@@ -474,7 +475,8 @@
 
                     TaskDescription item = createTaskDescription(recentInfo.id,
                             recentInfo.persistentId, recentInfo.baseIntent,
-                            recentInfo.origActivity, recentInfo.description);
+                            recentInfo.origActivity, recentInfo.description,
+                            recentInfo.userId);
 
                     if (item != null) {
                         while (true) {
diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
index ef56044..98bdee0 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/RecentsPanelView.java
@@ -689,7 +689,7 @@
             if (DEBUG) Log.v(TAG, "Starting activity " + intent);
             try {
                 context.startActivityAsUser(intent, opts,
-                        new UserHandle(UserHandle.USER_CURRENT));
+                        new UserHandle(ad.userId));
             } catch (SecurityException e) {
                 Log.e(TAG, "Recents does not have the permission to launch " + intent, e);
             } catch (ActivityNotFoundException e) {
diff --git a/packages/SystemUI/src/com/android/systemui/recent/TaskDescription.java b/packages/SystemUI/src/com/android/systemui/recent/TaskDescription.java
index 2e0ee36..5ad965f 100644
--- a/packages/SystemUI/src/com/android/systemui/recent/TaskDescription.java
+++ b/packages/SystemUI/src/com/android/systemui/recent/TaskDescription.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.recent;
 
+import android.os.UserHandle;
 import android.content.Intent;
 import android.content.pm.ResolveInfo;
 import android.graphics.drawable.Drawable;
@@ -27,6 +28,7 @@
     final Intent intent; // launch intent for application
     final String packageName; // used to override animations (see onClick())
     final CharSequence description;
+    final int userId;
 
     private Drawable mThumbnail; // generated by Activity.onCreateThumbnail()
     private Drawable mIcon; // application package icon
@@ -35,7 +37,7 @@
 
     public TaskDescription(int _taskId, int _persistentTaskId,
             ResolveInfo _resolveInfo, Intent _intent,
-            String _packageName, CharSequence _description) {
+            String _packageName, CharSequence _description, int _userId) {
         resolveInfo = _resolveInfo;
         intent = _intent;
         taskId = _taskId;
@@ -43,6 +45,7 @@
 
         description = _description;
         packageName = _packageName;
+        userId = _userId;
     }
 
     public TaskDescription() {
@@ -53,6 +56,7 @@
 
         description = null;
         packageName = null;
+        userId = UserHandle.USER_NULL;
     }
 
     public void setLoaded(boolean loaded) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java
index e193a95..d661f287 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsTaskLoader.java
@@ -414,7 +414,7 @@
 
             // Create a new task
             Task task = new Task(t.persistentId, (t.id > -1), t.baseIntent, activityLabel,
-                    activityIcon);
+                    activityIcon, t.userId);
 
             // Preload the specified number of apps
             if (i >= (taskCount - preloadCount)) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
index ed2ab2a..a0ff3b7 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
@@ -65,15 +65,17 @@
     public Bitmap activityIcon;
     public Bitmap thumbnail;
     public boolean isActive;
+    public int userId;
 
     TaskCallbacks mCb;
 
     public Task(int id, boolean isActive, Intent intent, String activityTitle,
-                Bitmap activityIcon) {
+                Bitmap activityIcon, int userId) {
         this.key = new TaskKey(id, intent);
         this.activityLabel = activityTitle;
         this.activityIcon = activityIcon;
         this.isActive = isActive;
+        this.userId = userId;
     }
 
     /** Set the callbacks */
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index cb52794..1ebe231 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -257,9 +257,9 @@
                     try {
                         if (opts != null) {
                             getContext().startActivityAsUser(i, opts.toBundle(),
-                                    UserHandle.CURRENT);
+                                    new UserHandle(task.userId));
                         } else {
-                            getContext().startActivityAsUser(i, UserHandle.CURRENT);
+                            getContext().startActivityAsUser(i, new UserHandle(task.userId));
                         }
                     } catch (ActivityNotFoundException anfe) {
                         Console.logError(getContext(), "Could not start Activity");
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindow.java b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
index f1db904..79ed866 100644
--- a/policy/src/com/android/internal/policy/impl/PhoneWindow.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindow.java
@@ -25,6 +25,8 @@
 import android.animation.Animator;
 import android.animation.ObjectAnimator;
 import android.app.ActivityOptions;
+import android.os.Looper;
+import android.transition.Fade;
 import android.transition.Scene;
 import android.transition.Transition;
 import android.transition.TransitionInflater;
@@ -109,7 +111,6 @@
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.List;
 import java.util.Map;
 
 /**
@@ -253,8 +254,10 @@
 
     private ActivityOptions mActivityOptions;
     private SceneTransitionListener mSceneTransitionListener;
-    private boolean mTriggerEarly = true;
+    private boolean mAllowEnterOverlap = true;
+    private boolean mAllowExitOverlap = true;
     private Map<String, String> mSharedElementsMap;
+    private ArrayList<View> mTransitioningViews;
 
     static class WindowManagerHolder {
         static final IWindowManager sWindowManager = IWindowManager.Stub.asInterface(
@@ -4081,14 +4084,26 @@
     }
 
     @Override
-    public void setTransitionOptions(ActivityOptions options, SceneTransitionListener listener) {
+    public void setTransitionOptions(Bundle options, SceneTransitionListener listener) {
         mSceneTransitionListener = listener;
-        mActivityOptions = options;
+        ActivityOptions activityOptions = null;
+        if (options != null) {
+            activityOptions = new ActivityOptions(options);
+            if (activityOptions.getAnimationType() != ActivityOptions.ANIM_SCENE_TRANSITION) {
+                activityOptions = null;
+            }
+        }
+        mActivityOptions = activityOptions;
     }
 
     @Override
-    public void setTriggerEarlyEnterTransition(boolean triggerEarly) {
-        mTriggerEarly = triggerEarly;
+    public void setAllowOverlappingEnterTransition(boolean allow) {
+        mAllowEnterOverlap = allow;
+    }
+
+    @Override
+    public void setAllowOverlappingExitTransition(boolean allow) {
+        mAllowExitOverlap = allow;
     }
 
     @Override
@@ -4097,7 +4112,50 @@
     }
 
     @Override
-    public Bundle startExitTransition(ActivityOptions activityOptions) {
+    public void restoreViewVisibilityAfterTransitionToCallee() {
+        if (mTransitioningViews != null) {
+            setViewVisibility(mTransitioningViews, View.VISIBLE);
+        }
+    }
+
+    @Override
+    public void startExitTransitionToCaller(final Runnable onTransitionEnd) {
+        Transition transition;
+        if (mContentScene == null || mTransitionManager == null || mActivityOptions == null
+                || (transition = mTransitionManager.getEnterTransition(mContentScene)) == null) {
+            onTransitionEnd.run();
+            return;
+        }
+        if (mAllowExitOverlap) {
+            TransitionSet transitionSet = new TransitionSet();
+            transitionSet.addTransition(transition);
+            Fade fade = new Fade();
+            transitionSet.addTransition(fade);
+            transition = transitionSet;
+        }
+
+        final ArrayMap<String, View> sharedElements = new ArrayMap<String, View>();
+        mapSharedElements(sharedElements);
+        final Bundle sharedElementArgs = new Bundle();
+        captureTerminalSharedElementState(sharedElements, sharedElementArgs);
+
+        final ArrayList<View> transitioningViews = new ArrayList<View>();
+        mDecor.captureTransitioningViews(transitioningViews);
+        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);
+        mTransitionManager.beginDelayedTransition(mDecor, transition);
+        setViewVisibility(transitioningViews, View.INVISIBLE);
+    }
+
+    @Override
+    public Bundle startExitTransitionToCallee(Bundle options) {
         if (mContentScene == null) {
             return null;
         }
@@ -4106,16 +4164,15 @@
             return null;
         }
 
+        ActivityOptions activityOptions = new ActivityOptions(options);
         ArrayMap<String, View> sharedElements = findSharedElements(activityOptions);
 
         // Find exiting Views and shared elements
-        final ArrayList<View> transitioningViews = new ArrayList<View>();
-        mDecor.captureTransitioningViews(transitioningViews);
-        transitioningViews.removeAll(sharedElements.values());
+        ArrayList<View> transitioningViews = captureTransitioningViews(sharedElements.values());
 
-        Transition exitTransition = cloneAndSetTransitionTargets(transition,
+        Transition exitTransition = addTransitionTargets(transition,
                 transitioningViews, true);
-        Transition sharedElementTransition = cloneAndSetTransitionTargets(transition,
+        Transition sharedElementTransition = addTransitionTargets(transition,
                 transitioningViews, false);
 
         // transitionSet is the total exit transition, including hero animation.
@@ -4123,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, exitTransition);
+                sharedElementTransition, transitioningViews, exitTransition, epicenterCallback);
 
         // Start exiting the Views that need to exit
         TransitionManager.beginDelayedTransition(mDecor, transitionSet);
@@ -4133,6 +4194,14 @@
         return activityOptions.toBundle();
     }
 
+    private ArrayList<View> captureTransitioningViews(Collection<View> sharedElements) {
+        mTransitioningViews = new ArrayList<View>();
+        mDecor.captureTransitioningViews(mTransitioningViews);
+        ArrayList<View> transitioningViews = (ArrayList<View>) mTransitioningViews.clone();
+        transitioningViews.removeAll(sharedElements);
+        return transitioningViews;
+    }
+
     private ArrayMap<String, View> findSharedElements(ActivityOptions activityOptions) {
         ArrayMap<String, View> sharedElements = new ArrayMap<String, View>();
         mDecor.findSharedElements(sharedElements);
@@ -4149,9 +4218,18 @@
         return sharedElements;
     }
 
+    private static void runOnUiThread(Handler handler, Runnable runnable) {
+        if (handler.getLooper() != Looper.myLooper()) {
+            handler.post(runnable);
+        } else {
+            runnable.run();
+        }
+    }
+
     private void updateExitActivityOptions(ActivityOptions activityOptions,
             final Map<String, View> sharedElements, Transition sharedElementTransition,
-            Transition exitTransition) {
+            final ArrayList<View> transitioningViews, Transition exitTransition,
+            final Transition.EpicenterCallback epicenterCallback) {
 
         // Schedule capturing of the shared element state
         final Bundle sharedElementArgs = new Bundle();
@@ -4159,30 +4237,88 @@
 
         ActivityOptions.SharedElementSource sharedElementSource
                 = new ActivityOptions.SharedElementSource() {
+            private Handler mHandler = new Handler();
+
             @Override
             public Bundle getSharedElementExitState() {
                 return sharedElementArgs;
             }
 
             @Override
-            public void acceptedSharedElements(ArrayList<String> sharedElementNames) {
+            public void acceptedSharedElements(final ArrayList<String> sharedElementNames) {
                 if (sharedElementNames.size() == sharedElements.size()) {
                     return; // They were all accepted
                 }
-                Transition transition = mTransitionManager.getExitTransition(mContentScene).clone();
-                TransitionManager.beginDelayedTransition(mDecor, transition);
-                for (String name: sharedElements.keySet()) {
-                    if (!sharedElementNames.contains(name)) {
-                        sharedElements.get(name).setVisibility(View.INVISIBLE);
+                runOnUiThread(mHandler, new Runnable() {
+                    @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)) {
+                                sharedElements.get(name).setVisibility(View.INVISIBLE);
+                            }
+                        }
+                        sharedElements.keySet().retainAll(sharedElementNames);
                     }
-                }
-                sharedElements.keySet().retainAll(sharedElementNames);
+                });
             }
 
             @Override
             public void hideSharedElements() {
                 if (sharedElements != null) {
-                    setViewVisibility(sharedElements.values(), View.INVISIBLE);
+                    runOnUiThread(mHandler, new Runnable() {
+                        @Override
+                        public void run() {
+                            setViewVisibility(sharedElements.values(), View.INVISIBLE);
+                        }
+                    });
+                }
+            }
+
+            @Override
+            public void restore(final Bundle sharedElementState) {
+                runOnUiThread(mHandler, new Runnable() {
+                    @Override
+                    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) {
+                            mSceneTransitionListener.sharedElementStart(transition);
+                            mDecor.getViewTreeObserver().addOnPreDrawListener(
+                                    new ViewTreeObserver.OnPreDrawListener() {
+                                        @Override
+                                        public boolean onPreDraw() {
+                                            mDecor.getViewTreeObserver().removeOnPreDrawListener(this);
+                                            mSceneTransitionListener.sharedElementEnd();
+                                            return true;
+                                        }
+                                    });
+                        }
+                        TransitionManager.beginDelayedTransition(mDecor, transition);
+                        setViewVisibility(transitioningViews, View.VISIBLE);
+                        for (View sharedElement: sharedElements.values()) {
+                            sharedElement.requestLayout();
+                        }
+                    }
+                });
+            }
+
+            @Override
+            public void prepareForRestore() {
+                if (mTransitioningViews != null) {
+                    runOnUiThread(mHandler, new Runnable() {
+                        @Override
+                        public void run() {
+                            setViewVisibility(mTransitioningViews, View.INVISIBLE);
+                        }
+                    });
                 }
             }
         };
@@ -4207,22 +4343,18 @@
         });
     }
 
-    private static Transition cloneAndSetTransitionTargets(Transition transition,
-            List<View> views, boolean add) {
-        transition = transition.clone();
-        if (!transition.getTargetIds().isEmpty() || !transition.getTargets().isEmpty()) {
-            TransitionSet set = new TransitionSet();
-            set.addTransition(transition);
-            transition = set;
-        }
+    private static Transition addTransitionTargets(Transition transition, Collection<View> views,
+            boolean add) {
+        TransitionSet set = new TransitionSet();
+        set.addTransition(transition.clone());
         for (View view: views) {
             if (add) {
-                transition.addTarget(view);
+                set.addTarget(view);
             } else {
-                transition.excludeTarget(view, true);
+                set.excludeTarget(view, true);
             }
         }
-        return transition;
+        return set;
     }
 
     private static void setViewVisibility(Collection<View> views, int visibility) {
@@ -4231,6 +4363,14 @@
         }
     }
 
+    private static void setSharedElementState(Map<String, View> sharedElements,
+            Bundle sharedElementState) {
+        int[] tempLoc = new int[2];
+        for (Map.Entry<String, View> entry: sharedElements.entrySet()) {
+            setSharedElementState(entry.getValue(), entry.getKey(), sharedElementState, tempLoc);
+        }
+    }
+
     /**
      * Sets the captured values from a previous
      * {@link #captureSharedElementState(android.view.View, String, android.os.Bundle, int[])}
@@ -4247,23 +4387,27 @@
             return;
         }
 
-        int x = sharedElementBundle.getInt(KEY_SCREEN_X);
-        view.getLocationOnScreen(tempLoc);
-        int offsetX = x - tempLoc[0];
-        view.offsetLeftAndRight(offsetX);
-
-        int width = sharedElementBundle.getInt(KEY_WIDTH);
-        view.setRight(view.getLeft() + width);
-
-        int y = sharedElementBundle.getInt(KEY_SCREEN_Y);
-        int offsetY = y - tempLoc[1];
-        view.offsetTopAndBottom(offsetY);
-
-        int height = sharedElementBundle.getInt(KEY_HEIGHT);
-        view.setBottom(view.getTop() + height);
-
         float z = sharedElementBundle.getFloat(KEY_TRANSLATION_Z);
         view.setTranslationZ(z);
+
+        int x = sharedElementBundle.getInt(KEY_SCREEN_X);
+        int y = sharedElementBundle.getInt(KEY_SCREEN_Y);
+        int width = sharedElementBundle.getInt(KEY_WIDTH);
+        int height = sharedElementBundle.getInt(KEY_HEIGHT);
+
+        int widthSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
+        int heightSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);
+        view.measure(widthSpec, heightSpec);
+
+        ViewGroup parent = (ViewGroup) view.getParent();
+        parent.getLocationOnScreen(tempLoc);
+        int left = x - tempLoc[0];
+        int top = y - tempLoc[1];
+        int right = left + width;
+        int bottom = top + height;
+        view.layout(left, top, right, bottom);
+
+        view.requestLayout();
     }
 
     /**
@@ -4297,6 +4441,124 @@
         transitionArgs.putBundle(name, sharedElementBundle);
     }
 
+    private void mapSharedElements(ArrayMap<String, View> sharedElements) {
+        ArrayList<String> sharedElementNames = mActivityOptions.getSharedElementNames();
+        if (sharedElementNames != null) {
+            mDecor.findSharedElements(sharedElements);
+            if (mSharedElementsMap != null) {
+                for (Map.Entry<String, String> entry : mSharedElementsMap.entrySet()) {
+                    View sharedElement = sharedElements.remove(entry.getValue());
+                    if (sharedElement != null) {
+                        sharedElements.put(entry.getKey(), sharedElement);
+                    }
+                }
+            }
+            sharedElements.keySet().retainAll(sharedElementNames);
+        }
+    }
+
+    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;
+        private boolean mBackgroundFadeComplete;
+        private boolean mOnCompleteExecuted;
+        private boolean mSharedElementTransitioned;
+        private Runnable mOnComplete;
+        private Bundle mSharedElementArgs;
+        private Collection<View> mSharedElements;
+
+        public ExitSceneBack(Runnable onComplete, Bundle sharedElementArgs,
+                Collection<View> sharedElements) {
+            mOnComplete = onComplete;
+            mSharedElementArgs = sharedElementArgs;
+            mSharedElements = sharedElements;
+        }
+
+        public void start(Transition exitTransition) {
+            if (mActivityOptions != null) {
+                mActivityOptions.dispatchPrepareRestore();
+            }
+            exitTransition.addListener(this);
+            Drawable background = mDecor.getBackground();
+            if (background != null) {
+                ObjectAnimator animator = ObjectAnimator.ofInt(background, "alpha", 0);
+                animator.addListener(this);
+                animator.start();
+            } else {
+                mBackgroundFadeComplete = true;
+                startCalledActivityEnter();
+            }
+        }
+
+        @Override
+        public void onTransitionEnd(Transition transition) {
+            transition.removeListener(this);
+            mExitTransitionComplete = true;
+            notifyComplete();
+            if (!mAllowExitOverlap) {
+                startCalledActivityEnter();
+            }
+        }
+
+        private void notifyComplete() {
+            if (mExitTransitionComplete && mBackgroundFadeComplete
+                    && mSharedElementTransitioned && !mOnCompleteExecuted) {
+                mOnComplete.run();
+                mSceneTransitionListener.nullPendingTransition();
+                mOnCompleteExecuted = true;
+            }
+        }
+
+        @Override
+        public void onAnimationStart(Animator animation) {
+        }
+
+        @Override
+        public void onAnimationEnd(Animator animation) {
+            mBackgroundFadeComplete = true;
+            if (mAllowExitOverlap) {
+                startCalledActivityEnter();
+            }
+        }
+
+        @Override
+        public void onAnimationCancel(Animator animation) {
+        }
+
+        @Override
+        public void onAnimationRepeat(Animator animation) {
+        }
+
+        private void startCalledActivityEnter() {
+            mActivityOptions.dispatchRestore(mSharedElementArgs);
+            setViewVisibility(mSharedElements, View.INVISIBLE);
+            mSharedElementTransitioned = true;
+            notifyComplete();
+        }
+    }
+
     /**
      * Provides code for handling the Activity transition entering scene.
      * When the first scene is laid out (onPreDraw), it makes views invisible.
@@ -4317,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();
@@ -4335,33 +4598,25 @@
             if (!mEnterTransitionStarted && mSceneTransitionListener != null) {
                 mEnterTransitionStarted = true;
                 mDecor.captureTransitioningViews(mEnteringViews);
-                ArrayList<String> sharedElementNames = mActivityOptions.getSharedElementNames();
-                if (sharedElementNames != null) {
-                    mDecor.findSharedElements(mSharedElementTargets);
-                    if (mSharedElementsMap != null) {
-                        for (Map.Entry<String, String> entry : mSharedElementsMap.entrySet()) {
-                            View sharedElement = mSharedElementTargets.remove(entry.getValue());
-                            if (sharedElement != null) {
-                                mSharedElementTargets.put(entry.getKey(), sharedElement);
-                            }
-                        }
-                    }
-                    mSharedElementTargets.keySet().retainAll(sharedElementNames);
-                    mEnteringViews.removeAll(mSharedElementTargets.values());
-                }
+                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);
-                if (mTriggerEarly) {
+                if (mAllowEnterOverlap) {
                     beginEnterScene();
                 }
                 observer.addOnPreDrawListener(this);
+                return false;
             } else {
                 mHandler.postDelayed(this, MAX_TRANSITION_START_WAIT);
                 mActivityOptions.dispatchSceneTransitionStarted(this,
                         new ArrayList<String>(mSharedElementTargets.keySet()));
+                return !mSharedElementReadyReceived;
             }
-            return true;
         }
 
         public void start() {
@@ -4375,43 +4630,48 @@
         }
 
         @Override
-        public void sharedElementTransitionComplete(Bundle transitionArgs) {
+        public void sharedElementTransitionComplete(final Bundle transitionArgs) {
             if (!mSharedElementReadyReceived) {
                 mSharedElementReadyReceived = true;
                 mHandler.removeCallbacks(this);
                 mHandler.postDelayed(this, MAX_TRANSITION_FINISH_WAIT);
                 if (!mSharedElementTargets.isEmpty()) {
-                    Transition transition = getTransitionManager().getEnterTransition(
-                            mContentScene);
-                    if (transition == null) {
-                        transition = TransitionManager.getDefaultTransition();
-                    }
-                    transition = transition.clone();
-                    if (transitionArgs == null) {
-                        TransitionManager.beginDelayedTransition(mDecor, transition);
-                        setViewVisibility(mSharedElementTargets.values(), View.VISIBLE);
-                    } else {
-                        int[] tempLoc = new int[2];
-                        for (Map.Entry<String, View> entry: mSharedElementTargets.entrySet()) {
-                            setSharedElementState(entry.getValue(), entry.getKey(), transitionArgs,
-                                    tempLoc);
+                    runOnUiThread(mHandler, new Runnable() {
+                        @Override
+                        public void run() {
+                            Transition transition = getTransitionManager().getEnterTransition(
+                                    mContentScene);
+                            if (transition == null) {
+                                transition = TransitionManager.getDefaultTransition();
+                            }
+                            transition = addTransitionTargets(transition,
+                                    mSharedElementTargets.values(),
+                                    true);
+                            transition.setEpicenterCallback(mEpicenterCallback);
+                            if (transitionArgs == null) {
+                                TransitionManager.beginDelayedTransition(mDecor, transition);
+                                setViewVisibility(mSharedElementTargets.values(), View.VISIBLE);
+                            } else {
+                                mSceneTransitionListener.sharedElementStart(transition);
+                                setSharedElementState(mSharedElementTargets, transitionArgs);
+                                setViewVisibility(mSharedElementTargets.values(), View.VISIBLE);
+                                mDecor.getViewTreeObserver().addOnPreDrawListener(
+                                        new ViewTreeObserver.OnPreDrawListener() {
+                                            @Override
+                                            public boolean onPreDraw() {
+                                                mDecor.getViewTreeObserver()
+                                                        .removeOnPreDrawListener(this);
+                                                mSceneTransitionListener.sharedElementEnd();
+                                                mActivityOptions.dispatchSharedElementsReady();
+                                                return true;
+                                            }
+                                        });
+                                TransitionManager.beginDelayedTransition(mDecor, transition);
+                            }
                         }
-                        setViewVisibility(mSharedElementTargets.values(), View.VISIBLE);
-                        mSceneTransitionListener.sharedElementStart(transition);
-                        mDecor.getViewTreeObserver().addOnPreDrawListener(
-                                new ViewTreeObserver.OnPreDrawListener() {
-                                    @Override
-                                    public boolean onPreDraw() {
-                                        mDecor.getViewTreeObserver().removeOnPreDrawListener(this);
-                                        mSceneTransitionListener.sharedElementEnd();
-                                        mActivityOptions.dispatchSharedElementsReady();
-                                        return true;
-                                    }
-                                });
-                        TransitionManager.beginDelayedTransition(mDecor, transition);
-                    }
+                    });
                 }
-                if (mTriggerEarly) {
+                if (mAllowEnterOverlap) {
                     fadeInBackground();
                 }
             }
@@ -4436,9 +4696,14 @@
             mAllDone = true;
             sharedElementTransitionComplete(null);
             mHandler.removeCallbacks(this);
-            if (!mTriggerEarly) {
-                beginEnterScene();
-                fadeInBackground();
+            if (!mAllowEnterOverlap) {
+                runOnUiThread(mHandler, new Runnable() {
+                    @Override
+                    public void run() {
+                        beginEnterScene();
+                        fadeInBackground();
+                    }
+                });
             }
         }
 
@@ -4462,10 +4727,25 @@
         private void beginEnterScene() {
             Transition transition = getTransitionManager().getEnterTransition(mContentScene);
             if (transition == null) {
-                transition = TransitionManager.getDefaultTransition().clone();
+                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;
+        }
+    };
 }
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 57c2f92..0b688b6 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -4690,7 +4690,11 @@
 
     // ----- Restore handling -----
 
-    private boolean signaturesMatch(Signature[] storedSigs, PackageInfo target) {
+    static boolean signaturesMatch(Signature[] storedSigs, PackageInfo target) {
+        if (target == null) {
+            return false;
+        }
+
         // If the target resides on the system partition, we allow it to restore
         // data from the like-named package in a restore set even if the signatures
         // do not match.  (Unlike general applications, those flashed to the system
diff --git a/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java b/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
index 495da88..39f2441 100644
--- a/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
+++ b/services/backup/java/com/android/server/backup/PackageManagerBackupAgent.java
@@ -19,15 +19,19 @@
 import android.app.backup.BackupAgent;
 import android.app.backup.BackupDataInput;
 import android.app.backup.BackupDataOutput;
+import android.content.ComponentName;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.Signature;
 import android.os.Build;
 import android.os.ParcelFileDescriptor;
 import android.util.Slog;
 
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.DataInputStream;
@@ -42,6 +46,8 @@
 import java.util.List;
 import java.util.Set;
 
+import java.util.Objects;
+
 /**
  * We back up the signatures of each package so that during a system restore,
  * we can verify that the app whose data we think we have matches the app
@@ -58,6 +64,9 @@
     // is stored using the package name as a key)
     private static final String GLOBAL_METADATA_KEY = "@meta@";
 
+    // key under which we store the identity of the user's chosen default home app
+    private static final String DEFAULT_HOME_KEY = "@home@";
+
     private List<PackageInfo> mAllPackages;
     private PackageManager mPackageManager;
     // version & signature info of each app in a restore set
@@ -68,7 +77,15 @@
     private final HashSet<String> mExisting = new HashSet<String>();
     private int mStoredSdkVersion;
     private String mStoredIncrementalVersion;
+    private ComponentName mStoredHomeComponent;
+    private long mStoredHomeVersion;
+    private Signature[] mStoredHomeSigs;
+
     private boolean mHasMetadata;
+    private ComponentName mRestoredHome;
+    private long mRestoredHomeVersion;
+    private String mRestoredHomeInstaller;
+    private Signature[] mRestoredHomeSignatures;
 
     public class Metadata {
         public int versionCode;
@@ -136,7 +153,50 @@
             mExisting.clear();
         }
 
+        long homeVersion = 0;
+        Signature[] homeSigs = null;
+        PackageInfo homeInfo = null;
+        String homeInstaller = null;
+        ComponentName home = getPreferredHomeComponent();
+        if (home != null) {
+            try {
+                homeInfo = mPackageManager.getPackageInfo(home.getPackageName(),
+                        PackageManager.GET_SIGNATURES);
+                homeInstaller = mPackageManager.getInstallerPackageName(home.getPackageName());
+                homeVersion = homeInfo.versionCode;
+                homeSigs = homeInfo.signatures;
+            } catch (NameNotFoundException e) {
+                Slog.w(TAG, "Can't access preferred home info");
+                // proceed as though there were no preferred home set
+                home = null;
+            }
+        }
+
         try {
+            // We need to push a new preferred-home-app record if:
+            //    1. the version of the home app has changed since our last backup;
+            //    2. the home app [or absence] we now use differs from the prior state,
+            // OR 3. it looks like we use the same home app + version as before, but
+            //       the signatures don't match so we treat them as different apps.
+            final boolean needHomeBackup = (homeVersion != mStoredHomeVersion)
+                    || Objects.equals(home, mStoredHomeComponent)
+                    || (home != null
+                        && !BackupManagerService.signaturesMatch(mStoredHomeSigs, homeInfo));
+            if (needHomeBackup) {
+                if (DEBUG) {
+                    Slog.i(TAG, "Home preference changed; backing up new state " + home);
+                }
+                if (home != null) {
+                    outputBufferStream.writeUTF(home.flattenToString());
+                    outputBufferStream.writeLong(homeVersion);
+                    outputBufferStream.writeUTF(homeInstaller);
+                    writeSignatureArray(outputBufferStream, homeSigs);
+                    writeEntity(data, DEFAULT_HOME_KEY, outputBuffer.toByteArray());
+                } else {
+                    data.writeEntityHeader(DEFAULT_HOME_KEY, -1);
+                }
+            }
+
             /*
              * Global metadata:
              *
@@ -146,6 +206,7 @@
              * String incremental -- the incremental release name of the OS stored in
              *                       the backup set.
              */
+            outputBuffer.reset();
             if (!mExisting.contains(GLOBAL_METADATA_KEY)) {
                 if (DEBUG) Slog.v(TAG, "Storing global metadata key");
                 outputBufferStream.writeInt(Build.VERSION.SDK_INT);
@@ -238,7 +299,7 @@
         }
 
         // Finally, write the new state blob -- just the list of all apps we handled
-        writeStateFile(mAllPackages, newState);
+        writeStateFile(mAllPackages, home, homeVersion, homeSigs, newState);
     }
     
     private static void writeEntity(BackupDataOutput data, String key, byte[] bytes)
@@ -286,6 +347,19 @@
                             + " (" + mStoredIncrementalVersion + " vs "
                             + Build.VERSION.INCREMENTAL + ")");
                 }
+            } else if (key.equals(DEFAULT_HOME_KEY)) {
+                String cn = inputBufferStream.readUTF();
+                mRestoredHome = ComponentName.unflattenFromString(cn);
+                mRestoredHomeVersion = inputBufferStream.readLong();
+                mRestoredHomeInstaller = inputBufferStream.readUTF();
+                mRestoredHomeSignatures = readSignatureArray(inputBufferStream);
+                if (DEBUG) {
+                    Slog.i(TAG, "   read preferred home app " + mRestoredHome
+                            + " version=" + mRestoredHomeVersion
+                            + " installer=" + mRestoredHomeVersion
+                            + " sig=" + mRestoredHomeVersion);
+                }
+
             } else {
                 // it's a file metadata record
                 int versionCode = inputBufferStream.readInt();
@@ -365,18 +439,34 @@
         mStateVersions.clear();
         mStoredSdkVersion = 0;
         mStoredIncrementalVersion = null;
+        mStoredHomeComponent = null;
+        mStoredHomeVersion = 0;
+        mStoredHomeSigs = null;
 
         // The state file is just the list of app names we have stored signatures for
         // with the exception of the metadata block, to which is also appended the
         // version numbers corresponding with the last time we wrote this PM block.
         // If they mismatch the current system, we'll re-store the metadata key.
         FileInputStream instream = new FileInputStream(stateFile.getFileDescriptor());
-        DataInputStream in = new DataInputStream(instream);
+        BufferedInputStream inbuffer = new BufferedInputStream(instream);
+        DataInputStream in = new DataInputStream(inbuffer);
 
-        int bufSize = 256;
-        byte[] buf = new byte[bufSize];
         try {
             String pkg = in.readUTF();
+
+            // First comes the preferred home app data, if any, headed by the DEFAULT_HOME_KEY tag
+            if (pkg.equals(DEFAULT_HOME_KEY)) {
+                // flattened component name, version, signature of the home app
+                mStoredHomeComponent = ComponentName.unflattenFromString(in.readUTF());
+                mStoredHomeVersion = in.readLong();
+                mStoredHomeSigs = readSignatureArray(in);
+
+                pkg = in.readUTF(); // set up for the next block of state
+            } else {
+                // else no preferred home app on the ancestral device - fall through to the rest
+            }
+
+            // After (possible) home app data comes the global metadata block
             if (pkg.equals(GLOBAL_METADATA_KEY)) {
                 mStoredSdkVersion = in.readInt();
                 mStoredIncrementalVersion = in.readUTF();
@@ -386,7 +476,7 @@
                 return;
             }
 
-            // The global metadata was first; now read all the apps
+            // The global metadata was last; now read all the apps
             while (true) {
                 pkg = in.readUTF();
                 int versionCode = in.readInt();
@@ -401,13 +491,28 @@
         }
     }
 
-    // Util: write out our new backup state file
-    private void writeStateFile(List<PackageInfo> pkgs, ParcelFileDescriptor stateFile) {
-        FileOutputStream outstream = new FileOutputStream(stateFile.getFileDescriptor());
-        DataOutputStream out = new DataOutputStream(outstream);
+    private ComponentName getPreferredHomeComponent() {
+        return mPackageManager.getHomeActivities(new ArrayList<ResolveInfo>());
+    }
 
+    // Util: write out our new backup state file
+    private void writeStateFile(List<PackageInfo> pkgs, ComponentName preferredHome,
+            long homeVersion, Signature[] homeSignatures, ParcelFileDescriptor stateFile) {
+        FileOutputStream outstream = new FileOutputStream(stateFile.getFileDescriptor());
+        BufferedOutputStream outbuf = new BufferedOutputStream(outstream);
+        DataOutputStream out = new DataOutputStream(outbuf);
+
+        // by the time we get here we know we've done all our backing up
         try {
-            // by the time we get here we know we've stored the global metadata record
+            // If we remembered a preferred home app, record that
+            if (preferredHome != null) {
+                out.writeUTF(DEFAULT_HOME_KEY);
+                out.writeUTF(preferredHome.flattenToString());
+                out.writeLong(homeVersion);
+                writeSignatureArray(out, homeSignatures);
+            }
+
+            // Conclude with the metadata block
             out.writeUTF(GLOBAL_METADATA_KEY);
             out.writeInt(Build.VERSION.SDK_INT);
             out.writeUTF(Build.VERSION.INCREMENTAL);
@@ -417,9 +522,10 @@
                 out.writeUTF(pkg.packageName);
                 out.writeInt(pkg.versionCode);
             }
+
+            out.flush();
         } catch (IOException e) {
             Slog.e(TAG, "Unable to write package manager state file!");
-            return;
         }
     }
 }
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java
index 3414b3e..b9f2ed9 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgePowerManager.java
@@ -51,6 +51,11 @@
     }
 
     @Override
+    public void powerHint(int hintId, int data) {
+        // pass for now.
+    }
+
+    @Override
     public void crash(String arg0) throws RemoteException {
         // pass for now.
     }