Allow antroid.transition Transitions in fragments.

Bug 15274281
Bug 15189829

Change-Id: I8e2974430b84a611866fe20afe1f5745e803683f
diff --git a/core/java/android/app/ActivityTransitionCoordinator.java b/core/java/android/app/ActivityTransitionCoordinator.java
index c910aca..c7030b0 100644
--- a/core/java/android/app/ActivityTransitionCoordinator.java
+++ b/core/java/android/app/ActivityTransitionCoordinator.java
@@ -293,13 +293,17 @@
         if (transition == null || views == null || views.isEmpty()) {
             return null;
         }
+        // Add the targets to a set containing transition so that transition
+        // remains unaffected. We don't want to modify the targets of transition itself.
         TransitionSet set = new TransitionSet();
-        set.addTransition(transition);
         if (views != null) {
-            for (View view: views) {
+            for (View view : views) {
                 set.addTarget(view);
             }
         }
+        // By adding the transition after addTarget, we prevent addTarget from
+        // affecting transition.
+        set.addTransition(transition);
         return set;
     }
 
diff --git a/core/java/android/app/BackStackRecord.java b/core/java/android/app/BackStackRecord.java
index 89ee145..01a388f 100644
--- a/core/java/android/app/BackStackRecord.java
+++ b/core/java/android/app/BackStackRecord.java
@@ -16,36 +16,53 @@
 
 package android.app;
 
+import com.android.internal.util.FastPrintWriter;
+
+import android.graphics.Rect;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
+import android.transition.Transition;
+import android.transition.TransitionInflater;
+import android.transition.TransitionManager;
+import android.transition.TransitionSet;
+import android.util.ArrayMap;
 import android.util.Log;
 import android.util.LogWriter;
