1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.support.v4.app;
18
19import android.os.Build;
20import android.os.Parcel;
21import android.os.Parcelable;
22import android.support.v4.util.ArrayMap;
23import android.support.v4.util.LogWriter;
24import android.text.TextUtils;
25import android.util.Log;
26import android.util.SparseArray;
27import android.view.View;
28import android.view.ViewGroup;
29import android.view.ViewTreeObserver;
30
31import java.io.FileDescriptor;
32import java.io.PrintWriter;
33import java.lang.reflect.Modifier;
34import java.util.ArrayList;
35
36final class BackStackState implements Parcelable {
37    final int[] mOps;
38    final int mTransition;
39    final int mTransitionStyle;
40    final String mName;
41    final int mIndex;
42    final int mBreadCrumbTitleRes;
43    final CharSequence mBreadCrumbTitleText;
44    final int mBreadCrumbShortTitleRes;
45    final CharSequence mBreadCrumbShortTitleText;
46    final ArrayList<String> mSharedElementSourceNames;
47    final ArrayList<String> mSharedElementTargetNames;
48
49    public BackStackState(BackStackRecord bse) {
50        int numRemoved = 0;
51        BackStackRecord.Op op = bse.mHead;
52        while (op != null) {
53            if (op.removed != null) numRemoved += op.removed.size();
54            op = op.next;
55        }
56        mOps = new int[bse.mNumOp*7 + numRemoved];
57
58        if (!bse.mAddToBackStack) {
59            throw new IllegalStateException("Not on back stack");
60        }
61
62        op = bse.mHead;
63        int pos = 0;
64        while (op != null) {
65            mOps[pos++] = op.cmd;
66            mOps[pos++] = op.fragment != null ? op.fragment.mIndex : -1;
67            mOps[pos++] = op.enterAnim;
68            mOps[pos++] = op.exitAnim;
69            mOps[pos++] = op.popEnterAnim;
70            mOps[pos++] = op.popExitAnim;
71            if (op.removed != null) {
72                final int N = op.removed.size();
73                mOps[pos++] = N;
74                for (int i=0; i<N; i++) {
75                    mOps[pos++] = op.removed.get(i).mIndex;
76                }
77            } else {
78                mOps[pos++] = 0;
79            }
80            op = op.next;
81        }
82        mTransition = bse.mTransition;
83        mTransitionStyle = bse.mTransitionStyle;
84        mName = bse.mName;
85        mIndex = bse.mIndex;
86        mBreadCrumbTitleRes = bse.mBreadCrumbTitleRes;
87        mBreadCrumbTitleText = bse.mBreadCrumbTitleText;
88        mBreadCrumbShortTitleRes = bse.mBreadCrumbShortTitleRes;
89        mBreadCrumbShortTitleText = bse.mBreadCrumbShortTitleText;
90        mSharedElementSourceNames = bse.mSharedElementSourceNames;
91        mSharedElementTargetNames = bse.mSharedElementTargetNames;
92    }
93
94    public BackStackState(Parcel in) {
95        mOps = in.createIntArray();
96        mTransition = in.readInt();
97        mTransitionStyle = in.readInt();
98        mName = in.readString();
99        mIndex = in.readInt();
100        mBreadCrumbTitleRes = in.readInt();
101        mBreadCrumbTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
102        mBreadCrumbShortTitleRes = in.readInt();
103        mBreadCrumbShortTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
104        mSharedElementSourceNames = in.createStringArrayList();
105        mSharedElementTargetNames = in.createStringArrayList();
106    }
107
108    public BackStackRecord instantiate(FragmentManagerImpl fm) {
109        BackStackRecord bse = new BackStackRecord(fm);
110        int pos = 0;
111        int num = 0;
112        while (pos < mOps.length) {
113            BackStackRecord.Op op = new BackStackRecord.Op();
114            op.cmd = mOps[pos++];
115            if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG,
116                    "Instantiate " + bse + " op #" + num + " base fragment #" + mOps[pos]);
117            int findex = mOps[pos++];
118            if (findex >= 0) {
119                Fragment f = fm.mActive.get(findex);
120                op.fragment = f;
121            } else {
122                op.fragment = null;
123            }
124            op.enterAnim = mOps[pos++];
125            op.exitAnim = mOps[pos++];
126            op.popEnterAnim = mOps[pos++];
127            op.popExitAnim = mOps[pos++];
128            final int N = mOps[pos++];
129            if (N > 0) {
130                op.removed = new ArrayList<Fragment>(N);
131                for (int i=0; i<N; i++) {
132                    if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG,
133                            "Instantiate " + bse + " set remove fragment #" + mOps[pos]);
134                    Fragment r = fm.mActive.get(mOps[pos++]);
135                    op.removed.add(r);
136                }
137            }
138            bse.mEnterAnim = op.enterAnim;
139            bse.mExitAnim = op.exitAnim;
140            bse.mPopEnterAnim = op.popEnterAnim;
141            bse.mPopExitAnim = op.popExitAnim;
142            bse.addOp(op);
143            num++;
144        }
145        bse.mTransition = mTransition;
146        bse.mTransitionStyle = mTransitionStyle;
147        bse.mName = mName;
148        bse.mIndex = mIndex;
149        bse.mAddToBackStack = true;
150        bse.mBreadCrumbTitleRes = mBreadCrumbTitleRes;
151        bse.mBreadCrumbTitleText = mBreadCrumbTitleText;
152        bse.mBreadCrumbShortTitleRes = mBreadCrumbShortTitleRes;
153        bse.mBreadCrumbShortTitleText = mBreadCrumbShortTitleText;
154        bse.mSharedElementSourceNames = mSharedElementSourceNames;
155        bse.mSharedElementTargetNames = mSharedElementTargetNames;
156        bse.bumpBackStackNesting(1);
157        return bse;
158    }
159
160    @Override
161    public int describeContents() {
162        return 0;
163    }
164
165    @Override
166    public void writeToParcel(Parcel dest, int flags) {
167        dest.writeIntArray(mOps);
168        dest.writeInt(mTransition);
169        dest.writeInt(mTransitionStyle);
170        dest.writeString(mName);
171        dest.writeInt(mIndex);
172        dest.writeInt(mBreadCrumbTitleRes);
173        TextUtils.writeToParcel(mBreadCrumbTitleText, dest, 0);
174        dest.writeInt(mBreadCrumbShortTitleRes);
175        TextUtils.writeToParcel(mBreadCrumbShortTitleText, dest, 0);
176        dest.writeStringList(mSharedElementSourceNames);
177        dest.writeStringList(mSharedElementTargetNames);
178    }
179
180    public static final Parcelable.Creator<BackStackState> CREATOR
181            = new Parcelable.Creator<BackStackState>() {
182        @Override
183        public BackStackState createFromParcel(Parcel in) {
184            return new BackStackState(in);
185        }
186
187        @Override
188        public BackStackState[] newArray(int size) {
189            return new BackStackState[size];
190        }
191    };
192}
193
194/**
195 * Entry of an operation on the fragment back stack.
196 */
197final class BackStackRecord extends FragmentTransaction implements
198        FragmentManager.BackStackEntry, Runnable {
199    static final String TAG = FragmentManagerImpl.TAG;
200    static final boolean SUPPORTS_TRANSITIONS = Build.VERSION.SDK_INT >= 21;
201
202    final FragmentManagerImpl mManager;
203
204    static final int OP_NULL = 0;
205    static final int OP_ADD = 1;
206    static final int OP_REPLACE = 2;
207    static final int OP_REMOVE = 3;
208    static final int OP_HIDE = 4;
209    static final int OP_SHOW = 5;
210    static final int OP_DETACH = 6;
211    static final int OP_ATTACH = 7;
212
213    static final class Op {
214        Op next;
215        Op prev;
216        int cmd;
217        Fragment fragment;
218        int enterAnim;
219        int exitAnim;
220        int popEnterAnim;
221        int popExitAnim;
222        ArrayList<Fragment> removed;
223    }
224
225    Op mHead;
226    Op mTail;
227    int mNumOp;
228    int mEnterAnim;
229    int mExitAnim;
230    int mPopEnterAnim;
231    int mPopExitAnim;
232    int mTransition;
233    int mTransitionStyle;
234    boolean mAddToBackStack;
235    boolean mAllowAddToBackStack = true;
236    String mName;
237    boolean mCommitted;
238    int mIndex = -1;
239
240    int mBreadCrumbTitleRes;
241    CharSequence mBreadCrumbTitleText;
242    int mBreadCrumbShortTitleRes;
243    CharSequence mBreadCrumbShortTitleText;
244
245    ArrayList<String> mSharedElementSourceNames;
246    ArrayList<String> mSharedElementTargetNames;
247
248    @Override
249    public String toString() {
250        StringBuilder sb = new StringBuilder(128);
251        sb.append("BackStackEntry{");
252        sb.append(Integer.toHexString(System.identityHashCode(this)));
253        if (mIndex >= 0) {
254            sb.append(" #");
255            sb.append(mIndex);
256        }
257        if (mName != null) {
258            sb.append(" ");
259            sb.append(mName);
260        }
261        sb.append("}");
262        return sb.toString();
263    }
264
265    public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
266        dump(prefix, writer, true);
267    }
268
269    public void dump(String prefix, PrintWriter writer, boolean full) {
270        if (full) {
271            writer.print(prefix); writer.print("mName="); writer.print(mName);
272                    writer.print(" mIndex="); writer.print(mIndex);
273                    writer.print(" mCommitted="); writer.println(mCommitted);
274            if (mTransition != FragmentTransaction.TRANSIT_NONE) {
275                writer.print(prefix); writer.print("mTransition=#");
276                        writer.print(Integer.toHexString(mTransition));
277                        writer.print(" mTransitionStyle=#");
278                        writer.println(Integer.toHexString(mTransitionStyle));
279            }
280            if (mEnterAnim != 0 || mExitAnim !=0) {
281                writer.print(prefix); writer.print("mEnterAnim=#");
282                        writer.print(Integer.toHexString(mEnterAnim));
283                        writer.print(" mExitAnim=#");
284                        writer.println(Integer.toHexString(mExitAnim));
285            }
286            if (mPopEnterAnim != 0 || mPopExitAnim !=0) {
287                writer.print(prefix); writer.print("mPopEnterAnim=#");
288                        writer.print(Integer.toHexString(mPopEnterAnim));
289                        writer.print(" mPopExitAnim=#");
290                        writer.println(Integer.toHexString(mPopExitAnim));
291            }
292            if (mBreadCrumbTitleRes != 0 || mBreadCrumbTitleText != null) {
293                writer.print(prefix); writer.print("mBreadCrumbTitleRes=#");
294                        writer.print(Integer.toHexString(mBreadCrumbTitleRes));
295                        writer.print(" mBreadCrumbTitleText=");
296                        writer.println(mBreadCrumbTitleText);
297            }
298            if (mBreadCrumbShortTitleRes != 0 || mBreadCrumbShortTitleText != null) {
299                writer.print(prefix); writer.print("mBreadCrumbShortTitleRes=#");
300                        writer.print(Integer.toHexString(mBreadCrumbShortTitleRes));
301                        writer.print(" mBreadCrumbShortTitleText=");
302                        writer.println(mBreadCrumbShortTitleText);
303            }
304        }
305
306        if (mHead != null) {
307            writer.print(prefix); writer.println("Operations:");
308            String innerPrefix = prefix + "    ";
309            Op op = mHead;
310            int num = 0;
311            while (op != null) {
312                String cmdStr;
313                switch (op.cmd) {
314                    case OP_NULL: cmdStr="NULL"; break;
315                    case OP_ADD: cmdStr="ADD"; break;
316                    case OP_REPLACE: cmdStr="REPLACE"; break;
317                    case OP_REMOVE: cmdStr="REMOVE"; break;
318                    case OP_HIDE: cmdStr="HIDE"; break;
319                    case OP_SHOW: cmdStr="SHOW"; break;
320                    case OP_DETACH: cmdStr="DETACH"; break;
321                    case OP_ATTACH: cmdStr="ATTACH"; break;
322                    default: cmdStr="cmd=" + op.cmd; break;
323                }
324                writer.print(prefix); writer.print("  Op #"); writer.print(num);
325                        writer.print(": "); writer.print(cmdStr);
326                        writer.print(" "); writer.println(op.fragment);
327                if (full) {
328                    if (op.enterAnim != 0 || op.exitAnim != 0) {
329                        writer.print(prefix); writer.print("enterAnim=#");
330                                writer.print(Integer.toHexString(op.enterAnim));
331                                writer.print(" exitAnim=#");
332                                writer.println(Integer.toHexString(op.exitAnim));
333                    }
334                    if (op.popEnterAnim != 0 || op.popExitAnim != 0) {
335                        writer.print(prefix); writer.print("popEnterAnim=#");
336                                writer.print(Integer.toHexString(op.popEnterAnim));
337                                writer.print(" popExitAnim=#");
338                                writer.println(Integer.toHexString(op.popExitAnim));
339                    }
340                }
341                if (op.removed != null && op.removed.size() > 0) {
342                    for (int i=0; i<op.removed.size(); i++) {
343                        writer.print(innerPrefix);
344                        if (op.removed.size() == 1) {
345                            writer.print("Removed: ");
346                        } else {
347                            if (i == 0) {
348                                writer.println("Removed:");
349                            }
350                            writer.print(innerPrefix); writer.print("  #"); writer.print(i);
351                                    writer.print(": ");
352                        }
353                        writer.println(op.removed.get(i));
354                    }
355                }
356                op = op.next;
357                num++;
358            }
359        }
360    }
361
362    public BackStackRecord(FragmentManagerImpl manager) {
363        mManager = manager;
364    }
365
366    @Override
367    public int getId() {
368        return mIndex;
369    }
370
371    @Override
372    public int getBreadCrumbTitleRes() {
373        return mBreadCrumbTitleRes;
374    }
375
376    @Override
377    public int getBreadCrumbShortTitleRes() {
378        return mBreadCrumbShortTitleRes;
379    }
380
381    @Override
382    public CharSequence getBreadCrumbTitle() {
383        if (mBreadCrumbTitleRes != 0) {
384            return mManager.mHost.getContext().getText(mBreadCrumbTitleRes);
385        }
386        return mBreadCrumbTitleText;
387    }
388
389    @Override
390    public CharSequence getBreadCrumbShortTitle() {
391        if (mBreadCrumbShortTitleRes != 0) {
392            return mManager.mHost.getContext().getText(mBreadCrumbShortTitleRes);
393        }
394        return mBreadCrumbShortTitleText;
395    }
396
397    void addOp(Op op) {
398        if (mHead == null) {
399            mHead = mTail = op;
400        } else {
401            op.prev = mTail;
402            mTail.next = op;
403            mTail = op;
404        }
405        op.enterAnim = mEnterAnim;
406        op.exitAnim = mExitAnim;
407        op.popEnterAnim = mPopEnterAnim;
408        op.popExitAnim = mPopExitAnim;
409        mNumOp++;
410    }
411
412    @Override
413    public FragmentTransaction add(Fragment fragment, String tag) {
414        doAddOp(0, fragment, tag, OP_ADD);
415        return this;
416    }
417
418    @Override
419    public FragmentTransaction add(int containerViewId, Fragment fragment) {
420        doAddOp(containerViewId, fragment, null, OP_ADD);
421        return this;
422    }
423
424    @Override
425    public FragmentTransaction add(int containerViewId, Fragment fragment, String tag) {
426        doAddOp(containerViewId, fragment, tag, OP_ADD);
427        return this;
428    }
429
430    private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) {
431        final Class fragmentClass = fragment.getClass();
432        final int modifiers = fragmentClass.getModifiers();
433        if (fragmentClass.isAnonymousClass() || !Modifier.isPublic(modifiers)
434                || (fragmentClass.isMemberClass() && !Modifier.isStatic(modifiers))) {
435            throw new IllegalStateException("Fragment " + fragmentClass.getCanonicalName()
436                    + " must be a public static class to be  properly recreated from"
437                    + " instance state.");
438        }
439
440        fragment.mFragmentManager = mManager;
441
442        if (tag != null) {
443            if (fragment.mTag != null && !tag.equals(fragment.mTag)) {
444                throw new IllegalStateException("Can't change tag of fragment "
445                        + fragment + ": was " + fragment.mTag
446                        + " now " + tag);
447            }
448            fragment.mTag = tag;
449        }
450
451        if (containerViewId != 0) {
452            if (containerViewId == View.NO_ID) {
453                throw new IllegalArgumentException("Can't add fragment "
454                        + fragment + " with tag " + tag + " to container view with no id");
455            }
456            if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) {
457                throw new IllegalStateException("Can't change container ID of fragment "
458                        + fragment + ": was " + fragment.mFragmentId
459                        + " now " + containerViewId);
460            }
461            fragment.mContainerId = fragment.mFragmentId = containerViewId;
462        }
463
464        Op op = new Op();
465        op.cmd = opcmd;
466        op.fragment = fragment;
467        addOp(op);
468    }
469
470    @Override
471    public FragmentTransaction replace(int containerViewId, Fragment fragment) {
472        return replace(containerViewId, fragment, null);
473    }
474
475    @Override
476    public FragmentTransaction replace(int containerViewId, Fragment fragment, String tag) {
477        if (containerViewId == 0) {
478            throw new IllegalArgumentException("Must use non-zero containerViewId");
479        }
480
481        doAddOp(containerViewId, fragment, tag, OP_REPLACE);
482        return this;
483    }
484
485    @Override
486    public FragmentTransaction remove(Fragment fragment) {
487        Op op = new Op();
488        op.cmd = OP_REMOVE;
489        op.fragment = fragment;
490        addOp(op);
491
492        return this;
493    }
494
495    @Override
496    public FragmentTransaction hide(Fragment fragment) {
497        Op op = new Op();
498        op.cmd = OP_HIDE;
499        op.fragment = fragment;
500        addOp(op);
501
502        return this;
503    }
504
505    @Override
506    public FragmentTransaction show(Fragment fragment) {
507        Op op = new Op();
508        op.cmd = OP_SHOW;
509        op.fragment = fragment;
510        addOp(op);
511
512        return this;
513    }
514
515    @Override
516    public FragmentTransaction detach(Fragment fragment) {
517        Op op = new Op();
518        op.cmd = OP_DETACH;
519        op.fragment = fragment;
520        addOp(op);
521
522        return this;
523    }
524
525    @Override
526    public FragmentTransaction attach(Fragment fragment) {
527        Op op = new Op();
528        op.cmd = OP_ATTACH;
529        op.fragment = fragment;
530        addOp(op);
531
532        return this;
533    }
534
535    @Override
536    public FragmentTransaction setCustomAnimations(int enter, int exit) {
537        return setCustomAnimations(enter, exit, 0, 0);
538    }
539
540    @Override
541    public FragmentTransaction setCustomAnimations(int enter, int exit,
542            int popEnter, int popExit) {
543        mEnterAnim = enter;
544        mExitAnim = exit;
545        mPopEnterAnim = popEnter;
546        mPopExitAnim = popExit;
547        return this;
548    }
549
550    @Override
551    public FragmentTransaction setTransition(int transition) {
552        mTransition = transition;
553        return this;
554    }
555
556    @Override
557    public FragmentTransaction addSharedElement(View sharedElement, String name) {
558        if (SUPPORTS_TRANSITIONS) {
559            String transitionName = FragmentTransitionCompat21.getTransitionName(sharedElement);
560            if (transitionName == null) {
561                throw new IllegalArgumentException("Unique transitionNames are required for all" +
562                        " sharedElements");
563            }
564            if (mSharedElementSourceNames == null) {
565                mSharedElementSourceNames = new ArrayList<String>();
566                mSharedElementTargetNames = new ArrayList<String>();
567            }
568
569            mSharedElementSourceNames.add(transitionName);
570            mSharedElementTargetNames.add(name);
571        }
572        return this;
573    }
574
575    @Override
576    public FragmentTransaction setTransitionStyle(int styleRes) {
577        mTransitionStyle = styleRes;
578        return this;
579    }
580
581    @Override
582    public FragmentTransaction addToBackStack(String name) {
583        if (!mAllowAddToBackStack) {
584            throw new IllegalStateException(
585                    "This FragmentTransaction is not allowed to be added to the back stack.");
586        }
587        mAddToBackStack = true;
588        mName = name;
589        return this;
590    }
591
592    @Override
593    public boolean isAddToBackStackAllowed() {
594        return mAllowAddToBackStack;
595    }
596
597    @Override
598    public FragmentTransaction disallowAddToBackStack() {
599        if (mAddToBackStack) {
600            throw new IllegalStateException(
601                    "This transaction is already being added to the back stack");
602        }
603        mAllowAddToBackStack = false;
604        return this;
605    }
606
607    @Override
608    public FragmentTransaction setBreadCrumbTitle(int res) {
609        mBreadCrumbTitleRes = res;
610        mBreadCrumbTitleText = null;
611        return this;
612    }
613
614    @Override
615    public FragmentTransaction setBreadCrumbTitle(CharSequence text) {
616        mBreadCrumbTitleRes = 0;
617        mBreadCrumbTitleText = text;
618        return this;
619    }
620
621    @Override
622    public FragmentTransaction setBreadCrumbShortTitle(int res) {
623        mBreadCrumbShortTitleRes = res;
624        mBreadCrumbShortTitleText = null;
625        return this;
626    }
627
628    @Override
629    public FragmentTransaction setBreadCrumbShortTitle(CharSequence text) {
630        mBreadCrumbShortTitleRes = 0;
631        mBreadCrumbShortTitleText = text;
632        return this;
633    }
634
635    void bumpBackStackNesting(int amt) {
636        if (!mAddToBackStack) {
637            return;
638        }
639        if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting in " + this
640                + " by " + amt);
641        Op op = mHead;
642        while (op != null) {
643            if (op.fragment != null) {
644                op.fragment.mBackStackNesting += amt;
645                if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting of "
646                        + op.fragment + " to " + op.fragment.mBackStackNesting);
647            }
648            if (op.removed != null) {
649                for (int i=op.removed.size()-1; i>=0; i--) {
650                    Fragment r = op.removed.get(i);
651                    r.mBackStackNesting += amt;
652                    if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting of "
653                            + r + " to " + r.mBackStackNesting);
654                }
655            }
656            op = op.next;
657        }
658    }
659
660    @Override
661    public int commit() {
662        return commitInternal(false);
663    }
664
665    @Override
666    public int commitAllowingStateLoss() {
667        return commitInternal(true);
668    }
669
670    @Override
671    public void commitNow() {
672        disallowAddToBackStack();
673        mManager.execSingleAction(this, false);
674    }
675
676    @Override
677    public void commitNowAllowingStateLoss() {
678        disallowAddToBackStack();
679        mManager.execSingleAction(this, true);
680    }
681
682    int commitInternal(boolean allowStateLoss) {
683        if (mCommitted) throw new IllegalStateException("commit already called");
684        if (FragmentManagerImpl.DEBUG) {
685            Log.v(TAG, "Commit: " + this);
686            LogWriter logw = new LogWriter(TAG);
687            PrintWriter pw = new PrintWriter(logw);
688            dump("  ", null, pw, null);
689        }
690        mCommitted = true;
691        if (mAddToBackStack) {
692            mIndex = mManager.allocBackStackIndex(this);
693        } else {
694            mIndex = -1;
695        }
696        mManager.enqueueAction(this, allowStateLoss);
697        return mIndex;
698    }
699
700    @Override
701    public void run() {
702        if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Run: " + this);
703
704        if (mAddToBackStack) {
705            if (mIndex < 0) {
706                throw new IllegalStateException("addToBackStack() called after commit()");
707            }
708        }
709
710        bumpBackStackNesting(1);
711
712        TransitionState state = null;
713        SparseArray<Fragment> firstOutFragments = null;
714        SparseArray<Fragment> lastInFragments = null;
715        if (SUPPORTS_TRANSITIONS && mManager.mCurState >= Fragment.CREATED) {
716            firstOutFragments = new SparseArray<Fragment>();
717            lastInFragments = new SparseArray<Fragment>();
718
719            calculateFragments(firstOutFragments, lastInFragments);
720
721            state = beginTransition(firstOutFragments, lastInFragments, false);
722        }
723
724        int transitionStyle = state != null ? 0 : mTransitionStyle;
725        int transition = state != null ? 0 : mTransition;
726        Op op = mHead;
727        while (op != null) {
728            int enterAnim = state != null ? 0 : op.enterAnim;
729            int exitAnim = state != null ? 0 : op.exitAnim;
730            switch (op.cmd) {
731                case OP_ADD: {
732                    Fragment f = op.fragment;
733                    f.mNextAnim = enterAnim;
734                    mManager.addFragment(f, false);
735                } break;
736                case OP_REPLACE: {
737                    Fragment f = op.fragment;
738                    int containerId = f.mContainerId;
739                    if (mManager.mAdded != null) {
740                        for (int i = mManager.mAdded.size() - 1; i >= 0; i--) {
741                            Fragment old = mManager.mAdded.get(i);
742                            if (FragmentManagerImpl.DEBUG) Log.v(TAG,
743                                    "OP_REPLACE: adding=" + f + " old=" + old);
744                            if (old.mContainerId == containerId) {
745                                if (old == f) {
746                                    op.fragment = f = null;
747                                } else {
748                                    if (op.removed == null) {
749                                        op.removed = new ArrayList<Fragment>();
750                                    }
751                                    op.removed.add(old);
752                                    old.mNextAnim = exitAnim;
753                                    if (mAddToBackStack) {
754                                        old.mBackStackNesting += 1;
755                                        if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting of "
756                                                + old + " to " + old.mBackStackNesting);
757                                    }
758                                    mManager.removeFragment(old, transition, transitionStyle);
759                                }
760                            }
761                        }
762                    }
763                    if (f != null) {
764                        f.mNextAnim = enterAnim;
765                        mManager.addFragment(f, false);
766                    }
767                } break;
768                case OP_REMOVE: {
769                    Fragment f = op.fragment;
770                    f.mNextAnim = exitAnim;
771                    mManager.removeFragment(f, transition, transitionStyle);
772                } break;
773                case OP_HIDE: {
774                    Fragment f = op.fragment;
775                    f.mNextAnim = exitAnim;
776                    mManager.hideFragment(f, transition, transitionStyle);
777                } break;
778                case OP_SHOW: {
779                    Fragment f = op.fragment;
780                    f.mNextAnim = enterAnim;
781                    mManager.showFragment(f, transition, transitionStyle);
782                } break;
783                case OP_DETACH: {
784                    Fragment f = op.fragment;
785                    f.mNextAnim = exitAnim;
786                    mManager.detachFragment(f, transition, transitionStyle);
787                } break;
788                case OP_ATTACH: {
789                    Fragment f = op.fragment;
790                    f.mNextAnim = enterAnim;
791                    mManager.attachFragment(f, transition, transitionStyle);
792                } break;
793                default: {
794                    throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
795                }
796            }
797
798            op = op.next;
799        }
800
801        mManager.moveToState(mManager.mCurState, transition, transitionStyle, true);
802
803        if (mAddToBackStack) {
804            mManager.addBackStackState(this);
805        }
806    }
807
808    private static void setFirstOut(SparseArray<Fragment> firstOutFragments,
809            SparseArray<Fragment> lastInFragments, Fragment fragment) {
810        if (fragment != null) {
811            int containerId = fragment.mContainerId;
812            if (containerId != 0 && !fragment.isHidden()) {
813                if (fragment.isAdded() && fragment.getView() != null
814                        && firstOutFragments.get(containerId) == null) {
815                    firstOutFragments.put(containerId, fragment);
816                }
817                if (lastInFragments.get(containerId) == fragment) {
818                    lastInFragments.remove(containerId);
819                }
820            }
821        }
822    }
823
824    private void setLastIn(SparseArray<Fragment> firstOutFragments,
825            SparseArray<Fragment> lastInFragments, Fragment fragment) {
826        if (fragment != null) {
827            int containerId = fragment.mContainerId;
828            if (containerId != 0) {
829                if (!fragment.isAdded()) {
830                    lastInFragments.put(containerId, fragment);
831                }
832                if (firstOutFragments.get(containerId) == fragment) {
833                    firstOutFragments.remove(containerId);
834                }
835            }
836            if (fragment.mState < Fragment.CREATED && mManager.mCurState >= Fragment.CREATED) {
837                mManager.makeActive(fragment);
838                mManager.moveToState(fragment, Fragment.CREATED, 0, 0, false);
839            }
840        }
841    }
842
843    /**
844     * Finds the first removed fragment and last added fragments when going forward.
845     * If none of the fragments have transitions, then both lists will be empty.
846     *
847     * @param firstOutFragments The list of first fragments to be removed, keyed on the
848     *                          container ID. This list will be modified by the method.
849     * @param lastInFragments The list of last fragments to be added, keyed on the
850     *                        container ID. This list will be modified by the method.
851     */
852    private void calculateFragments(SparseArray<Fragment> firstOutFragments,
853            SparseArray<Fragment> lastInFragments) {
854        if (!mManager.mContainer.onHasView()) {
855            return; // nothing to see, so no transitions
856        }
857        Op op = mHead;
858        while (op != null) {
859            switch (op.cmd) {
860                case OP_ADD:
861                    setLastIn(firstOutFragments, lastInFragments, op.fragment);
862                    break;
863                case OP_REPLACE: {
864                    Fragment f = op.fragment;
865                    if (mManager.mAdded != null) {
866                        for (int i = 0; i < mManager.mAdded.size(); i++) {
867                            Fragment old = mManager.mAdded.get(i);
868                            if (f == null || old.mContainerId == f.mContainerId) {
869                                if (old == f) {
870                                    f = null;
871                                    lastInFragments.remove(old.mContainerId);
872                                } else {
873                                    setFirstOut(firstOutFragments, lastInFragments, old);
874                                }
875                            }
876                        }
877                    }
878                    setLastIn(firstOutFragments, lastInFragments, op.fragment);
879                    break;
880                }
881                case OP_REMOVE:
882                    setFirstOut(firstOutFragments, lastInFragments, op.fragment);
883                    break;
884                case OP_HIDE:
885                    setFirstOut(firstOutFragments, lastInFragments, op.fragment);
886                    break;
887                case OP_SHOW:
888                    setLastIn(firstOutFragments, lastInFragments, op.fragment);
889                    break;
890                case OP_DETACH:
891                    setFirstOut(firstOutFragments, lastInFragments, op.fragment);
892                    break;
893                case OP_ATTACH:
894                    setLastIn(firstOutFragments, lastInFragments, op.fragment);
895                    break;
896            }
897
898            op = op.next;
899        }
900    }
901
902    /**
903     * Finds the first removed fragment and last added fragments when popping the back stack.
904     * If none of the fragments have transitions, then both lists will be empty.
905     *
906     * @param firstOutFragments The list of first fragments to be removed, keyed on the
907     *                          container ID. This list will be modified by the method.
908     * @param lastInFragments The list of last fragments to be added, keyed on the
909     *                        container ID. This list will be modified by the method.
910     */
911    public void calculateBackFragments(SparseArray<Fragment> firstOutFragments,
912            SparseArray<Fragment> lastInFragments) {
913        if (!mManager.mContainer.onHasView()) {
914            return; // nothing to see, so no transitions
915        }
916        Op op = mTail;
917        while (op != null) {
918            switch (op.cmd) {
919                case OP_ADD:
920                    setFirstOut(firstOutFragments, lastInFragments, op.fragment);
921                    break;
922                case OP_REPLACE:
923                    if (op.removed != null) {
924                        for (int i = op.removed.size() - 1; i >= 0; i--) {
925                            setLastIn(firstOutFragments, lastInFragments, op.removed.get(i));
926                        }
927                    }
928                    setFirstOut(firstOutFragments, lastInFragments, op.fragment);
929                    break;
930                case OP_REMOVE:
931                    setLastIn(firstOutFragments, lastInFragments, op.fragment);
932                    break;
933                case OP_HIDE:
934                    setLastIn(firstOutFragments, lastInFragments, op.fragment);
935                    break;
936                case OP_SHOW:
937                    setFirstOut(firstOutFragments, lastInFragments, op.fragment);
938                    break;
939                case OP_DETACH:
940                    setLastIn(firstOutFragments, lastInFragments, op.fragment);
941                    break;
942                case OP_ATTACH:
943                    setFirstOut(firstOutFragments, lastInFragments, op.fragment);
944                    break;
945            }
946
947            op = op.prev;
948        }
949    }
950
951    public TransitionState popFromBackStack(boolean doStateMove, TransitionState state,
952            SparseArray<Fragment> firstOutFragments, SparseArray<Fragment> lastInFragments) {
953        if (FragmentManagerImpl.DEBUG) {
954            Log.v(TAG, "popFromBackStack: " + this);
955            LogWriter logw = new LogWriter(TAG);
956            PrintWriter pw = new PrintWriter(logw);
957            dump("  ", null, pw, null);
958        }
959
960        if (SUPPORTS_TRANSITIONS && mManager.mCurState >= Fragment.CREATED) {
961            if (state == null) {
962                if (firstOutFragments.size() != 0 || lastInFragments.size() != 0) {
963                    state = beginTransition(firstOutFragments, lastInFragments, true);
964                }
965            } else if (!doStateMove) {
966                setNameOverrides(state, mSharedElementTargetNames, mSharedElementSourceNames);
967            }
968        }
969
970        bumpBackStackNesting(-1);
971
972        int transitionStyle = state != null ? 0 : mTransitionStyle;
973        int transition = state != null ? 0 : mTransition;
974        Op op = mTail;
975        while (op != null) {
976            int popEnterAnim = state != null ? 0 : op.popEnterAnim;
977            int popExitAnim= state != null ? 0 : op.popExitAnim;
978            switch (op.cmd) {
979                case OP_ADD: {
980                    Fragment f = op.fragment;
981                    f.mNextAnim = popExitAnim;
982                    mManager.removeFragment(f,
983                            FragmentManagerImpl.reverseTransit(transition), transitionStyle);
984                } break;
985                case OP_REPLACE: {
986                    Fragment f = op.fragment;
987                    if (f != null) {
988                        f.mNextAnim = popExitAnim;
989                        mManager.removeFragment(f,
990                                FragmentManagerImpl.reverseTransit(transition), transitionStyle);
991                    }
992                    if (op.removed != null) {
993                        for (int i=0; i<op.removed.size(); i++) {
994                            Fragment old = op.removed.get(i);
995                            old.mNextAnim = popEnterAnim;
996                            mManager.addFragment(old, false);
997                        }
998                    }
999                } break;
1000                case OP_REMOVE: {
1001                    Fragment f = op.fragment;
1002                    f.mNextAnim = popEnterAnim;
1003                    mManager.addFragment(f, false);
1004                } break;
1005                case OP_HIDE: {
1006                    Fragment f = op.fragment;
1007                    f.mNextAnim = popEnterAnim;
1008                    mManager.showFragment(f,
1009                            FragmentManagerImpl.reverseTransit(transition), transitionStyle);
1010                } break;
1011                case OP_SHOW: {
1012                    Fragment f = op.fragment;
1013                    f.mNextAnim = popExitAnim;
1014                    mManager.hideFragment(f,
1015                            FragmentManagerImpl.reverseTransit(transition), transitionStyle);
1016                } break;
1017                case OP_DETACH: {
1018                    Fragment f = op.fragment;
1019                    f.mNextAnim = popEnterAnim;
1020                    mManager.attachFragment(f,
1021                            FragmentManagerImpl.reverseTransit(transition), transitionStyle);
1022                } break;
1023                case OP_ATTACH: {
1024                    Fragment f = op.fragment;
1025                    f.mNextAnim = popEnterAnim;
1026                    mManager.detachFragment(f,
1027                            FragmentManagerImpl.reverseTransit(transition), transitionStyle);
1028                } break;
1029                default: {
1030                    throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
1031                }
1032            }
1033
1034            op = op.prev;
1035        }
1036
1037        if (doStateMove) {
1038            mManager.moveToState(mManager.mCurState,
1039                    FragmentManagerImpl.reverseTransit(transition), transitionStyle, true);
1040            state = null;
1041        }
1042
1043        if (mIndex >= 0) {
1044            mManager.freeBackStackIndex(mIndex);
1045            mIndex = -1;
1046        }
1047        return state;
1048    }
1049
1050    @Override
1051    public String getName() {
1052        return mName;
1053    }
1054
1055    public int getTransition() {
1056        return mTransition;
1057    }
1058
1059    public int getTransitionStyle() {
1060        return mTransitionStyle;
1061    }
1062
1063    @Override
1064    public boolean isEmpty() {
1065        return mNumOp == 0;
1066    }
1067
1068    /**
1069     * When custom fragment transitions are used, this sets up the state for each transition
1070     * and begins the transition. A different transition is started for each fragment container
1071     * and consists of up to 3 different transitions: the exit transition, a shared element
1072     * transition and an enter transition.
1073     *
1074     * <p>The exit transition operates against the leaf nodes of the first fragment
1075     * with a view that was removed. If no such fragment was removed, then no exit
1076     * transition is executed. The exit transition comes from the outgoing fragment.</p>
1077     *
1078     * <p>The enter transition operates against the last fragment that was added. If
1079     * that fragment does not have a view or no fragment was added, then no enter
1080     * transition is executed. The enter transition comes from the incoming fragment.</p>
1081     *
1082     * <p>The shared element transition operates against all views and comes either
1083     * from the outgoing fragment or the incoming fragment, depending on whether this
1084     * is going forward or popping the back stack. When going forward, the incoming
1085     * fragment's enter shared element transition is used, but when going back, the
1086     * outgoing fragment's return shared element transition is used. Shared element
1087     * transitions only operate if there is both an incoming and outgoing fragment.</p>
1088     *
1089     * @param firstOutFragments The list of first fragments to be removed, keyed on the
1090     *                          container ID.
1091     * @param lastInFragments The list of last fragments to be added, keyed on the
1092     *                        container ID.
1093     * @param isBack true if this is popping the back stack or false if this is a
1094     *               forward operation.
1095     * @return The TransitionState used to complete the operation of the transition
1096     * in {@link #setNameOverrides(BackStackRecord.TransitionState, java.util.ArrayList,
1097     * java.util.ArrayList)}.
1098     */
1099    private TransitionState beginTransition(SparseArray<Fragment> firstOutFragments,
1100            SparseArray<Fragment> lastInFragments, boolean isBack) {
1101        TransitionState state = new TransitionState();
1102
1103        // Adding a non-existent target view makes sure that the transitions don't target
1104        // any views by default. They'll only target the views we tell add. If we don't
1105        // add any, then no views will be targeted.
1106        state.nonExistentView = new View(mManager.mHost.getContext());
1107
1108        boolean anyTransitionStarted = false;
1109        // Go over all leaving fragments.
1110        for (int i = 0; i < firstOutFragments.size(); i++) {
1111            int containerId = firstOutFragments.keyAt(i);
1112            if (configureTransitions(containerId, state, isBack, firstOutFragments,
1113                    lastInFragments)) {
1114                anyTransitionStarted = true;
1115            }
1116        }
1117
1118        // Now go over all entering fragments that didn't have a leaving fragment.
1119        for (int i = 0; i < lastInFragments.size(); i++) {
1120            int containerId = lastInFragments.keyAt(i);
1121            if (firstOutFragments.get(containerId) == null &&
1122                configureTransitions(containerId, state, isBack, firstOutFragments,
1123                        lastInFragments)) {
1124                anyTransitionStarted = true;
1125            }
1126        }
1127
1128        if (!anyTransitionStarted) {
1129            state = null;
1130        }
1131
1132        return state;
1133    }
1134
1135    private static Object getEnterTransition(Fragment inFragment, boolean isBack) {
1136        if (inFragment == null) {
1137            return null;
1138        }
1139        return FragmentTransitionCompat21.cloneTransition(isBack ?
1140                inFragment.getReenterTransition() : inFragment.getEnterTransition());
1141    }
1142
1143    private static Object getExitTransition(Fragment outFragment, boolean isBack) {
1144        if (outFragment == null) {
1145            return null;
1146        }
1147        return FragmentTransitionCompat21.cloneTransition(isBack ?
1148                outFragment.getReturnTransition() : outFragment.getExitTransition());
1149    }
1150
1151    private static Object getSharedElementTransition(Fragment inFragment, Fragment outFragment,
1152            boolean isBack) {
1153        if (inFragment == null || outFragment == null) {
1154            return null;
1155        }
1156        return FragmentTransitionCompat21.wrapSharedElementTransition(isBack ?
1157                outFragment.getSharedElementReturnTransition() :
1158                inFragment.getSharedElementEnterTransition());
1159    }
1160
1161    private static Object captureExitingViews(Object exitTransition, Fragment outFragment,
1162            ArrayList<View> exitingViews, ArrayMap<String, View> namedViews, View nonExistentView) {
1163        if (exitTransition != null) {
1164            exitTransition = FragmentTransitionCompat21.captureExitingViews(exitTransition,
1165                    outFragment.getView(), exitingViews, namedViews, nonExistentView);
1166        }
1167        return exitTransition;
1168    }
1169
1170    private ArrayMap<String, View> remapSharedElements(TransitionState state, Fragment outFragment,
1171            boolean isBack) {
1172        ArrayMap<String, View> namedViews = new ArrayMap<String, View>();
1173        if (mSharedElementSourceNames != null) {
1174            FragmentTransitionCompat21.findNamedViews(namedViews, outFragment.getView());
1175            if (isBack) {
1176                namedViews.retainAll(mSharedElementTargetNames);
1177            } else {
1178                namedViews = remapNames(mSharedElementSourceNames, mSharedElementTargetNames,
1179                        namedViews);
1180            }
1181        }
1182
1183        if (isBack) {
1184            if (outFragment.mEnterTransitionCallback != null) {
1185                outFragment.mEnterTransitionCallback.onMapSharedElements(
1186                        mSharedElementTargetNames, namedViews);
1187            }
1188            setBackNameOverrides(state, namedViews, false);
1189        } else {
1190            if (outFragment.mExitTransitionCallback != null) {
1191                outFragment.mExitTransitionCallback.onMapSharedElements(
1192                        mSharedElementTargetNames, namedViews);
1193            }
1194            setNameOverrides(state, namedViews, false);
1195        }
1196
1197        return namedViews;
1198    }
1199
1200    /**
1201     * Configures custom transitions for a specific fragment container.
1202     *
1203     * @param containerId The container ID of the fragments to configure the transition for.
1204     * @param state The Transition State keeping track of the executing transitions.
1205     * @param firstOutFragments The list of first fragments to be removed, keyed on the
1206     *                          container ID.
1207     * @param lastInFragments The list of last fragments to be added, keyed on the
1208     *                        container ID.
1209     * @param isBack true if this is popping the back stack or false if this is a
1210     *               forward operation.
1211     */
1212    private boolean configureTransitions(int containerId, TransitionState state, boolean isBack,
1213            SparseArray<Fragment> firstOutFragments, SparseArray<Fragment> lastInFragments) {
1214        ViewGroup sceneRoot = (ViewGroup) mManager.mContainer.onFindViewById(containerId);
1215        if (sceneRoot == null) {
1216            return false;
1217        }
1218        final Fragment inFragment = lastInFragments.get(containerId);
1219        Fragment outFragment = firstOutFragments.get(containerId);
1220
1221        Object enterTransition = getEnterTransition(inFragment, isBack);
1222        Object sharedElementTransition = getSharedElementTransition(inFragment, outFragment,
1223                isBack);
1224        Object exitTransition = getExitTransition(outFragment, isBack);
1225        ArrayMap<String, View> namedViews = null;
1226        ArrayList<View> sharedElementTargets = new ArrayList<View>();
1227        if (sharedElementTransition != null) {
1228            namedViews = remapSharedElements(state, outFragment, isBack);
1229            if (namedViews.isEmpty()) {
1230                sharedElementTransition = null;
1231                namedViews = null;
1232            } else {
1233                // Notify the start of the transition.
1234                SharedElementCallback callback = isBack ?
1235                        outFragment.mEnterTransitionCallback :
1236                        inFragment.mEnterTransitionCallback;
1237                if (callback != null) {
1238                    ArrayList<String> names = new ArrayList<String>(namedViews.keySet());
1239                    ArrayList<View> views = new ArrayList<View>(namedViews.values());
1240                    callback.onSharedElementStart(names, views, null);
1241                }
1242                prepareSharedElementTransition(state, sceneRoot, sharedElementTransition,
1243                        inFragment, outFragment, isBack, sharedElementTargets, enterTransition,
1244                        exitTransition);
1245            }
1246        }
1247        if (enterTransition == null && sharedElementTransition == null &&
1248                exitTransition == null) {
1249            return false; // no transitions!
1250        }
1251
1252        ArrayList<View> exitingViews = new ArrayList<View>();
1253        exitTransition = captureExitingViews(exitTransition, outFragment, exitingViews,
1254                namedViews, state.nonExistentView);
1255
1256        // Set the epicenter of the exit transition
1257        if (mSharedElementTargetNames != null && namedViews != null) {
1258            View epicenterView = namedViews.get(mSharedElementTargetNames.get(0));
1259            if (epicenterView != null) {
1260                if (exitTransition != null) {
1261                    FragmentTransitionCompat21.setEpicenter(exitTransition, epicenterView);
1262                }
1263                if (sharedElementTransition != null) {
1264                    FragmentTransitionCompat21.setEpicenter(sharedElementTransition,
1265                            epicenterView);
1266                }
1267            }
1268        }
1269
1270        FragmentTransitionCompat21.ViewRetriever viewRetriever =
1271                new FragmentTransitionCompat21.ViewRetriever() {
1272                    @Override
1273                    public View getView() {
1274                        return inFragment.getView();
1275                    }
1276                };
1277
1278        ArrayList<View> enteringViews = new ArrayList<View>();
1279        ArrayMap<String, View> renamedViews = new ArrayMap<String, View>();
1280
1281        boolean allowOverlap = true;
1282        if (inFragment != null) {
1283            allowOverlap = isBack ? inFragment.getAllowReturnTransitionOverlap() :
1284                    inFragment.getAllowEnterTransitionOverlap();
1285        }
1286        Object transition = FragmentTransitionCompat21.mergeTransitions(enterTransition,
1287                exitTransition, sharedElementTransition, allowOverlap);
1288
1289        if (transition != null) {
1290            FragmentTransitionCompat21.addTransitionTargets(enterTransition,
1291                    sharedElementTransition, exitTransition, sceneRoot, viewRetriever,
1292                    state.nonExistentView, state.enteringEpicenterView, state.nameOverrides,
1293                    enteringViews, exitingViews, namedViews, renamedViews, sharedElementTargets);
1294            excludeHiddenFragmentsAfterEnter(sceneRoot, state, containerId, transition);
1295
1296            // We want to exclude hidden views later, so we need a non-null list in the
1297            // transition now.
1298            FragmentTransitionCompat21.excludeTarget(transition, state.nonExistentView, true);
1299            // Now exclude all currently hidden fragments.
1300            excludeHiddenFragments(state, containerId, transition);
1301
1302            FragmentTransitionCompat21.beginDelayedTransition(sceneRoot, transition);
1303
1304            FragmentTransitionCompat21.cleanupTransitions(sceneRoot, state.nonExistentView,
1305                    enterTransition, enteringViews, exitTransition, exitingViews,
1306                    sharedElementTransition, sharedElementTargets,
1307                    transition, state.hiddenFragmentViews, renamedViews);
1308        }
1309        return transition != null;
1310    }
1311
1312    private void prepareSharedElementTransition(final TransitionState state, final View sceneRoot,
1313            final Object sharedElementTransition, final Fragment inFragment,
1314            final Fragment outFragment, final boolean isBack,
1315            final ArrayList<View> sharedElementTargets, final Object enterTransition,
1316            final Object exitTransition) {
1317        if (sharedElementTransition != null) {
1318            sceneRoot.getViewTreeObserver().addOnPreDrawListener(
1319                    new ViewTreeObserver.OnPreDrawListener() {
1320                @Override
1321                public boolean onPreDraw() {
1322                    sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
1323
1324                    // Remove the exclude for the shared elements from the exiting fragment.
1325                    FragmentTransitionCompat21.removeTargets(sharedElementTransition,
1326                            sharedElementTargets);
1327                    // keep the nonExistentView as excluded so the list doesn't get emptied
1328                    sharedElementTargets.remove(state.nonExistentView);
1329                    FragmentTransitionCompat21.excludeSharedElementViews(enterTransition,
1330                            exitTransition, sharedElementTransition, sharedElementTargets, false);
1331                    sharedElementTargets.clear();
1332
1333                    ArrayMap<String, View> namedViews = mapSharedElementsIn(
1334                            state, isBack, inFragment);
1335                    FragmentTransitionCompat21.setSharedElementTargets(sharedElementTransition,
1336                            state.nonExistentView, namedViews, sharedElementTargets);
1337
1338                    setEpicenterIn(namedViews, state);
1339
1340                    callSharedElementEnd(state, inFragment, outFragment, isBack,
1341                            namedViews);
1342
1343                    // Exclude the shared elements from the entering fragment.
1344                    FragmentTransitionCompat21.excludeSharedElementViews(enterTransition,
1345                            exitTransition, sharedElementTransition, sharedElementTargets, true);
1346                    return true;
1347                }
1348            });
1349        }
1350    }
1351
1352    void callSharedElementEnd(TransitionState state, Fragment inFragment,
1353            Fragment outFragment, boolean isBack, ArrayMap<String, View> namedViews) {
1354        SharedElementCallback sharedElementCallback = isBack ?
1355                outFragment.mEnterTransitionCallback :
1356                inFragment.mEnterTransitionCallback;
1357        if (sharedElementCallback != null) {
1358            ArrayList<String> names = new ArrayList<String>(namedViews.keySet());
1359            ArrayList<View> views = new ArrayList<View>(namedViews.values());
1360            sharedElementCallback.onSharedElementEnd(names, views, null);
1361        }
1362    }
1363
1364    void setEpicenterIn(ArrayMap<String, View> namedViews, TransitionState state) {
1365        if (mSharedElementTargetNames != null && !namedViews.isEmpty()) {
1366            // now we know the epicenter of the entering transition.
1367            View epicenter = namedViews
1368                    .get(mSharedElementTargetNames.get(0));
1369            if (epicenter != null) {
1370                state.enteringEpicenterView.epicenter = epicenter;
1371            }
1372        }
1373    }
1374
1375    ArrayMap<String, View> mapSharedElementsIn(TransitionState state,
1376            boolean isBack, Fragment inFragment) {
1377        // Now map the shared elements in the incoming fragment
1378        ArrayMap<String, View> namedViews = mapEnteringSharedElements(state, inFragment, isBack);
1379
1380        // remap shared elements and set the name mapping used
1381        // in the shared element transition.
1382        if (isBack) {
1383            if (inFragment.mExitTransitionCallback != null) {
1384                inFragment.mExitTransitionCallback.onMapSharedElements(
1385                        mSharedElementTargetNames, namedViews);
1386            }
1387            setBackNameOverrides(state, namedViews, true);
1388        } else {
1389            if (inFragment.mEnterTransitionCallback != null) {
1390                inFragment.mEnterTransitionCallback.onMapSharedElements(
1391                        mSharedElementTargetNames, namedViews);
1392            }
1393            setNameOverrides(state, namedViews, true);
1394        }
1395        return namedViews;
1396    }
1397
1398    /**
1399     * Remaps a name-to-View map, substituting different names for keys.
1400     *
1401     * @param inMap A list of keys found in the map, in the order in toGoInMap
1402     * @param toGoInMap A list of keys to use for the new map, in the order of inMap
1403     * @param namedViews The current mapping
1404     * @return A copy of namedViews with the keys coming from toGoInMap.
1405     */
1406    private static ArrayMap<String, View> remapNames(ArrayList<String> inMap,
1407            ArrayList<String> toGoInMap, ArrayMap<String, View> namedViews) {
1408        if (namedViews.isEmpty()) {
1409            return namedViews;
1410        }
1411        ArrayMap<String, View> remappedViews = new ArrayMap<String, View>();
1412        int numKeys = inMap.size();
1413        for (int i = 0; i < numKeys; i++) {
1414            View view = namedViews.get(inMap.get(i));
1415            if (view != null) {
1416                remappedViews.put(toGoInMap.get(i), view);
1417            }
1418        }
1419        return remappedViews;
1420    }
1421
1422    /**
1423     * Maps shared elements to views in the entering fragment.
1424     *
1425     * @param state The transition State as returned from {@link #beginTransition(
1426     * android.util.SparseArray, android.util.SparseArray, boolean)}.
1427     * @param inFragment The last fragment to be added.
1428     * @param isBack true if this is popping the back stack or false if this is a
1429     *               forward operation.
1430     */
1431    private ArrayMap<String, View> mapEnteringSharedElements(TransitionState state,
1432            Fragment inFragment, boolean isBack) {
1433        ArrayMap<String, View> namedViews = new ArrayMap<String, View>();
1434        View root = inFragment.getView();
1435        if (root != null) {
1436            if (mSharedElementSourceNames != null) {
1437                FragmentTransitionCompat21.findNamedViews(namedViews, root);
1438                if (isBack) {
1439                    namedViews = remapNames(mSharedElementSourceNames,
1440                            mSharedElementTargetNames, namedViews);
1441                } else {
1442                    namedViews.retainAll(mSharedElementTargetNames);
1443                }
1444            }
1445        }
1446        return namedViews;
1447    }
1448
1449    private void excludeHiddenFragmentsAfterEnter(final View sceneRoot, final TransitionState state,
1450            final int containerId, final Object transition) {
1451        sceneRoot.getViewTreeObserver().addOnPreDrawListener(
1452                new ViewTreeObserver.OnPreDrawListener() {
1453            @Override
1454            public boolean onPreDraw() {
1455                sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
1456                excludeHiddenFragments(state, containerId, transition);
1457                return true;
1458            }
1459        });
1460    }
1461
1462    void excludeHiddenFragments(TransitionState state, int containerId, Object transition) {
1463        if (mManager.mAdded != null) {
1464            for (int i = 0; i < mManager.mAdded.size(); i++) {
1465                Fragment fragment = mManager.mAdded.get(i);
1466                if (fragment.mView != null && fragment.mContainer != null &&
1467                        fragment.mContainerId == containerId) {
1468                    if (fragment.mHidden) {
1469                        if (!state.hiddenFragmentViews.contains(fragment.mView)) {
1470                            FragmentTransitionCompat21.excludeTarget(transition, fragment.mView,
1471                                    true);
1472                            state.hiddenFragmentViews.add(fragment.mView);
1473                        }
1474                    } else {
1475                        FragmentTransitionCompat21.excludeTarget(transition, fragment.mView,
1476                                false);
1477                        state.hiddenFragmentViews.remove(fragment.mView);
1478                    }
1479                }
1480            }
1481        }
1482    }
1483
1484    private static void setNameOverride(ArrayMap<String, String> overrides,
1485            String source, String target) {
1486        if (source != null && target != null) {
1487            for (int index = 0; index < overrides.size(); index++) {
1488                if (source.equals(overrides.valueAt(index))) {
1489                    overrides.setValueAt(index, target);
1490                    return;
1491                }
1492            }
1493            overrides.put(source, target);
1494        }
1495    }
1496
1497    private static void setNameOverrides(TransitionState state, ArrayList<String> sourceNames,
1498            ArrayList<String> targetNames) {
1499        if (sourceNames != null) {
1500            for (int i = 0; i < sourceNames.size(); i++) {
1501                String source = sourceNames.get(i);
1502                String target = targetNames.get(i);
1503                setNameOverride(state.nameOverrides, source, target);
1504            }
1505        }
1506    }
1507
1508    private void setBackNameOverrides(TransitionState state, ArrayMap<String, View> namedViews,
1509            boolean isEnd) {
1510        int count = mSharedElementTargetNames == null ? 0 : mSharedElementTargetNames.size();
1511        for (int i = 0; i < count; i++) {
1512            String source = mSharedElementSourceNames.get(i);
1513            String originalTarget = mSharedElementTargetNames.get(i);
1514            View view = namedViews.get(originalTarget);
1515            if (view != null) {
1516                String target = FragmentTransitionCompat21.getTransitionName(view);
1517                if (isEnd) {
1518                    setNameOverride(state.nameOverrides, source, target);
1519                } else {
1520                    setNameOverride(state.nameOverrides, target, source);
1521                }
1522            }
1523        }
1524    }
1525
1526    private void setNameOverrides(TransitionState state, ArrayMap<String, View> namedViews,
1527            boolean isEnd) {
1528        int count = namedViews.size();
1529        for (int i = 0; i < count; i++) {
1530            String source = namedViews.keyAt(i);
1531            String target = FragmentTransitionCompat21.getTransitionName(namedViews.valueAt(i));
1532            if (isEnd) {
1533                setNameOverride(state.nameOverrides, source, target);
1534            } else {
1535                setNameOverride(state.nameOverrides, target, source);
1536            }
1537        }
1538    }
1539
1540    public class TransitionState {
1541        public ArrayMap<String, String> nameOverrides = new ArrayMap<String, String>();
1542        public ArrayList<View> hiddenFragmentViews = new ArrayList<View>();
1543
1544        public FragmentTransitionCompat21.EpicenterView enteringEpicenterView =
1545                new FragmentTransitionCompat21.EpicenterView();
1546        public View nonExistentView;
1547    }
1548}
1549