-import com.android.internal.util.FastPrintWriter;
+import android.util.Pair;
+import android.view.View;
+import android.view.ViewGroup;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Collection;
 
 final class BackStackState implements Parcelable {
     final int[] mOps;
     final int mTransition;
     final int mTransitionStyle;
+    final int mCustomTransition;
+    final int mSceneRoot;
     final String mName;
     final int mIndex;
     final int mBreadCrumbTitleRes;
     final CharSequence mBreadCrumbTitleText;
     final int mBreadCrumbShortTitleRes;
     final CharSequence mBreadCrumbShortTitleText;
+    final ArrayList<String> mSharedElementSourceNames;
+    final ArrayList<String> mSharedElementTargetNames;
 
     public BackStackState(FragmentManagerImpl fm, BackStackRecord bse) {
         int numRemoved = 0;
         BackStackRecord.Op op = bse.mHead;
         while (op != null) {
-            if (op.removed != null) numRemoved += op.removed.size();
+            if (op.removed != null) {
+                numRemoved += op.removed.size();
+            }
             op = op.next;
         }
-        mOps = new int[bse.mNumOp*7 + numRemoved];
+        mOps = new int[bse.mNumOp * 7 + numRemoved];
 
         if (!bse.mAddToBackStack) {
             throw new IllegalStateException("Not on back stack");
@@ -63,7 +80,7 @@
             if (op.removed != null) {
                 final int N = op.removed.size();
                 mOps[pos++] = N;
-                for (int i=0; i<N; i++) {
+                for (int i = 0; i < N; i++) {
                     mOps[pos++] = op.removed.get(i).mIndex;
                 }
             } else {
@@ -79,6 +96,10 @@
         mBreadCrumbTitleText = bse.mBreadCrumbTitleText;
         mBreadCrumbShortTitleRes = bse.mBreadCrumbShortTitleRes;
         mBreadCrumbShortTitleText = bse.mBreadCrumbShortTitleText;
+        mCustomTransition = bse.mCustomTransition;
+        mSceneRoot = bse.mSceneRoot;
+        mSharedElementSourceNames = bse.mSharedElementSourceNames;
+        mSharedElementTargetNames = bse.mSharedElementTargetNames;
     }
 
     public BackStackState(Parcel in) {
@@ -91,6 +112,10 @@
         mBreadCrumbTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
         mBreadCrumbShortTitleRes = in.readInt();
         mBreadCrumbShortTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+        mCustomTransition = in.readInt();
+        mSceneRoot = in.readInt();
+        mSharedElementSourceNames = in.createStringArrayList();
+        mSharedElementTargetNames = in.createStringArrayList();
     }
 
     public BackStackRecord instantiate(FragmentManagerImpl fm) {
@@ -100,8 +125,10 @@
         while (pos < mOps.length) {
             BackStackRecord.Op op = new BackStackRecord.Op();
             op.cmd = mOps[pos++];
-            if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG,
-                    "Instantiate " + bse + " op #" + num + " base fragment #" + mOps[pos]);
+            if (FragmentManagerImpl.DEBUG) {
+                Log.v(FragmentManagerImpl.TAG,
+                        "Instantiate " + bse + " op #" + num + " base fragment #" + mOps[pos]);
+            }
             int findex = mOps[pos++];
             if (findex >= 0) {
                 Fragment f = fm.mActive.get(findex);
@@ -116,9 +143,11 @@
             final int N = mOps[pos++];
             if (N > 0) {
                 op.removed = new ArrayList<Fragment>(N);
-                for (int i=0; i<N; i++) {
-                    if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG,
-                            "Instantiate " + bse + " set remove fragment #" + mOps[pos]);
+                for (int i = 0; i < N; i++) {
+                    if (FragmentManagerImpl.DEBUG) {
+                        Log.v(FragmentManagerImpl.TAG,
+                                "Instantiate " + bse + " set remove fragment #" + mOps[pos]);
+                    }
                     Fragment r = fm.mActive.get(mOps[pos++]);
                     op.removed.add(r);
                 }
@@ -135,6 +164,10 @@
         bse.mBreadCrumbTitleText = mBreadCrumbTitleText;
         bse.mBreadCrumbShortTitleRes = mBreadCrumbShortTitleRes;
         bse.mBreadCrumbShortTitleText = mBreadCrumbShortTitleText;
+        bse.mCustomTransition = mCustomTransition;
+        bse.mSceneRoot = mSceneRoot;
+        bse.mSharedElementSourceNames = mSharedElementSourceNames;
+        bse.mSharedElementTargetNames = mSharedElementTargetNames;
         bse.bumpBackStackNesting(1);
         return bse;
     }
@@ -153,6 +186,10 @@
         TextUtils.writeToParcel(mBreadCrumbTitleText, dest, 0);
         dest.writeInt(mBreadCrumbShortTitleRes);
         TextUtils.writeToParcel(mBreadCrumbShortTitleText, dest, 0);
+        dest.writeInt(mCustomTransition);
+        dest.writeInt(mSceneRoot);
+        dest.writeStringList(mSharedElementSourceNames);
+        dest.writeStringList(mSharedElementTargetNames);
     }
 
     public static final Parcelable.Creator<BackStackState> CREATOR
@@ -217,6 +254,11 @@
     int mBreadCrumbShortTitleRes;
     CharSequence mBreadCrumbShortTitleText;
 
+    int mCustomTransition;
+    int mSceneRoot;
+    ArrayList<String> mSharedElementSourceNames;
+    ArrayList<String> mSharedElementTargetNames;
+
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder(128);
@@ -240,78 +282,112 @@
 
     void dump(String prefix, PrintWriter writer, boolean full) {
         if (full) {
-            writer.print(prefix); writer.print("mName="); writer.print(mName);
-                    writer.print(" mIndex="); writer.print(mIndex);
-                    writer.print(" mCommitted="); writer.println(mCommitted);
+            writer.print(prefix);
+            writer.print("mName=");
+            writer.print(mName);
+            writer.print(" mIndex=");
+            writer.print(mIndex);
+            writer.print(" mCommitted=");
+            writer.println(mCommitted);
             if (mTransition != FragmentTransaction.TRANSIT_NONE) {
-                writer.print(prefix); writer.print("mTransition=#");
-                        writer.print(Integer.toHexString(mTransition));
-                        writer.print(" mTransitionStyle=#");
-                        writer.println(Integer.toHexString(mTransitionStyle));
+                writer.print(prefix);
+                writer.print("mTransition=#");
+                writer.print(Integer.toHexString(mTransition));
+                writer.print(" mTransitionStyle=#");
+                writer.println(Integer.toHexString(mTransitionStyle));
             }
-            if (mEnterAnim != 0 || mExitAnim !=0) {
-                writer.print(prefix); writer.print("mEnterAnim=#");
-                        writer.print(Integer.toHexString(mEnterAnim));
-                        writer.print(" mExitAnim=#");
-                        writer.println(Integer.toHexString(mExitAnim));
+            if (mEnterAnim != 0 || mExitAnim != 0) {
+                writer.print(prefix);
+                writer.print("mEnterAnim=#");
+                writer.print(Integer.toHexString(mEnterAnim));
+                writer.print(" mExitAnim=#");
+                writer.println(Integer.toHexString(mExitAnim));
             }
-            if (mPopEnterAnim != 0 || mPopExitAnim !=0) {
-                writer.print(prefix); writer.print("mPopEnterAnim=#");
-                        writer.print(Integer.toHexString(mPopEnterAnim));
-                        writer.print(" mPopExitAnim=#");
-                        writer.println(Integer.toHexString(mPopExitAnim));
+            if (mPopEnterAnim != 0 || mPopExitAnim != 0) {
+                writer.print(prefix);
+                writer.print("mPopEnterAnim=#");
+                writer.print(Integer.toHexString(mPopEnterAnim));
+                writer.print(" mPopExitAnim=#");
+                writer.println(Integer.toHexString(mPopExitAnim));
             }
             if (mBreadCrumbTitleRes != 0 || mBreadCrumbTitleText != null) {
-                writer.print(prefix); writer.print("mBreadCrumbTitleRes=#");
-                        writer.print(Integer.toHexString(mBreadCrumbTitleRes));
-                        writer.print(" mBreadCrumbTitleText=");
-                        writer.println(mBreadCrumbTitleText);
+                writer.print(prefix);
+                writer.print("mBreadCrumbTitleRes=#");
+                writer.print(Integer.toHexString(mBreadCrumbTitleRes));
+                writer.print(" mBreadCrumbTitleText=");
+                writer.println(mBreadCrumbTitleText);
             }
             if (mBreadCrumbShortTitleRes != 0 || mBreadCrumbShortTitleText != null) {
-                writer.print(prefix); writer.print("mBreadCrumbShortTitleRes=#");
-                        writer.print(Integer.toHexString(mBreadCrumbShortTitleRes));
-                        writer.print(" mBreadCrumbShortTitleText=");
-                        writer.println(mBreadCrumbShortTitleText);
+                writer.print(prefix);
+                writer.print("mBreadCrumbShortTitleRes=#");
+                writer.print(Integer.toHexString(mBreadCrumbShortTitleRes));
+                writer.print(" mBreadCrumbShortTitleText=");
+                writer.println(mBreadCrumbShortTitleText);
             }
         }
 
         if (mHead != null) {
-            writer.print(prefix); writer.println("Operations:");
+            writer.print(prefix);
+            writer.println("Operations:");
             String innerPrefix = prefix + "    ";
             Op op = mHead;
             int num = 0;
             while (op != null) {
                 String cmdStr;
                 switch (op.cmd) {
-                    case OP_NULL: cmdStr="NULL"; break;
-                    case OP_ADD: cmdStr="ADD"; break;
-                    case OP_REPLACE: cmdStr="REPLACE"; break;
-                    case OP_REMOVE: cmdStr="REMOVE"; break;
-                    case OP_HIDE: cmdStr="HIDE"; break;
-                    case OP_SHOW: cmdStr="SHOW"; break;
-                    case OP_DETACH: cmdStr="DETACH"; break;
-                    case OP_ATTACH: cmdStr="ATTACH"; break;
-                    default: cmdStr="cmd=" + op.cmd; break;
+                    case OP_NULL:
+                        cmdStr = "NULL";
+                        break;
+                    case OP_ADD:
+                        cmdStr = "ADD";
+                        break;
+                    case OP_REPLACE:
+                        cmdStr = "REPLACE";
+                        break;
+                    case OP_REMOVE:
+                        cmdStr = "REMOVE";
+                        break;
+                    case OP_HIDE:
+                        cmdStr = "HIDE";
+                        break;
+                    case OP_SHOW:
+                        cmdStr = "SHOW";
+                        break;
+                    case OP_DETACH:
+                        cmdStr = "DETACH";
+                        break;
+                    case OP_ATTACH:
+                        cmdStr = "ATTACH";
+                        break;
+                    default:
+                        cmdStr = "cmd=" + op.cmd;
+                        break;
                 }
-                writer.print(prefix); writer.print("  Op #"); writer.print(num);
-                        writer.print(": "); writer.print(cmdStr);
-                        writer.print(" "); writer.println(op.fragment);
+                writer.print(prefix);
+                writer.print("  Op #");
+                writer.print(num);
+                writer.print(": ");
+                writer.print(cmdStr);
+                writer.print(" ");
+                writer.println(op.fragment);
                 if (full) {
                     if (op.enterAnim != 0 || op.exitAnim != 0) {
-                        writer.print(innerPrefix); writer.print("enterAnim=#");
-                                writer.print(Integer.toHexString(op.enterAnim));
-                                writer.print(" exitAnim=#");
-                                writer.println(Integer.toHexString(op.exitAnim));
+                        writer.print(innerPrefix);
+                        writer.print("enterAnim=#");
+                        writer.print(Integer.toHexString(op.enterAnim));
+                        writer.print(" exitAnim=#");
+                        writer.println(Integer.toHexString(op.exitAnim));
                     }
                     if (op.popEnterAnim != 0 || op.popExitAnim != 0) {
-                        writer.print(innerPrefix); writer.print("popEnterAnim=#");
-                                writer.print(Integer.toHexString(op.popEnterAnim));
-                                writer.print(" popExitAnim=#");
-                                writer.println(Integer.toHexString(op.popExitAnim));
+                        writer.print(innerPrefix);
+                        writer.print("popEnterAnim=#");
+                        writer.print(Integer.toHexString(op.popEnterAnim));
+                        writer.print(" popExitAnim=#");
+                        writer.println(Integer.toHexString(op.popExitAnim));
                     }
                 }
                 if (op.removed != null && op.removed.size() > 0) {
-                    for (int i=0; i<op.removed.size(); i++) {
+                    for (int i = 0; i < op.removed.size(); i++) {
                         writer.print(innerPrefix);
                         if (op.removed.size() == 1) {
                             writer.print("Removed: ");
@@ -319,8 +395,10 @@
                             if (i == 0) {
                                 writer.println("Removed:");
                             }
-                            writer.print(innerPrefix); writer.print("  #"); writer.print(i);
-                                    writer.print(": "); 
+                            writer.print(innerPrefix);
+                            writer.print("  #");
+                            writer.print(i);
+                            writer.print(": ");
                         }
                         writer.println(op.removed.get(i));
                     }
@@ -494,6 +572,51 @@
         return this;
     }
 
+    @Override
+    public FragmentTransaction setCustomTransition(int sceneRootId, int transitionId) {
+        mSceneRoot = sceneRootId;
+        mCustomTransition = transitionId;
+        return this;
+    }
+
+    @Override
+    public FragmentTransaction setSharedElement(View sharedElement, String name) {
+        String viewName = sharedElement.getViewName();
+        if (viewName == null) {
+            throw new IllegalArgumentException("Unique viewNames are required for all" +
+                    " sharedElements");
+        }
+        mSharedElementSourceNames = new ArrayList<String>(1);
+        mSharedElementSourceNames.add(viewName);
+
+        mSharedElementTargetNames = new ArrayList<String>(1);
+        mSharedElementTargetNames.add(name);
+        return this;
+    }
+
+    @Override
+    public FragmentTransaction setSharedElements(Pair<View, String>... sharedElements) {
+        if (sharedElements == null || sharedElements.length == 0) {
+            mSharedElementSourceNames = null;
+            mSharedElementTargetNames = null;
+        } else {
+            ArrayList<String> sourceNames = new ArrayList<String>(sharedElements.length);
+            ArrayList<String> targetNames = new ArrayList<String>(sharedElements.length);
+            for (int i = 0; i < sharedElements.length; i++) {
+                String viewName = sharedElements[i].first.getViewName();
+                if (viewName == null) {
+                    throw new IllegalArgumentException("Unique viewNames are required for all" +
+                            " sharedElements");
+                }
+                sourceNames.add(viewName);
+                targetNames.add(sharedElements[i].second);
+            }
+            mSharedElementSourceNames = sourceNames;
+            mSharedElementTargetNames = targetNames;
+        }
+        return this;
+    }
+
     public FragmentTransaction setTransitionStyle(int styleRes) {
         mTransitionStyle = styleRes;
         return this;
@@ -550,21 +673,27 @@
         if (!mAddToBackStack) {
             return;
         }
-        if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting in " + this
-                + " by " + amt);
+        if (FragmentManagerImpl.DEBUG) {
+            Log.v(TAG, "Bump nesting in " + this
+                    + " by " + amt);
+        }
         Op op = mHead;
         while (op != null) {
             if (op.fragment != null) {
                 op.fragment.mBackStackNesting += amt;
-                if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting of "
-                        + op.fragment + " to " + op.fragment.mBackStackNesting);
+                if (FragmentManagerImpl.DEBUG) {
+                    Log.v(TAG, "Bump nesting of "
+                            + op.fragment + " to " + op.fragment.mBackStackNesting);
+                }
             }
             if (op.removed != null) {
-                for (int i=op.removed.size()-1; i>=0; i--) {
+                for (int i = op.removed.size() - 1; i >= 0; i--) {
                     Fragment r = op.removed.get(i);
                     r.mBackStackNesting += amt;
-                    if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting of "
-                            + r + " to " + r.mBackStackNesting);
+                    if (FragmentManagerImpl.DEBUG) {
+                        Log.v(TAG, "Bump nesting of "
+                                + r + " to " + r.mBackStackNesting);
+                    }
                 }
             }
             op = op.next;
@@ -578,9 +707,11 @@
     public int commitAllowingStateLoss() {
         return commitInternal(true);
     }
-    
+
     int commitInternal(boolean allowStateLoss) {
-        if (mCommitted) throw new IllegalStateException("commit already called");
+        if (mCommitted) {
+            throw new IllegalStateException("commit already called");
+        }
         if (FragmentManagerImpl.DEBUG) {
             Log.v(TAG, "Commit: " + this);
             LogWriter logw = new LogWriter(Log.VERBOSE, TAG);
@@ -597,9 +728,11 @@
         mManager.enqueueAction(this, allowStateLoss);
         return mIndex;
     }
-    
+
     public void run() {
-        if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Run: " + this);
+        if (FragmentManagerImpl.DEBUG) {
+            Log.v(TAG, "Run: " + this);
+        }
 
         if (mAddToBackStack) {
             if (mIndex < 0) {
@@ -609,6 +742,9 @@
 
         bumpBackStackNesting(1);
 
+        TransitionState state = beginTransition(mSharedElementSourceNames,
+                mSharedElementTargetNames);
+
         Op op = mHead;
         while (op != null) {
             switch (op.cmd) {
@@ -616,14 +752,17 @@
                     Fragment f = op.fragment;
                     f.mNextAnim = op.enterAnim;
                     mManager.addFragment(f, false);
-                } break;
+                }
+                break;
                 case OP_REPLACE: {
                     Fragment f = op.fragment;
                     if (mManager.mAdded != null) {
-                        for (int i=0; i<mManager.mAdded.size(); i++) {
+                        for (int i = 0; i < mManager.mAdded.size(); i++) {
                             Fragment old = mManager.mAdded.get(i);
-                            if (FragmentManagerImpl.DEBUG) Log.v(TAG,
-                                    "OP_REPLACE: adding=" + f + " old=" + old);
+                            if (FragmentManagerImpl.DEBUG) {
+                                Log.v(TAG,
+                                        "OP_REPLACE: adding=" + f + " old=" + old);
+                            }
                             if (f == null || old.mContainerId == f.mContainerId) {
                                 if (old == f) {
                                     op.fragment = f = null;
@@ -635,8 +774,10 @@
                                     old.mNextAnim = op.exitAnim;
                                     if (mAddToBackStack) {
                                         old.mBackStackNesting += 1;
-                                        if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting of "
-                                                + old + " to " + old.mBackStackNesting);
+                                        if (FragmentManagerImpl.DEBUG) {
+                                            Log.v(TAG, "Bump nesting of "
+                                                    + old + " to " + old.mBackStackNesting);
+                                        }
                                     }
                                     mManager.removeFragment(old, mTransition, mTransitionStyle);
                                 }
@@ -647,32 +788,38 @@
                         f.mNextAnim = op.enterAnim;
                         mManager.addFragment(f, false);
                     }
-                } break;
+                }
+                break;
                 case OP_REMOVE: {
                     Fragment f = op.fragment;
                     f.mNextAnim = op.exitAnim;
                     mManager.removeFragment(f, mTransition, mTransitionStyle);
-                } break;
+                }
+                break;
                 case OP_HIDE: {
                     Fragment f = op.fragment;
                     f.mNextAnim = op.exitAnim;
                     mManager.hideFragment(f, mTransition, mTransitionStyle);
-                } break;
+                }
+                break;
                 case OP_SHOW: {
                     Fragment f = op.fragment;
                     f.mNextAnim = op.enterAnim;
                     mManager.showFragment(f, mTransition, mTransitionStyle);
-                } break;
+                }
+                break;
                 case OP_DETACH: {
                     Fragment f = op.fragment;
                     f.mNextAnim = op.exitAnim;
                     mManager.detachFragment(f, mTransition, mTransitionStyle);
-                } break;
+                }
+                break;
                 case OP_ATTACH: {
                     Fragment f = op.fragment;
                     f.mNextAnim = op.enterAnim;
                     mManager.attachFragment(f, mTransition, mTransitionStyle);
-                } break;
+                }
+                break;
                 default: {
                     throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
                 }
@@ -687,9 +834,174 @@
         if (mAddToBackStack) {
             mManager.addBackStackState(this);
         }
+
+        if (state != null) {
+            updateTransitionEndState(state, mSharedElementTargetNames);
+        }
     }
 
-    public void popFromBackStack(boolean doStateMove) {
+    private TransitionState beginTransition(ArrayList<String> sourceNames,
+            ArrayList<String> targetNames) {
+        if (mCustomTransition <= 0 || mSceneRoot <= 0) {
+            return null;
+        }
+        View rootView = mManager.mContainer.findViewById(mSceneRoot);
+        if (!(rootView instanceof ViewGroup)) {
+            throw new IllegalArgumentException("SceneRoot is not a ViewGroup");
+        }
+        TransitionState state = new TransitionState();
+        // get Transition scene root and create Transitions
+        state.sceneRoot = (ViewGroup) rootView;
+        state.sceneRoot.captureTransitioningViews(state.transitioningViews);
+
+        state.exitTransition = TransitionInflater.from(mManager.mActivity)
+                .inflateTransition(mCustomTransition);
+        state.sharedElementTransition = TransitionInflater.from(mManager.mActivity)
+                .inflateTransition(mCustomTransition);
+        state.enterTransition = TransitionInflater.from(mManager.mActivity)
+                .inflateTransition(mCustomTransition);
+        // Adding a non-existent target view makes sure that the transitions don't target
+        // any views by default. They'll only target the views we tell add. If we don't
+        // add any, then no views will be targeted.
+        View nonExistentView = new View(mManager.mActivity);
+        state.enterTransition.addTarget(nonExistentView);
+        state.exitTransition.addTarget(nonExistentView);
+        state.sharedElementTransition.addTarget(nonExistentView);
+
+        setSharedElementEpicenter(state.enterTransition, state);
+
+        state.excludingTransition = new TransitionSet()
+                .addTransition(state.exitTransition)
+                .addTransition(state.enterTransition);
+
+        if (sourceNames != null) {
+            // Map shared elements.
+            state.sceneRoot.findNamedViews(state.namedViews);
+            state.namedViews.retainAll(sourceNames);
+            View epicenterView = state.namedViews.get(sourceNames.get(0));
+            if (epicenterView != null) {
+                // The epicenter is only the first shared element.
+                setEpicenter(state.exitTransition, epicenterView);
+                setEpicenter(state.sharedElementTransition, epicenterView);
+            }
+            state.transitioningViews.removeAll(state.namedViews.values());
+            state.excludingTransition.addTransition(state.sharedElementTransition);
+            addTransitioningViews(state.sharedElementTransition, state.namedViews.values());
+        }
+
+        // Adds the (maybe) exiting views, not including the shared element.
+        // If some stay, that's ok.
+        addTransitioningViews(state.exitTransition, state.transitioningViews);
+
+        // Prepare for shared element name mapping. This could be chained in the case
+        // of popping several back stack states.
+        state.excludingTransition.setNameOverrides(new ArrayMap<String, String>());
+        setNameOverrides(state, sourceNames, targetNames);
+
+        // Don't include any subtree in the views that are hidden when capturing the
+        // view hierarchy transitions. They should be as if not there.
+        excludeHiddenFragments(state, true);
+
+        TransitionManager.beginDelayedTransition(state.sceneRoot, state.excludingTransition);
+        return state;
+    }
+
+    private void updateTransitionEndState(TransitionState state, ArrayList<String> names) {
+        // Find all views that are entering.
+        ArrayList<View> enteringViews = new ArrayList<View>();
+        state.sceneRoot.captureTransitioningViews(enteringViews);
+        enteringViews.removeAll(state.transitioningViews);
+
+        if (names != null) {
+            // find all shared elements.
+            state.namedViews.clear();
+            state.sceneRoot.findNamedViews(state.namedViews);
+            state.namedViews.retainAll(names);
+            if (!state.namedViews.isEmpty()) {
+                enteringViews.removeAll(state.namedViews.values());
+                addTransitioningViews(state.sharedElementTransition, state.namedViews.values());
+                // now we know the epicenter of the entering transition.
+                state.mEnteringEpicenterView = state.namedViews.get(names.get(0));
+            }
+        }
+
+        // Add all entering views to the enter transition.
+        addTransitioningViews(state.enterTransition, enteringViews);
+
+        // Don't allow capturing state for the newly-hidden fragments.
+        excludeHiddenFragments(state, false);
+
+        // Allow capturing state for the newly-shown fragments
+        includeVisibleFragments(state.excludingTransition);
+    }
+
+    private void addTransitioningViews(Transition transition, Collection<View> views) {
+        if (views.isEmpty()) {
+            // Add a view so that we can modify the valid views at the end of the
+            // fragment transaction.
+            transition.addTarget(new View(mManager.mActivity));
+        } else {
+            for (View view : views) {
+                transition.addTarget(view);
+            }
+        }
+    }
+
+    private void excludeHiddenFragments(TransitionState state, boolean forceExclude) {
+        if (mManager.mAdded != null) {
+            for (int i = 0; i < mManager.mAdded.size(); i++) {
+                Fragment fragment = mManager.mAdded.get(i);
+                if (fragment.mView != null && fragment.mHidden
+                        && (forceExclude || !state.hiddenViews.contains(fragment.mView))) {
+                    state.excludingTransition.excludeTarget(fragment.mView, true);
+                    state.hiddenViews.add(fragment.mView);
+                }
+            }
+        }
+        if (forceExclude && state.hiddenViews.isEmpty()) {
+            state.excludingTransition.excludeTarget(new View(mManager.mActivity), true);
+        }
+    }
+
+    private void includeVisibleFragments(Transition transition) {
+        if (mManager.mAdded != null) {
+            for (int i = 0; i < mManager.mAdded.size(); i++) {
+                Fragment fragment = mManager.mAdded.get(i);
+                if (fragment.mView != null && !fragment.mHidden) {
+                    transition.excludeTarget(fragment.mView, false);
+                }
+            }
+        }
+    }
+
+    private static void setEpicenter(Transition transition, View view) {
+        final Rect epicenter = new Rect();
+        view.getBoundsOnScreen(epicenter);
+
+        transition.setEpicenterCallback(new Transition.EpicenterCallback() {
+            @Override
+            public Rect onGetEpicenter(Transition transition) {
+                return epicenter;
+            }
+        });
+    }
+
+    private void setSharedElementEpicenter(Transition transition, final TransitionState state) {
+        transition.setEpicenterCallback(new Transition.EpicenterCallback() {
+            private Rect mEpicenter;
+
+            @Override
+            public Rect onGetEpicenter(Transition transition) {
+                if (mEpicenter == null && state.mEnteringEpicenterView != null) {
+                    mEpicenter = new Rect();
+                    state.mEnteringEpicenterView.getBoundsOnScreen(mEpicenter);
+                }
+                return mEpicenter;
+            }
+        });
+    }
+
+    public TransitionState popFromBackStack(boolean doStateMove, TransitionState state) {
         if (FragmentManagerImpl.DEBUG) {
             Log.v(TAG, "popFromBackStack: " + this);
             LogWriter logw = new LogWriter(Log.VERBOSE, TAG);
@@ -698,6 +1010,12 @@
             pw.flush();
         }
 
+        if (state == null) {
+            state = beginTransition(mSharedElementTargetNames, mSharedElementSourceNames);
+        } else {
+            setNameOverrides(state, mSharedElementTargetNames, mSharedElementSourceNames);
+        }
+
         bumpBackStackNesting(-1);
 
         Op op = mTail;
@@ -709,7 +1027,8 @@
                     mManager.removeFragment(f,
                             FragmentManagerImpl.reverseTransit(mTransition),
                             mTransitionStyle);
-                } break;
+                }
+                break;
                 case OP_REPLACE: {
                     Fragment f = op.fragment;
                     if (f != null) {
@@ -719,42 +1038,48 @@
                                 mTransitionStyle);
                     }
                     if (op.removed != null) {
-                        for (int i=0; i<op.removed.size(); i++) {
+                        for (int i = 0; i < op.removed.size(); i++) {
                             Fragment old = op.removed.get(i);
                             old.mNextAnim = op.popEnterAnim;
                             mManager.addFragment(old, false);
                         }
                     }
-                } break;
+                }
+                break;
                 case OP_REMOVE: {
                     Fragment f = op.fragment;
                     f.mNextAnim = op.popEnterAnim;
                     mManager.addFragment(f, false);
-                } break;
+                }
+                break;
                 case OP_HIDE: {
                     Fragment f = op.fragment;
                     f.mNextAnim = op.popEnterAnim;
                     mManager.showFragment(f,
                             FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
-                } break;
+                }
+                break;
                 case OP_SHOW: {
                     Fragment f = op.fragment;
                     f.mNextAnim = op.popExitAnim;
                     mManager.hideFragment(f,
                             FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
-                } break;
+                }
+                break;
                 case OP_DETACH: {
                     Fragment f = op.fragment;
                     f.mNextAnim = op.popEnterAnim;
                     mManager.attachFragment(f,
                             FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
-                } break;
+                }
+                break;
                 case OP_ATTACH: {
                     Fragment f = op.fragment;
                     f.mNextAnim = op.popExitAnim;
                     mManager.detachFragment(f,
                             FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
-                } break;
+                }
+                break;
                 default: {
                     throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
                 }
@@ -766,12 +1091,39 @@
         if (doStateMove) {
             mManager.moveToState(mManager.mCurState,
                     FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle, true);
+            if (state != null) {
+                updateTransitionEndState(state, mSharedElementSourceNames);
+                state = null;
+            }
         }
 
         if (mIndex >= 0) {
             mManager.freeBackStackIndex(mIndex);
             mIndex = -1;
         }
+        return state;
+    }
+
+    private static void setNameOverride(Transition transition, String source, String target) {
+        ArrayMap<String, String> overrides = transition.getNameOverrides();
+        for (int index = 0; index < overrides.size(); index++) {
+            if (source.equals(overrides.valueAt(index))) {
+                overrides.setValueAt(index, target);
+                return;
+            }
+        }
+        overrides.put(source, target);
+    }
+
+    private static void setNameOverrides(TransitionState state, ArrayList<String> sourceNames,
+            ArrayList<String> targetNames) {
+        if (sourceNames != null) {
+            for (int i = 0; i < sourceNames.size(); i++) {
+                String source = sourceNames.get(i);
+                String target = targetNames.get(i);
+                setNameOverride(state.excludingTransition, source, target);
+            }
+        }
     }
 
     public String getName() {
@@ -789,4 +1141,16 @@
     public boolean isEmpty() {
         return mNumOp == 0;
     }
+
+    public class TransitionState {
+        public ArrayList<View> hiddenViews = new ArrayList<View>();
+        public ArrayList<View> transitioningViews = new ArrayList<View>();
+        public ArrayMap<String, View> namedViews = new ArrayMap<String, View>();
+        public Transition exitTransition;
+        public Transition sharedElementTransition;
+        public Transition enterTransition;
+        public TransitionSet excludingTransition;
+        public ViewGroup sceneRoot;
+        public View mEnteringEpicenterView;
+    }
 }
diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java
index 76f9d97..b8f1962 100644
--- a/core/java/android/app/FragmentManager.java
+++ b/core/java/android/app/FragmentManager.java
@@ -1494,7 +1494,7 @@
                 return false;
             }
             final BackStackRecord bss = mBackStack.remove(last);
-            bss.popFromBackStack(true);
+            bss.popFromBackStack(true, null);
             reportBackStackChanged();
         } else {
             int index = -1;
@@ -1538,9 +1538,10 @@
                 states.add(mBackStack.remove(i));
             }
             final int LAST = states.size()-1;
+            BackStackRecord.TransitionState state = null;
             for (int i=0; i<=LAST; i++) {
                 if (DEBUG) Log.v(TAG, "Popping back stack state: " + states.get(i));
-                states.get(i).popFromBackStack(i == LAST);
+                state = states.get(i).popFromBackStack(i == LAST, state);
             }
             reportBackStackChanged();
         }
diff --git a/core/java/android/app/FragmentTransaction.java b/core/java/android/app/FragmentTransaction.java
index 6e99899..7479ecd 100644
--- a/core/java/android/app/FragmentTransaction.java
+++ b/core/java/android/app/FragmentTransaction.java
@@ -1,5 +1,8 @@
 package android.app;
 
+import android.util.Pair;
+import android.view.View;
+
 /**
  * API for performing a set of Fragment operations.
  *
@@ -169,6 +172,36 @@
     public abstract FragmentTransaction setTransition(int transit);
 
     /**
+     * Set a {@link android.transition.Transition} resource id to use with this transaction.
+     * <var>transitionId</var> will be played for fragments when going forward and when popping
+     * the back stack.
+     * @param sceneRootId The ID of the element acting as the scene root for the transition.
+     *                    This should be a ViewGroup containing all Fragments in the transaction.
+     * @param transitionId The resource ID for the Transition used during the Fragment transaction.
+     */
+    public abstract FragmentTransaction setCustomTransition(int sceneRootId, int transitionId);
+
+    /**
+     * Used with {@link #setCustomTransition(int, int)} to map a View from a removed or hidden
+     * Fragment to a View from a shown or added Fragment.
+     * <var>sharedElement</var> must have a unique viewName in the View hierarchy.
+     * @param sharedElement A View in a disappearing Fragment to match with a View in an
+     *                      appearing Fragment.
+     * @param name The viewName for a View in an appearing Fragment to match to the shared
+     *             element.
+     */
+    public abstract FragmentTransaction setSharedElement(View sharedElement, String name);
+
+    /**
+     * Used with {@link #setCustomTransition(int, int)} to map multiple Views from removed or hidden
+     * Fragments to a Views from a shown or added Fragments. Views in
+     * <var>sharedElements</var> must have unique viewNames in the View hierarchy.
+     * @param sharedElements Pairs of Views in disappearing Fragments to viewNames in
+     *                       appearing Fragments.
+     */
+    public abstract FragmentTransaction setSharedElements(Pair<View, String>... sharedElements);
+
+    /**
      * Set a custom style resource that will be used for resolving transit
      * animations.
      */
diff --git a/core/java/android/transition/Transition.java b/core/java/android/transition/Transition.java
index 0a4f641..508769d 100644
--- a/core/java/android/transition/Transition.java
+++ b/core/java/android/transition/Transition.java
@@ -206,6 +206,10 @@
     // like CircularPropagation
     EpicenterCallback mEpicenterCallback;
 
+    // For Fragment shared element transitions, linking views explicitly by mismatching
+    // viewNames.
+    ArrayMap<String, String> mNameOverrides;
+
     /**
      * Constructs a Transition object with no target objects. A transition with
      * no targets defaults to running on all target objects in the scene hierarchy
@@ -1347,6 +1351,21 @@
         } else {
             captureHierarchy(sceneRoot, start);
         }
+        if (!start && mNameOverrides != null) {
+            int numOverrides = mNameOverrides.size();
+            ArrayList<View> overriddenViews = new ArrayList<View>(numOverrides);
+            for (int i = 0; i < numOverrides; i++) {
+                String fromName = mNameOverrides.keyAt(i);
+                overriddenViews.add(mStartValues.nameValues.remove(fromName));
+            }
+            for (int i = 0; i < numOverrides; i++) {
+                View view = overriddenViews.get(i);
+                if (view != null) {
+                    String toName = mNameOverrides.valueAt(i);
+                    mStartValues.nameValues.put(toName, view);
+                }
+            }
+        }
     }
 
     static void addViewValues(TransitionValuesMaps transitionValuesMaps,
@@ -1400,10 +1419,12 @@
             mStartValues.viewValues.clear();
             mStartValues.idValues.clear();
             mStartValues.itemIdValues.clear();
+            mStartValues.nameValues.clear();
         } else {
             mEndValues.viewValues.clear();
             mEndValues.idValues.clear();
             mEndValues.itemIdValues.clear();
+            mEndValues.nameValues.clear();
         }
     }
 
@@ -1866,6 +1887,20 @@
         return mCanRemoveViews;
     }
 
+    /**
+     * Sets the shared element names -- a mapping from a name at the start state to
+     * a different name at the end state.
+     * @hide
+     */
+    public void setNameOverrides(ArrayMap<String, String> overrides) {
+        mNameOverrides = overrides;
+    }
+
+    /** @hide */
+    public ArrayMap<String, String> getNameOverrides() {
+        return mNameOverrides;
+    }
+
     @Override
     public String toString() {
         return toString("");
diff --git a/core/java/android/transition/TransitionSet.java b/core/java/android/transition/TransitionSet.java
index 698b563..c04be4f 100644
--- a/core/java/android/transition/TransitionSet.java
+++ b/core/java/android/transition/TransitionSet.java
@@ -168,30 +168,106 @@
 
     @Override
     public TransitionSet addTarget(View target) {
+        for (int i = 0; i < mTransitions.size(); i++) {
+            mTransitions.get(i).addTarget(target);
+        }
         return (TransitionSet) super.addTarget(target);
     }
 
     @Override
     public TransitionSet addTarget(int targetId) {
+        for (int i = 0; i < mTransitions.size(); i++) {
+            mTransitions.get(i).addTarget(targetId);
+        }
         return (TransitionSet) super.addTarget(targetId);
     }
 
     @Override
+    public TransitionSet addTarget(String targetName) {
+        for (int i = 0; i < mTransitions.size(); i++) {
+            mTransitions.get(i).addTarget(targetName);
+        }
+        return (TransitionSet) super.addTarget(targetName);
+    }
+
+    @Override
+    public TransitionSet addTarget(Class targetType) {
+        for (int i = 0; i < mTransitions.size(); i++) {
+            mTransitions.get(i).addTarget(targetType);
+        }
+        return (TransitionSet) super.addTarget(targetType);
+    }
+
+    @Override
     public TransitionSet addListener(TransitionListener listener) {
         return (TransitionSet) super.addListener(listener);
     }
 
     @Override
     public TransitionSet removeTarget(int targetId) {
+        for (int i = 0; i < mTransitions.size(); i++) {
+            mTransitions.get(i).removeTarget(targetId);
+        }
         return (TransitionSet) super.removeTarget(targetId);
     }
 
     @Override
     public TransitionSet removeTarget(View target) {
+        for (int i = 0; i < mTransitions.size(); i++) {
+            mTransitions.get(i).removeTarget(target);
+        }
         return (TransitionSet) super.removeTarget(target);
     }
 
     @Override
+    public TransitionSet removeTarget(Class target) {
+        for (int i = 0; i < mTransitions.size(); i++) {
+            mTransitions.get(i).removeTarget(target);
+        }
+        return (TransitionSet) super.removeTarget(target);
+    }
+
+    @Override
+    public TransitionSet removeTarget(String target) {
+        for (int i = 0; i < mTransitions.size(); i++) {
+            mTransitions.get(i).removeTarget(target);
+        }
+        return (TransitionSet) super.removeTarget(target);
+    }
+
+    @Override
+    public Transition excludeTarget(View target, boolean exclude) {
+        for (int i = 0; i < mTransitions.size(); i++) {
+            mTransitions.get(i).excludeTarget(target, exclude);
+        }
+        return super.excludeTarget(target, exclude);
+    }
+
+    @Override
+    public Transition excludeTarget(String targetViewName, boolean exclude) {
+        for (int i = 0; i < mTransitions.size(); i++) {
+            mTransitions.get(i).excludeTarget(targetViewName, exclude);
+        }
+        return super.excludeTarget(targetViewName, exclude);
+    }
+
+    @Override
+    public Transition excludeTarget(int targetId, boolean exclude) {
+        for (int i = 0; i < mTransitions.size(); i++) {
+            mTransitions.get(i).excludeTarget(targetId, exclude);
+        }
+        return super.excludeTarget(targetId, exclude);
+    }
+
+    @Override
+    public Transition excludeTarget(Class type, boolean exclude) {
+        for (int i = 0; i < mTransitions.size(); i++) {
+            mTransitions.get(i).excludeTarget(type, exclude);
+        }
+        return super.excludeTarget(type, exclude);
+    }
+
+    @Override
     public TransitionSet removeListener(TransitionListener listener) {
         return (TransitionSet) super.removeListener(listener);
     }
diff --git a/core/java/android/transition/Visibility.java b/core/java/android/transition/Visibility.java
index 0f7638b..c6c8337 100644
--- a/core/java/android/transition/Visibility.java
+++ b/core/java/android/transition/Visibility.java
@@ -18,6 +18,9 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
 import android.view.View;
 import android.view.ViewGroup;
 
@@ -272,15 +275,23 @@
                 if (startView.getParent() == null) {
                     // no parent - safe to use
                     overlayView = startView;
-                } else if (startView.getParent() instanceof View &&
-                        startView.getParent().getParent() == null) {
+                } else if (startView.getParent() instanceof View) {
                     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;
+                    if (!isValidTarget(startParent)) {
+                        if (startView.isAttachedToWindow()) {
+                            overlayView = copyViewImage(startView);
+                        } else {
+                            overlayView = startView;
+                        }
+                    } else if (startParent.getParent() == null) {
+                        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;
+                        }
                     }
                 }
             }
@@ -378,6 +389,26 @@
         return null;
     }
 
+    private View copyViewImage(View view) {
+        int width = view.getWidth();
+        int height = view.getHeight();
+        if (width <= 0 || height <= 0) {
+            return null;
+        }
+        Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+        Canvas canvas = new Canvas(bitmap);
+        view.draw(canvas);
+        final BitmapDrawable drawable = new BitmapDrawable(bitmap);
+
+        View overlayView = new View(view.getContext());
+        overlayView.setBackground(drawable);
+        int widthSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
+        int heightSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);
+        overlayView.measure(widthSpec, heightSpec);
+        overlayView.layout(0, 0, width, height);
+        return overlayView;
+    }
+
     /**
      * The default implementation of this method returns a null Animator. Subclasses should
      * override this method to make targets disappear with the desired transition. The