1/*
2 * Copyright (C) 2010 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.app;
18
19import android.graphics.Rect;
20import android.os.Build;
21import android.os.Parcel;
22import android.os.Parcelable;
23import android.text.TextUtils;
24import android.transition.Transition;
25import android.transition.TransitionManager;
26import android.transition.TransitionSet;
27import android.util.ArrayMap;
28import android.util.Log;
29import android.util.LogWriter;
30import android.util.SparseArray;
31import android.view.View;
32import android.view.ViewGroup;
33import android.view.ViewTreeObserver;
34
35import com.android.internal.util.FastPrintWriter;
36
37import java.io.FileDescriptor;
38import java.io.PrintWriter;
39import java.util.ArrayList;
40import java.util.List;
41
42final class BackStackState implements Parcelable {
43    final int[] mOps;
44    final int mTransition;
45    final int mTransitionStyle;
46    final String mName;
47    final int mIndex;
48    final int mBreadCrumbTitleRes;
49    final CharSequence mBreadCrumbTitleText;
50    final int mBreadCrumbShortTitleRes;
51    final CharSequence mBreadCrumbShortTitleText;
52    final ArrayList<String> mSharedElementSourceNames;
53    final ArrayList<String> mSharedElementTargetNames;
54
55    public BackStackState(FragmentManagerImpl fm, BackStackRecord bse) {
56        int numRemoved = 0;
57        BackStackRecord.Op op = bse.mHead;
58        while (op != null) {
59            if (op.removed != null) {
60                numRemoved += op.removed.size();
61            }
62            op = op.next;
63        }
64        mOps = new int[bse.mNumOp * 7 + numRemoved];
65
66        if (!bse.mAddToBackStack) {
67            throw new IllegalStateException("Not on back stack");
68        }
69
70        op = bse.mHead;
71        int pos = 0;
72        while (op != null) {
73            mOps[pos++] = op.cmd;
74            mOps[pos++] = op.fragment != null ? op.fragment.mIndex : -1;
75            mOps[pos++] = op.enterAnim;
76            mOps[pos++] = op.exitAnim;
77            mOps[pos++] = op.popEnterAnim;
78            mOps[pos++] = op.popExitAnim;
79            if (op.removed != null) {
80                final int N = op.removed.size();
81                mOps[pos++] = N;
82                for (int i = 0; i < N; i++) {
83                    mOps[pos++] = op.removed.get(i).mIndex;
84                }
85            } else {
86                mOps[pos++] = 0;
87            }
88            op = op.next;
89        }
90        mTransition = bse.mTransition;
91        mTransitionStyle = bse.mTransitionStyle;
92        mName = bse.mName;
93        mIndex = bse.mIndex;
94        mBreadCrumbTitleRes = bse.mBreadCrumbTitleRes;
95        mBreadCrumbTitleText = bse.mBreadCrumbTitleText;
96        mBreadCrumbShortTitleRes = bse.mBreadCrumbShortTitleRes;
97        mBreadCrumbShortTitleText = bse.mBreadCrumbShortTitleText;
98        mSharedElementSourceNames = bse.mSharedElementSourceNames;
99        mSharedElementTargetNames = bse.mSharedElementTargetNames;
100    }
101
102    public BackStackState(Parcel in) {
103        mOps = in.createIntArray();
104        mTransition = in.readInt();
105        mTransitionStyle = in.readInt();
106        mName = in.readString();
107        mIndex = in.readInt();
108        mBreadCrumbTitleRes = in.readInt();
109        mBreadCrumbTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
110        mBreadCrumbShortTitleRes = in.readInt();
111        mBreadCrumbShortTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
112        mSharedElementSourceNames = in.createStringArrayList();
113        mSharedElementTargetNames = in.createStringArrayList();
114    }
115
116    public BackStackRecord instantiate(FragmentManagerImpl fm) {
117        BackStackRecord bse = new BackStackRecord(fm);
118        int pos = 0;
119        int num = 0;
120        while (pos < mOps.length) {
121            BackStackRecord.Op op = new BackStackRecord.Op();
122            op.cmd = mOps[pos++];
123            if (FragmentManagerImpl.DEBUG) {
124                Log.v(FragmentManagerImpl.TAG,
125                        "Instantiate " + bse + " op #" + num + " base fragment #" + mOps[pos]);
126            }
127            int findex = mOps[pos++];
128            if (findex >= 0) {
129                Fragment f = fm.mActive.get(findex);
130                op.fragment = f;
131            } else {
132                op.fragment = null;
133            }
134            op.enterAnim = mOps[pos++];
135            op.exitAnim = mOps[pos++];
136            op.popEnterAnim = mOps[pos++];
137            op.popExitAnim = mOps[pos++];
138            final int N = mOps[pos++];
139            if (N > 0) {
140                op.removed = new ArrayList<Fragment>(N);
141                for (int i = 0; i < N; i++) {
142                    if (FragmentManagerImpl.DEBUG) {
143                        Log.v(FragmentManagerImpl.TAG,
144                                "Instantiate " + bse + " set remove fragment #" + mOps[pos]);
145                    }
146                    Fragment r = fm.mActive.get(mOps[pos++]);
147                    op.removed.add(r);
148                }
149            }
150            bse.mEnterAnim = op.enterAnim;
151            bse.mExitAnim = op.exitAnim;
152            bse.mPopEnterAnim = op.popEnterAnim;
153            bse.mPopExitAnim = op.popExitAnim;
154            bse.addOp(op);
155            num++;
156        }
157        bse.mTransition = mTransition;
158        bse.mTransitionStyle = mTransitionStyle;
159        bse.mName = mName;
160        bse.mIndex = mIndex;
161        bse.mAddToBackStack = true;
162        bse.mBreadCrumbTitleRes = mBreadCrumbTitleRes;
163        bse.mBreadCrumbTitleText = mBreadCrumbTitleText;
164        bse.mBreadCrumbShortTitleRes = mBreadCrumbShortTitleRes;
165        bse.mBreadCrumbShortTitleText = mBreadCrumbShortTitleText;
166        bse.mSharedElementSourceNames = mSharedElementSourceNames;
167        bse.mSharedElementTargetNames = mSharedElementTargetNames;
168        bse.bumpBackStackNesting(1);
169        return bse;
170    }
171
172    public int describeContents() {
173        return 0;
174    }
175
176    public void writeToParcel(Parcel dest, int flags) {
177        dest.writeIntArray(mOps);
178        dest.writeInt(mTransition);
179        dest.writeInt(mTransitionStyle);
180        dest.writeString(mName);
181        dest.writeInt(mIndex);
182        dest.writeInt(mBreadCrumbTitleRes);
183        TextUtils.writeToParcel(mBreadCrumbTitleText, dest, 0);
184        dest.writeInt(mBreadCrumbShortTitleRes);
185        TextUtils.writeToParcel(mBreadCrumbShortTitleText, dest, 0);
186        dest.writeStringList(mSharedElementSourceNames);
187        dest.writeStringList(mSharedElementTargetNames);
188    }
189
190    public static final Parcelable.Creator<BackStackState> CREATOR
191            = new Parcelable.Creator<BackStackState>() {
192        public BackStackState createFromParcel(Parcel in) {
193            return new BackStackState(in);
194        }
195
196        public BackStackState[] newArray(int size) {
197            return new BackStackState[size];
198        }
199    };
200}
201
202/**
203 * @hide Entry of an operation on the fragment back stack.
204 */
205final class BackStackRecord extends FragmentTransaction implements
206        FragmentManager.BackStackEntry, Runnable {
207    static final String TAG = FragmentManagerImpl.TAG;
208
209    final FragmentManagerImpl mManager;
210
211    static final int OP_NULL = 0;
212    static final int OP_ADD = 1;
213    static final int OP_REPLACE = 2;
214    static final int OP_REMOVE = 3;
215    static final int OP_HIDE = 4;
216    static final int OP_SHOW = 5;
217    static final int OP_DETACH = 6;
218    static final int OP_ATTACH = 7;
219
220    static final class Op {
221        Op next;
222        Op prev;
223        int cmd;
224        Fragment fragment;
225        int enterAnim;
226        int exitAnim;
227        int popEnterAnim;
228        int popExitAnim;
229        ArrayList<Fragment> removed;
230    }
231
232    Op mHead;
233    Op mTail;
234    int mNumOp;
235    int mEnterAnim;
236    int mExitAnim;
237    int mPopEnterAnim;
238    int mPopExitAnim;
239    int mTransition;
240    int mTransitionStyle;
241    boolean mAddToBackStack;
242    boolean mAllowAddToBackStack = true;
243    String mName;
244    boolean mCommitted;
245    int mIndex = -1;
246
247    int mBreadCrumbTitleRes;
248    CharSequence mBreadCrumbTitleText;
249    int mBreadCrumbShortTitleRes;
250    CharSequence mBreadCrumbShortTitleText;
251
252    ArrayList<String> mSharedElementSourceNames;
253    ArrayList<String> mSharedElementTargetNames;
254
255    @Override
256    public String toString() {
257        StringBuilder sb = new StringBuilder(128);
258        sb.append("BackStackEntry{");
259        sb.append(Integer.toHexString(System.identityHashCode(this)));
260        if (mIndex >= 0) {
261            sb.append(" #");
262            sb.append(mIndex);
263        }
264        if (mName != null) {
265            sb.append(" ");
266            sb.append(mName);
267        }
268        sb.append("}");
269        return sb.toString();
270    }
271
272    public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
273        dump(prefix, writer, true);
274    }
275
276    void dump(String prefix, PrintWriter writer, boolean full) {
277        if (full) {
278            writer.print(prefix);
279            writer.print("mName=");
280            writer.print(mName);
281            writer.print(" mIndex=");
282            writer.print(mIndex);
283            writer.print(" mCommitted=");
284            writer.println(mCommitted);
285            if (mTransition != FragmentTransaction.TRANSIT_NONE) {
286                writer.print(prefix);
287                writer.print("mTransition=#");
288                writer.print(Integer.toHexString(mTransition));
289                writer.print(" mTransitionStyle=#");
290                writer.println(Integer.toHexString(mTransitionStyle));
291            }
292            if (mEnterAnim != 0 || mExitAnim != 0) {
293                writer.print(prefix);
294                writer.print("mEnterAnim=#");
295                writer.print(Integer.toHexString(mEnterAnim));
296                writer.print(" mExitAnim=#");
297                writer.println(Integer.toHexString(mExitAnim));
298            }
299            if (mPopEnterAnim != 0 || mPopExitAnim != 0) {
300                writer.print(prefix);
301                writer.print("mPopEnterAnim=#");
302                writer.print(Integer.toHexString(mPopEnterAnim));
303                writer.print(" mPopExitAnim=#");
304                writer.println(Integer.toHexString(mPopExitAnim));
305            }
306            if (mBreadCrumbTitleRes != 0 || mBreadCrumbTitleText != null) {
307                writer.print(prefix);
308                writer.print("mBreadCrumbTitleRes=#");
309                writer.print(Integer.toHexString(mBreadCrumbTitleRes));
310                writer.print(" mBreadCrumbTitleText=");
311                writer.println(mBreadCrumbTitleText);
312            }
313            if (mBreadCrumbShortTitleRes != 0 || mBreadCrumbShortTitleText != null) {
314                writer.print(prefix);
315                writer.print("mBreadCrumbShortTitleRes=#");
316                writer.print(Integer.toHexString(mBreadCrumbShortTitleRes));
317                writer.print(" mBreadCrumbShortTitleText=");
318                writer.println(mBreadCrumbShortTitleText);
319            }
320        }
321
322        if (mHead != null) {
323            writer.print(prefix);
324            writer.println("Operations:");
325            String innerPrefix = prefix + "    ";
326            Op op = mHead;
327            int num = 0;
328            while (op != null) {
329                String cmdStr;
330                switch (op.cmd) {
331                    case OP_NULL:
332                        cmdStr = "NULL";
333                        break;
334                    case OP_ADD:
335                        cmdStr = "ADD";
336                        break;
337                    case OP_REPLACE:
338                        cmdStr = "REPLACE";
339                        break;
340                    case OP_REMOVE:
341                        cmdStr = "REMOVE";
342                        break;
343                    case OP_HIDE:
344                        cmdStr = "HIDE";
345                        break;
346                    case OP_SHOW:
347                        cmdStr = "SHOW";
348                        break;
349                    case OP_DETACH:
350                        cmdStr = "DETACH";
351                        break;
352                    case OP_ATTACH:
353                        cmdStr = "ATTACH";
354                        break;
355                    default:
356                        cmdStr = "cmd=" + op.cmd;
357                        break;
358                }
359                writer.print(prefix);
360                writer.print("  Op #");
361                writer.print(num);
362                writer.print(": ");
363                writer.print(cmdStr);
364                writer.print(" ");
365                writer.println(op.fragment);
366                if (full) {
367                    if (op.enterAnim != 0 || op.exitAnim != 0) {
368                        writer.print(innerPrefix);
369                        writer.print("enterAnim=#");
370                        writer.print(Integer.toHexString(op.enterAnim));
371                        writer.print(" exitAnim=#");
372                        writer.println(Integer.toHexString(op.exitAnim));
373                    }
374                    if (op.popEnterAnim != 0 || op.popExitAnim != 0) {
375                        writer.print(innerPrefix);
376                        writer.print("popEnterAnim=#");
377                        writer.print(Integer.toHexString(op.popEnterAnim));
378                        writer.print(" popExitAnim=#");
379                        writer.println(Integer.toHexString(op.popExitAnim));
380                    }
381                }
382                if (op.removed != null && op.removed.size() > 0) {
383                    for (int i = 0; i < op.removed.size(); i++) {
384                        writer.print(innerPrefix);
385                        if (op.removed.size() == 1) {
386                            writer.print("Removed: ");
387                        } else {
388                            if (i == 0) {
389                                writer.println("Removed:");
390                            }
391                            writer.print(innerPrefix);
392                            writer.print("  #");
393                            writer.print(i);
394                            writer.print(": ");
395                        }
396                        writer.println(op.removed.get(i));
397                    }
398                }
399                op = op.next;
400                num++;
401            }
402        }
403    }
404
405    public BackStackRecord(FragmentManagerImpl manager) {
406        mManager = manager;
407    }
408
409    public int getId() {
410        return mIndex;
411    }
412
413    public int getBreadCrumbTitleRes() {
414        return mBreadCrumbTitleRes;
415    }
416
417    public int getBreadCrumbShortTitleRes() {
418        return mBreadCrumbShortTitleRes;
419    }
420
421    public CharSequence getBreadCrumbTitle() {
422        if (mBreadCrumbTitleRes != 0) {
423            return mManager.mHost.getContext().getText(mBreadCrumbTitleRes);
424        }
425        return mBreadCrumbTitleText;
426    }
427
428    public CharSequence getBreadCrumbShortTitle() {
429        if (mBreadCrumbShortTitleRes != 0) {
430            return mManager.mHost.getContext().getText(mBreadCrumbShortTitleRes);
431        }
432        return mBreadCrumbShortTitleText;
433    }
434
435    void addOp(Op op) {
436        if (mHead == null) {
437            mHead = mTail = op;
438        } else {
439            op.prev = mTail;
440            mTail.next = op;
441            mTail = op;
442        }
443        op.enterAnim = mEnterAnim;
444        op.exitAnim = mExitAnim;
445        op.popEnterAnim = mPopEnterAnim;
446        op.popExitAnim = mPopExitAnim;
447        mNumOp++;
448    }
449
450    public FragmentTransaction add(Fragment fragment, String tag) {
451        doAddOp(0, fragment, tag, OP_ADD);
452        return this;
453    }
454
455    public FragmentTransaction add(int containerViewId, Fragment fragment) {
456        doAddOp(containerViewId, fragment, null, OP_ADD);
457        return this;
458    }
459
460    public FragmentTransaction add(int containerViewId, Fragment fragment, String tag) {
461        doAddOp(containerViewId, fragment, tag, OP_ADD);
462        return this;
463    }
464
465    private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) {
466        fragment.mFragmentManager = mManager;
467
468        if (tag != null) {
469            if (fragment.mTag != null && !tag.equals(fragment.mTag)) {
470                throw new IllegalStateException("Can't change tag of fragment "
471                        + fragment + ": was " + fragment.mTag
472                        + " now " + tag);
473            }
474            fragment.mTag = tag;
475        }
476
477        if (containerViewId != 0) {
478            if (containerViewId == View.NO_ID) {
479                throw new IllegalArgumentException("Can't add fragment "
480                        + fragment + " with tag " + tag + " to container view with no id");
481            }
482            if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) {
483                throw new IllegalStateException("Can't change container ID of fragment "
484                        + fragment + ": was " + fragment.mFragmentId
485                        + " now " + containerViewId);
486            }
487            fragment.mContainerId = fragment.mFragmentId = containerViewId;
488        }
489
490        Op op = new Op();
491        op.cmd = opcmd;
492        op.fragment = fragment;
493        addOp(op);
494    }
495
496    public FragmentTransaction replace(int containerViewId, Fragment fragment) {
497        return replace(containerViewId, fragment, null);
498    }
499
500    public FragmentTransaction replace(int containerViewId, Fragment fragment, String tag) {
501        if (containerViewId == 0) {
502            throw new IllegalArgumentException("Must use non-zero containerViewId");
503        }
504
505        doAddOp(containerViewId, fragment, tag, OP_REPLACE);
506        return this;
507    }
508
509    public FragmentTransaction remove(Fragment fragment) {
510        Op op = new Op();
511        op.cmd = OP_REMOVE;
512        op.fragment = fragment;
513        addOp(op);
514
515        return this;
516    }
517
518    public FragmentTransaction hide(Fragment fragment) {
519        Op op = new Op();
520        op.cmd = OP_HIDE;
521        op.fragment = fragment;
522        addOp(op);
523
524        return this;
525    }
526
527    public FragmentTransaction show(Fragment fragment) {
528        Op op = new Op();
529        op.cmd = OP_SHOW;
530        op.fragment = fragment;
531        addOp(op);
532
533        return this;
534    }
535
536    public FragmentTransaction detach(Fragment fragment) {
537        Op op = new Op();
538        op.cmd = OP_DETACH;
539        op.fragment = fragment;
540        addOp(op);
541
542        return this;
543    }
544
545    public FragmentTransaction attach(Fragment fragment) {
546        Op op = new Op();
547        op.cmd = OP_ATTACH;
548        op.fragment = fragment;
549        addOp(op);
550
551        return this;
552    }
553
554    public FragmentTransaction setCustomAnimations(int enter, int exit) {
555        return setCustomAnimations(enter, exit, 0, 0);
556    }
557
558    public FragmentTransaction setCustomAnimations(int enter, int exit,
559            int popEnter, int popExit) {
560        mEnterAnim = enter;
561        mExitAnim = exit;
562        mPopEnterAnim = popEnter;
563        mPopExitAnim = popExit;
564        return this;
565    }
566
567    public FragmentTransaction setTransition(int transition) {
568        mTransition = transition;
569        return this;
570    }
571
572    @Override
573    public FragmentTransaction addSharedElement(View sharedElement, String name) {
574        String transitionName = sharedElement.getTransitionName();
575        if (transitionName == null) {
576            throw new IllegalArgumentException("Unique transitionNames are required for all" +
577                    " sharedElements");
578        }
579        if (mSharedElementSourceNames == null) {
580            mSharedElementSourceNames = new ArrayList<String>();
581            mSharedElementTargetNames = new ArrayList<String>();
582        }
583        mSharedElementSourceNames.add(transitionName);
584        mSharedElementTargetNames.add(name);
585        return this;
586    }
587
588    public FragmentTransaction setTransitionStyle(int styleRes) {
589        mTransitionStyle = styleRes;
590        return this;
591    }
592
593    public FragmentTransaction addToBackStack(String name) {
594        if (!mAllowAddToBackStack) {
595            throw new IllegalStateException(
596                    "This FragmentTransaction is not allowed to be added to the back stack.");
597        }
598        mAddToBackStack = true;
599        mName = name;
600        return this;
601    }
602
603    public boolean isAddToBackStackAllowed() {
604        return mAllowAddToBackStack;
605    }
606
607    public FragmentTransaction disallowAddToBackStack() {
608        if (mAddToBackStack) {
609            throw new IllegalStateException(
610                    "This transaction is already being added to the back stack");
611        }
612        mAllowAddToBackStack = false;
613        return this;
614    }
615
616    public FragmentTransaction setBreadCrumbTitle(int res) {
617        mBreadCrumbTitleRes = res;
618        mBreadCrumbTitleText = null;
619        return this;
620    }
621
622    public FragmentTransaction setBreadCrumbTitle(CharSequence text) {
623        mBreadCrumbTitleRes = 0;
624        mBreadCrumbTitleText = text;
625        return this;
626    }
627
628    public FragmentTransaction setBreadCrumbShortTitle(int res) {
629        mBreadCrumbShortTitleRes = res;
630        mBreadCrumbShortTitleText = null;
631        return this;
632    }
633
634    public FragmentTransaction setBreadCrumbShortTitle(CharSequence text) {
635        mBreadCrumbShortTitleRes = 0;
636        mBreadCrumbShortTitleText = text;
637        return this;
638    }
639
640    void bumpBackStackNesting(int amt) {
641        if (!mAddToBackStack) {
642            return;
643        }
644        if (FragmentManagerImpl.DEBUG) {
645            Log.v(TAG, "Bump nesting in " + this
646                    + " by " + amt);
647        }
648        Op op = mHead;
649        while (op != null) {
650            if (op.fragment != null) {
651                op.fragment.mBackStackNesting += amt;
652                if (FragmentManagerImpl.DEBUG) {
653                    Log.v(TAG, "Bump nesting of "
654                            + op.fragment + " to " + op.fragment.mBackStackNesting);
655                }
656            }
657            if (op.removed != null) {
658                for (int i = op.removed.size() - 1; i >= 0; i--) {
659                    Fragment r = op.removed.get(i);
660                    r.mBackStackNesting += amt;
661                    if (FragmentManagerImpl.DEBUG) {
662                        Log.v(TAG, "Bump nesting of "
663                                + r + " to " + r.mBackStackNesting);
664                    }
665                }
666            }
667            op = op.next;
668        }
669    }
670
671    public int commit() {
672        return commitInternal(false);
673    }
674
675    public int commitAllowingStateLoss() {
676        return commitInternal(true);
677    }
678
679    @Override
680    public void commitNow() {
681        disallowAddToBackStack();
682        mManager.execSingleAction(this, false);
683    }
684
685    @Override
686    public void commitNowAllowingStateLoss() {
687        disallowAddToBackStack();
688        mManager.execSingleAction(this, true);
689    }
690
691    int commitInternal(boolean allowStateLoss) {
692        if (mCommitted) {
693            throw new IllegalStateException("commit already called");
694        }
695        if (FragmentManagerImpl.DEBUG) {
696            Log.v(TAG, "Commit: " + this);
697            LogWriter logw = new LogWriter(Log.VERBOSE, TAG);
698            PrintWriter pw = new FastPrintWriter(logw, false, 1024);
699            dump("  ", null, pw, null);
700            pw.flush();
701        }
702        mCommitted = true;
703        if (mAddToBackStack) {
704            mIndex = mManager.allocBackStackIndex(this);
705        } else {
706            mIndex = -1;
707        }
708        mManager.enqueueAction(this, allowStateLoss);
709        return mIndex;
710    }
711
712    public void run() {
713        if (FragmentManagerImpl.DEBUG) {
714            Log.v(TAG, "Run: " + this);
715        }
716
717        if (mAddToBackStack) {
718            if (mIndex < 0) {
719                throw new IllegalStateException("addToBackStack() called after commit()");
720            }
721        }
722
723        bumpBackStackNesting(1);
724
725        if (mManager.mCurState >= Fragment.CREATED) {
726            SparseArray<Fragment> firstOutFragments = new SparseArray<Fragment>();
727            SparseArray<Fragment> lastInFragments = new SparseArray<Fragment>();
728            calculateFragments(firstOutFragments, lastInFragments);
729            beginTransition(firstOutFragments, lastInFragments, false);
730        }
731
732        Op op = mHead;
733        while (op != null) {
734            switch (op.cmd) {
735                case OP_ADD: {
736                    Fragment f = op.fragment;
737                    f.mNextAnim = op.enterAnim;
738                    mManager.addFragment(f, false);
739                }
740                break;
741                case OP_REPLACE: {
742                    Fragment f = op.fragment;
743                    int containerId = f.mContainerId;
744                    if (mManager.mAdded != null) {
745                        for (int i = mManager.mAdded.size() - 1; i >= 0; i--) {
746                            Fragment old = mManager.mAdded.get(i);
747                            if (FragmentManagerImpl.DEBUG) {
748                                Log.v(TAG,
749                                        "OP_REPLACE: adding=" + f + " old=" + old);
750                            }
751                            if (old.mContainerId == containerId) {
752                                if (old == f) {
753                                    op.fragment = f = null;
754                                } else {
755                                    if (op.removed == null) {
756                                        op.removed = new ArrayList<Fragment>();
757                                    }
758                                    op.removed.add(old);
759                                    old.mNextAnim = op.exitAnim;
760                                    if (mAddToBackStack) {
761                                        old.mBackStackNesting += 1;
762                                        if (FragmentManagerImpl.DEBUG) {
763                                            Log.v(TAG, "Bump nesting of "
764                                                    + old + " to " + old.mBackStackNesting);
765                                        }
766                                    }
767                                    mManager.removeFragment(old, mTransition, mTransitionStyle);
768                                }
769                            }
770                        }
771                    }
772                    if (f != null) {
773                        f.mNextAnim = op.enterAnim;
774                        mManager.addFragment(f, false);
775                    }
776                }
777                break;
778                case OP_REMOVE: {
779                    Fragment f = op.fragment;
780                    f.mNextAnim = op.exitAnim;
781                    mManager.removeFragment(f, mTransition, mTransitionStyle);
782                }
783                break;
784                case OP_HIDE: {
785                    Fragment f = op.fragment;
786                    f.mNextAnim = op.exitAnim;
787                    mManager.hideFragment(f, mTransition, mTransitionStyle);
788                }
789                break;
790                case OP_SHOW: {
791                    Fragment f = op.fragment;
792                    f.mNextAnim = op.enterAnim;
793                    mManager.showFragment(f, mTransition, mTransitionStyle);
794                }
795                break;
796                case OP_DETACH: {
797                    Fragment f = op.fragment;
798                    f.mNextAnim = op.exitAnim;
799                    mManager.detachFragment(f, mTransition, mTransitionStyle);
800                }
801                break;
802                case OP_ATTACH: {
803                    Fragment f = op.fragment;
804                    f.mNextAnim = op.enterAnim;
805                    mManager.attachFragment(f, mTransition, mTransitionStyle);
806                }
807                break;
808                default: {
809                    throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
810                }
811            }
812
813            op = op.next;
814        }
815
816        mManager.moveToState(mManager.mCurState, mTransition,
817                mTransitionStyle, true);
818
819        if (mAddToBackStack) {
820            mManager.addBackStackState(this);
821        }
822    }
823
824    private static void setFirstOut(SparseArray<Fragment> firstOutFragments,
825                            SparseArray<Fragment> lastInFragments, Fragment fragment) {
826        if (fragment != null) {
827            int containerId = fragment.mContainerId;
828            if (containerId != 0 && !fragment.isHidden()) {
829                if (fragment.isAdded() && fragment.getView() != null
830                        && firstOutFragments.get(containerId) == null) {
831                    firstOutFragments.put(containerId, fragment);
832                }
833                if (lastInFragments.get(containerId) == fragment) {
834                    lastInFragments.remove(containerId);
835                }
836            }
837        }
838    }
839
840    private void setLastIn(SparseArray<Fragment> firstOutFragments,
841            SparseArray<Fragment> lastInFragments, Fragment fragment) {
842        if (fragment != null) {
843            int containerId = fragment.mContainerId;
844            if (containerId != 0) {
845                if (!fragment.isAdded()) {
846                    lastInFragments.put(containerId, fragment);
847                }
848                if (firstOutFragments.get(containerId) == fragment) {
849                    firstOutFragments.remove(containerId);
850                }
851            }
852            /**
853             * Ensure that fragments that are entering are at least at the CREATED state
854             * so that they may load Transitions using TransitionInflater.
855             */
856            if (fragment.mState < Fragment.CREATED && mManager.mCurState >= Fragment.CREATED &&
857                    mManager.mHost.getContext().getApplicationInfo().targetSdkVersion >=
858                    Build.VERSION_CODES.N) {
859                mManager.makeActive(fragment);
860                mManager.moveToState(fragment, Fragment.CREATED, 0, 0, false);
861            }
862        }
863    }
864
865    /**
866     * Finds the first removed fragment and last added fragments when going forward.
867     * If none of the fragments have transitions, then both lists will be empty.
868     *
869     * @param firstOutFragments The list of first fragments to be removed, keyed on the
870     *                          container ID. This list will be modified by the method.
871     * @param lastInFragments The list of last fragments to be added, keyed on the
872     *                        container ID. This list will be modified by the method.
873     */
874    private void calculateFragments(SparseArray<Fragment> firstOutFragments,
875            SparseArray<Fragment> lastInFragments) {
876        if (!mManager.mContainer.onHasView()) {
877            return; // nothing to see, so no transitions
878        }
879        Op op = mHead;
880        while (op != null) {
881            switch (op.cmd) {
882                case OP_ADD:
883                    setLastIn(firstOutFragments, lastInFragments, op.fragment);
884                    break;
885                case OP_REPLACE: {
886                    Fragment f = op.fragment;
887                    if (mManager.mAdded != null) {
888                        for (int i = 0; i < mManager.mAdded.size(); i++) {
889                            Fragment old = mManager.mAdded.get(i);
890                            if (f == null || old.mContainerId == f.mContainerId) {
891                                if (old == f) {
892                                    f = null;
893                                    lastInFragments.remove(old.mContainerId);
894                                } else {
895                                    setFirstOut(firstOutFragments, lastInFragments, old);
896                                }
897                            }
898                        }
899                    }
900                    setLastIn(firstOutFragments, lastInFragments, op.fragment);
901                    break;
902                }
903                case OP_REMOVE:
904                    setFirstOut(firstOutFragments, lastInFragments, op.fragment);
905                    break;
906                case OP_HIDE:
907                    setFirstOut(firstOutFragments, lastInFragments, op.fragment);
908                    break;
909                case OP_SHOW:
910                    setLastIn(firstOutFragments, lastInFragments, op.fragment);
911                    break;
912                case OP_DETACH:
913                    setFirstOut(firstOutFragments, lastInFragments, op.fragment);
914                    break;
915                case OP_ATTACH:
916                    setLastIn(firstOutFragments, lastInFragments, op.fragment);
917                    break;
918            }
919
920            op = op.next;
921        }
922    }
923
924    /**
925     * Finds the first removed fragment and last added fragments when popping the back stack.
926     * If none of the fragments have transitions, then both lists will be empty.
927     *
928     * @param firstOutFragments The list of first fragments to be removed, keyed on the
929     *                          container ID. This list will be modified by the method.
930     * @param lastInFragments The list of last fragments to be added, keyed on the
931     *                        container ID. This list will be modified by the method.
932     */
933    public void calculateBackFragments(SparseArray<Fragment> firstOutFragments,
934            SparseArray<Fragment> lastInFragments) {
935        if (!mManager.mContainer.onHasView()) {
936            return; // nothing to see, so no transitions
937        }
938        Op op = mTail;
939        while (op != null) {
940            switch (op.cmd) {
941                case OP_ADD:
942                    setFirstOut(firstOutFragments, lastInFragments, op.fragment);
943                    break;
944                case OP_REPLACE:
945                    if (op.removed != null) {
946                        for (int i = op.removed.size() - 1; i >= 0; i--) {
947                            setLastIn(firstOutFragments, lastInFragments, op.removed.get(i));
948                        }
949                    }
950                    setFirstOut(firstOutFragments, lastInFragments, op.fragment);
951                    break;
952                case OP_REMOVE:
953                    setLastIn(firstOutFragments, lastInFragments, op.fragment);
954                    break;
955                case OP_HIDE:
956                    setLastIn(firstOutFragments, lastInFragments, op.fragment);
957                    break;
958                case OP_SHOW:
959                    setFirstOut(firstOutFragments, lastInFragments, op.fragment);
960                    break;
961                case OP_DETACH:
962                    setLastIn(firstOutFragments, lastInFragments, op.fragment);
963                    break;
964                case OP_ATTACH:
965                    setFirstOut(firstOutFragments, lastInFragments, op.fragment);
966                    break;
967            }
968
969            op = op.prev;
970        }
971    }
972
973    /**
974     * When custom fragment transitions are used, this sets up the state for each transition
975     * and begins the transition. A different transition is started for each fragment container
976     * and consists of up to 3 different transitions: the exit transition, a shared element
977     * transition and an enter transition.
978     *
979     * <p>The exit transition operates against the leaf nodes of the first fragment
980     * with a view that was removed. If no such fragment was removed, then no exit
981     * transition is executed. The exit transition comes from the outgoing fragment.</p>
982     *
983     * <p>The enter transition operates against the last fragment that was added. If
984     * that fragment does not have a view or no fragment was added, then no enter
985     * transition is executed. The enter transition comes from the incoming fragment.</p>
986     *
987     * <p>The shared element transition operates against all views and comes either
988     * from the outgoing fragment or the incoming fragment, depending on whether this
989     * is going forward or popping the back stack. When going forward, the incoming
990     * fragment's enter shared element transition is used, but when going back, the
991     * outgoing fragment's return shared element transition is used. Shared element
992     * transitions only operate if there is both an incoming and outgoing fragment.</p>
993     *
994     * @param firstOutFragments The list of first fragments to be removed, keyed on the
995     *                          container ID.
996     * @param lastInFragments The list of last fragments to be added, keyed on the
997     *                        container ID.
998     * @param isBack true if this is popping the back stack or false if this is a
999     *               forward operation.
1000     * @return The TransitionState used to complete the operation of the transition
1001     * in {@link #setNameOverrides(android.app.BackStackRecord.TransitionState, java.util.ArrayList,
1002     * java.util.ArrayList)}.
1003     */
1004    private TransitionState beginTransition(SparseArray<Fragment> firstOutFragments,
1005            SparseArray<Fragment> lastInFragments, boolean isBack) {
1006        TransitionState state = new TransitionState();
1007
1008        // Adding a non-existent target view makes sure that the transitions don't target
1009        // any views by default. They'll only target the views we tell add. If we don't
1010        // add any, then no views will be targeted.
1011        state.nonExistentView = new View(mManager.mHost.getContext());
1012
1013        // Go over all leaving fragments.
1014        for (int i = 0; i < firstOutFragments.size(); i++) {
1015            int containerId = firstOutFragments.keyAt(i);
1016            configureTransitions(containerId, state, isBack, firstOutFragments,
1017                    lastInFragments);
1018        }
1019
1020        // Now go over all entering fragments that didn't have a leaving fragment.
1021        for (int i = 0; i < lastInFragments.size(); i++) {
1022            int containerId = lastInFragments.keyAt(i);
1023            if (firstOutFragments.get(containerId) == null) {
1024                configureTransitions(containerId, state, isBack, firstOutFragments,
1025                        lastInFragments);
1026            }
1027        }
1028        return state;
1029    }
1030
1031    private static Transition cloneTransition(Transition transition) {
1032        if (transition != null) {
1033            transition = transition.clone();
1034        }
1035        return transition;
1036    }
1037
1038    private static Transition getEnterTransition(Fragment inFragment, boolean isBack) {
1039        if (inFragment == null) {
1040            return null;
1041        }
1042        return cloneTransition(isBack ? inFragment.getReenterTransition() :
1043                inFragment.getEnterTransition());
1044    }
1045
1046    private static Transition getExitTransition(Fragment outFragment, boolean isBack) {
1047        if (outFragment == null) {
1048            return null;
1049        }
1050        return cloneTransition(isBack ? outFragment.getReturnTransition() :
1051                outFragment.getExitTransition());
1052    }
1053
1054    private static TransitionSet getSharedElementTransition(Fragment inFragment,
1055            Fragment outFragment, boolean isBack) {
1056        if (inFragment == null || outFragment == null) {
1057            return null;
1058        }
1059        Transition transition = cloneTransition(isBack
1060                ? outFragment.getSharedElementReturnTransition()
1061                : inFragment.getSharedElementEnterTransition());
1062        if (transition == null) {
1063            return null;
1064        }
1065        TransitionSet transitionSet = new TransitionSet();
1066        transitionSet.addTransition(transition);
1067        return transitionSet;
1068    }
1069
1070    private static ArrayList<View> captureExitingViews(Transition exitTransition,
1071            Fragment outFragment, ArrayMap<String, View> namedViews, View nonExistentView) {
1072        ArrayList<View> viewList = null;
1073        if (exitTransition != null) {
1074            viewList = new ArrayList<View>();
1075            View root = outFragment.getView();
1076            root.captureTransitioningViews(viewList);
1077            if (namedViews != null) {
1078                viewList.removeAll(namedViews.values());
1079            }
1080            if (!viewList.isEmpty()) {
1081                viewList.add(nonExistentView);
1082                addTargets(exitTransition, viewList);
1083            }
1084        }
1085        return viewList;
1086    }
1087
1088    private ArrayMap<String, View> remapSharedElements(TransitionState state, Fragment outFragment,
1089            boolean isBack) {
1090        ArrayMap<String, View> namedViews = new ArrayMap<String, View>();
1091        if (mSharedElementSourceNames != null) {
1092            outFragment.getView().findNamedViews(namedViews);
1093            if (isBack) {
1094                namedViews.retainAll(mSharedElementTargetNames);
1095            } else {
1096                namedViews = remapNames(mSharedElementSourceNames, mSharedElementTargetNames,
1097                        namedViews);
1098            }
1099        }
1100
1101        if (isBack) {
1102            outFragment.mEnterTransitionCallback.onMapSharedElements(
1103                    mSharedElementTargetNames, namedViews);
1104            setBackNameOverrides(state, namedViews, false);
1105        } else {
1106            outFragment.mExitTransitionCallback.onMapSharedElements(
1107                    mSharedElementTargetNames, namedViews);
1108            setNameOverrides(state, namedViews, false);
1109        }
1110
1111        return namedViews;
1112    }
1113
1114    /**
1115     * Prepares the enter transition by adding a non-existent view to the transition's target list
1116     * and setting it epicenter callback. By adding a non-existent view to the target list,
1117     * we can prevent any view from being targeted at the beginning of the transition.
1118     * We will add to the views before the end state of the transition is captured so that the
1119     * views will appear. At the start of the transition, we clear the list of targets so that
1120     * we can restore the state of the transition and use it again.
1121     *
1122     * <p>The shared element transition maps its shared elements immediately prior to
1123     * capturing the final state of the Transition.</p>
1124     */
1125    private ArrayList<View> addTransitionTargets(final TransitionState state,
1126            final Transition enterTransition, final TransitionSet sharedElementTransition,
1127            final Transition exitTransition, final Transition overallTransition,
1128            final View container, final Fragment inFragment, final Fragment outFragment,
1129            final ArrayList<View> hiddenFragmentViews, final boolean isBack,
1130            final ArrayList<View> sharedElementTargets) {
1131        if (enterTransition == null && sharedElementTransition == null &&
1132                overallTransition == null) {
1133            return null;
1134        }
1135        final ArrayList<View> enteringViews = new ArrayList<View>();
1136        container.getViewTreeObserver().addOnPreDrawListener(
1137                new ViewTreeObserver.OnPreDrawListener() {
1138                    @Override
1139                    public boolean onPreDraw() {
1140                        container.getViewTreeObserver().removeOnPreDrawListener(this);
1141
1142                        // Don't include any newly-hidden fragments in the transition.
1143                        if (inFragment != null) {
1144                            excludeHiddenFragments(hiddenFragmentViews, inFragment.mContainerId,
1145                                    overallTransition);
1146                        }
1147
1148                        ArrayMap<String, View> namedViews = null;
1149                        if (sharedElementTransition != null) {
1150                            namedViews = mapSharedElementsIn(state, isBack, inFragment);
1151                            removeTargets(sharedElementTransition, sharedElementTargets);
1152                            // keep the nonExistentView as excluded so the list doesn't get emptied
1153                            sharedElementTargets.remove(state.nonExistentView);
1154                            excludeViews(exitTransition, sharedElementTransition,
1155                                    sharedElementTargets, false);
1156                            excludeViews(enterTransition, sharedElementTransition,
1157                                    sharedElementTargets, false);
1158
1159                            setSharedElementTargets(sharedElementTransition,
1160                                    state.nonExistentView, namedViews, sharedElementTargets);
1161
1162                            setEpicenterIn(namedViews, state);
1163
1164                            callSharedElementEnd(state, inFragment, outFragment, isBack,
1165                                    namedViews);
1166                        }
1167
1168                        if (enterTransition != null) {
1169                            enterTransition.removeTarget(state.nonExistentView);
1170                            View view = inFragment.getView();
1171                            if (view != null) {
1172                                view.captureTransitioningViews(enteringViews);
1173                                if (namedViews != null) {
1174                                    enteringViews.removeAll(namedViews.values());
1175                                }
1176                                enteringViews.add(state.nonExistentView);
1177                                // We added this earlier to prevent any views being targeted.
1178                                addTargets(enterTransition, enteringViews);
1179                            }
1180                            setSharedElementEpicenter(enterTransition, state);
1181                        }
1182
1183                        excludeViews(exitTransition, enterTransition, enteringViews, true);
1184                        excludeViews(exitTransition, sharedElementTransition, sharedElementTargets,
1185                                true);
1186                        excludeViews(enterTransition, sharedElementTransition, sharedElementTargets,
1187                                true);
1188                        return true;
1189                    }
1190                });
1191        return enteringViews;
1192    }
1193
1194    private void callSharedElementEnd(TransitionState state, Fragment inFragment,
1195            Fragment outFragment, boolean isBack, ArrayMap<String, View> namedViews) {
1196        SharedElementCallback sharedElementCallback = isBack ?
1197                outFragment.mEnterTransitionCallback :
1198                inFragment.mEnterTransitionCallback;
1199        ArrayList<String> names = new ArrayList<String>(namedViews.keySet());
1200        ArrayList<View> views = new ArrayList<View>(namedViews.values());
1201        sharedElementCallback.onSharedElementEnd(names, views, null);
1202    }
1203
1204    private void setEpicenterIn(ArrayMap<String, View> namedViews, TransitionState state) {
1205        if (mSharedElementTargetNames != null && !namedViews.isEmpty()) {
1206            // now we know the epicenter of the entering transition.
1207            View epicenter = namedViews
1208                    .get(mSharedElementTargetNames.get(0));
1209            if (epicenter != null) {
1210                state.enteringEpicenterView = epicenter;
1211            }
1212        }
1213    }
1214
1215    private ArrayMap<String, View> mapSharedElementsIn(TransitionState state,
1216            boolean isBack, Fragment inFragment) {
1217        // Now map the shared elements in the incoming fragment
1218        ArrayMap<String, View> namedViews = mapEnteringSharedElements(state, inFragment, isBack);
1219
1220        // remap shared elements and set the name mapping used
1221        // in the shared element transition.
1222        if (isBack) {
1223            inFragment.mExitTransitionCallback.onMapSharedElements(
1224                    mSharedElementTargetNames, namedViews);
1225            setBackNameOverrides(state, namedViews, true);
1226        } else {
1227            inFragment.mEnterTransitionCallback.onMapSharedElements(
1228                    mSharedElementTargetNames, namedViews);
1229            setNameOverrides(state, namedViews, true);
1230        }
1231        return namedViews;
1232    }
1233
1234    private static Transition mergeTransitions(Transition enterTransition,
1235            Transition exitTransition, Transition sharedElementTransition, Fragment inFragment,
1236            boolean isBack) {
1237        boolean overlap = true;
1238        if (enterTransition != null && exitTransition != null && inFragment != null) {
1239            overlap = isBack ? inFragment.getAllowReturnTransitionOverlap() :
1240                    inFragment.getAllowEnterTransitionOverlap();
1241        }
1242
1243        // Wrap the transitions. Explicit targets like in enter and exit will cause the
1244        // views to be targeted regardless of excluded views. If that happens, then the
1245        // excluded fragments views (hidden fragments) will still be in the transition.
1246
1247        Transition transition;
1248        if (overlap) {
1249            // Regular transition -- do it all together
1250            TransitionSet transitionSet = new TransitionSet();
1251            if (enterTransition != null) {
1252                transitionSet.addTransition(enterTransition);
1253            }
1254            if (exitTransition != null) {
1255                transitionSet.addTransition(exitTransition);
1256            }
1257            if (sharedElementTransition != null) {
1258                transitionSet.addTransition(sharedElementTransition);
1259            }
1260            transition = transitionSet;
1261        } else {
1262            // First do exit, then enter, but allow shared element transition to happen
1263            // during both.
1264            Transition staggered = null;
1265            if (exitTransition != null && enterTransition != null) {
1266                staggered = new TransitionSet()
1267                        .addTransition(exitTransition)
1268                        .addTransition(enterTransition)
1269                        .setOrdering(TransitionSet.ORDERING_SEQUENTIAL);
1270            } else if (exitTransition != null) {
1271                staggered = exitTransition;
1272            } else if (enterTransition != null) {
1273                staggered = enterTransition;
1274            }
1275            if (sharedElementTransition != null) {
1276                TransitionSet together = new TransitionSet();
1277                if (staggered != null) {
1278                    together.addTransition(staggered);
1279                }
1280                together.addTransition(sharedElementTransition);
1281                transition = together;
1282            } else {
1283                transition = staggered;
1284            }
1285        }
1286        return transition;
1287    }
1288
1289    /**
1290     * Configures custom transitions for a specific fragment container.
1291     *
1292     * @param containerId The container ID of the fragments to configure the transition for.
1293     * @param state The Transition State keeping track of the executing transitions.
1294     * @param firstOutFragments The list of first fragments to be removed, keyed on the
1295     *                          container ID.
1296     * @param lastInFragments The list of last fragments to be added, keyed on the
1297     *                        container ID.
1298     * @param isBack true if this is popping the back stack or false if this is a
1299     *               forward operation.
1300     */
1301    private void configureTransitions(int containerId, TransitionState state, boolean isBack,
1302            SparseArray<Fragment> firstOutFragments, SparseArray<Fragment> lastInFragments) {
1303        ViewGroup sceneRoot = (ViewGroup) mManager.mContainer.onFindViewById(containerId);
1304        if (sceneRoot != null) {
1305            Fragment inFragment = lastInFragments.get(containerId);
1306            Fragment outFragment = firstOutFragments.get(containerId);
1307
1308            Transition enterTransition = getEnterTransition(inFragment, isBack);
1309            TransitionSet sharedElementTransition =
1310                    getSharedElementTransition(inFragment, outFragment, isBack);
1311            Transition exitTransition = getExitTransition(outFragment, isBack);
1312
1313            if (enterTransition == null && sharedElementTransition == null &&
1314                    exitTransition == null) {
1315                return; // no transitions!
1316            }
1317            if (enterTransition != null) {
1318                enterTransition.addTarget(state.nonExistentView);
1319            }
1320            ArrayMap<String, View> namedViews = null;
1321            ArrayList<View> sharedElementTargets = new ArrayList<View>();
1322            if (sharedElementTransition != null) {
1323                namedViews = remapSharedElements(state, outFragment, isBack);
1324                setSharedElementTargets(sharedElementTransition,
1325                        state.nonExistentView, namedViews, sharedElementTargets);
1326
1327                // Notify the start of the transition.
1328                SharedElementCallback callback = isBack ?
1329                        outFragment.mEnterTransitionCallback :
1330                        inFragment.mEnterTransitionCallback;
1331                ArrayList<String> names = new ArrayList<String>(namedViews.keySet());
1332                ArrayList<View> views = new ArrayList<View>(namedViews.values());
1333                callback.onSharedElementStart(names, views, null);
1334            }
1335
1336            ArrayList<View> exitingViews = captureExitingViews(exitTransition, outFragment,
1337                    namedViews, state.nonExistentView);
1338            if (exitingViews == null || exitingViews.isEmpty()) {
1339                exitTransition = null;
1340            }
1341            excludeViews(enterTransition, exitTransition, exitingViews, true);
1342            excludeViews(enterTransition, sharedElementTransition, sharedElementTargets, true);
1343            excludeViews(exitTransition, sharedElementTransition, sharedElementTargets, true);
1344
1345            // Set the epicenter of the exit transition
1346            if (mSharedElementTargetNames != null && namedViews != null) {
1347                View epicenterView = namedViews.get(mSharedElementTargetNames.get(0));
1348                if (epicenterView != null) {
1349                    if (exitTransition != null) {
1350                        setEpicenter(exitTransition, epicenterView);
1351                    }
1352                    if (sharedElementTransition != null) {
1353                        setEpicenter(sharedElementTransition, epicenterView);
1354                    }
1355                }
1356            }
1357
1358            Transition transition = mergeTransitions(enterTransition, exitTransition,
1359                    sharedElementTransition, inFragment, isBack);
1360
1361            if (transition != null) {
1362                ArrayList<View> hiddenFragments = new ArrayList<View>();
1363                ArrayList<View> enteringViews = addTransitionTargets(state, enterTransition,
1364                        sharedElementTransition, exitTransition, transition, sceneRoot, inFragment,
1365                        outFragment, hiddenFragments, isBack, sharedElementTargets);
1366
1367                transition.setNameOverrides(state.nameOverrides);
1368                // We want to exclude hidden views later, so we need a non-null list in the
1369                // transition now.
1370                transition.excludeTarget(state.nonExistentView, true);
1371                // Now exclude all currently hidden fragments.
1372                excludeHiddenFragments(hiddenFragments, containerId, transition);
1373                TransitionManager.beginDelayedTransition(sceneRoot, transition);
1374                // Remove the view targeting after the transition starts
1375                removeTargetedViewsFromTransitions(sceneRoot, state.nonExistentView,
1376                        enterTransition, enteringViews, exitTransition, exitingViews,
1377                        sharedElementTransition, sharedElementTargets, transition,
1378                        hiddenFragments);
1379            }
1380        }
1381    }
1382
1383    /**
1384     * Finds all children of the shared elements and sets the wrapping TransitionSet
1385     * targets to point to those. It also limits transitions that have no targets to the
1386     * specific shared elements. This allows developers to target child views of the
1387     * shared elements specifically, but this doesn't happen by default.
1388     */
1389    private static void setSharedElementTargets(TransitionSet transition,
1390            View nonExistentView, ArrayMap<String, View> namedViews,
1391            ArrayList<View> sharedElementTargets) {
1392        sharedElementTargets.clear();
1393        sharedElementTargets.addAll(namedViews.values());
1394
1395        final List<View> views = transition.getTargets();
1396        views.clear();
1397        final int count = sharedElementTargets.size();
1398        for (int i = 0; i < count; i++) {
1399            final View view = sharedElementTargets.get(i);
1400            bfsAddViewChildren(views, view);
1401        }
1402        sharedElementTargets.add(nonExistentView);
1403        addTargets(transition, sharedElementTargets);
1404    }
1405
1406    /**
1407     * Uses a breadth-first scheme to add startView and all of its children to views.
1408     * It won't add a child if it is already in views.
1409     */
1410    private static void bfsAddViewChildren(final List<View> views, final View startView) {
1411        final int startIndex = views.size();
1412        if (containedBeforeIndex(views, startView, startIndex)) {
1413            return; // This child is already in the list, so all its children are also.
1414        }
1415        views.add(startView);
1416        for (int index = startIndex; index < views.size(); index++) {
1417            final View view = views.get(index);
1418            if (view instanceof ViewGroup) {
1419                ViewGroup viewGroup = (ViewGroup) view;
1420                final int childCount =  viewGroup.getChildCount();
1421                for (int childIndex = 0; childIndex < childCount; childIndex++) {
1422                    final View child = viewGroup.getChildAt(childIndex);
1423                    if (!containedBeforeIndex(views, child, startIndex)) {
1424                        views.add(child);
1425                    }
1426                }
1427            }
1428        }
1429    }
1430
1431    /**
1432     * Does a linear search through views for view, limited to maxIndex.
1433     */
1434    private static boolean containedBeforeIndex(final List<View> views, final View view,
1435            final int maxIndex) {
1436        for (int i = 0; i < maxIndex; i++) {
1437            if (views.get(i) == view) {
1438                return true;
1439            }
1440        }
1441        return false;
1442    }
1443
1444    private static void excludeViews(Transition transition, Transition fromTransition,
1445            ArrayList<View> views, boolean exclude) {
1446        if (transition != null) {
1447            final int viewCount = fromTransition == null ? 0 : views.size();
1448            for (int i = 0; i < viewCount; i++) {
1449                transition.excludeTarget(views.get(i), exclude);
1450            }
1451        }
1452    }
1453
1454    /**
1455     * After the transition has started, remove all targets that we added to the transitions
1456     * so that the transitions are left in a clean state.
1457     */
1458    private void removeTargetedViewsFromTransitions(
1459            final ViewGroup sceneRoot, final View nonExistingView,
1460            final Transition enterTransition, final ArrayList<View> enteringViews,
1461            final Transition exitTransition, final ArrayList<View> exitingViews,
1462            final Transition sharedElementTransition, final ArrayList<View> sharedElementTargets,
1463            final Transition overallTransition, final ArrayList<View> hiddenViews) {
1464        if (overallTransition != null) {
1465            sceneRoot.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
1466                @Override
1467                public boolean onPreDraw() {
1468                    sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
1469                    if (enterTransition != null) {
1470                        removeTargets(enterTransition, enteringViews);
1471                        excludeViews(enterTransition, exitTransition, exitingViews, false);
1472                        excludeViews(enterTransition, sharedElementTransition, sharedElementTargets,
1473                                false);
1474                    }
1475                    if (exitTransition != null) {
1476                        removeTargets(exitTransition, exitingViews);
1477                        excludeViews(exitTransition, enterTransition, enteringViews, false);
1478                        excludeViews(exitTransition, sharedElementTransition, sharedElementTargets,
1479                                false);
1480                    }
1481                    if (sharedElementTransition != null) {
1482                        removeTargets(sharedElementTransition, sharedElementTargets);
1483                    }
1484                    int numViews = hiddenViews.size();
1485                    for (int i = 0; i < numViews; i++) {
1486                        overallTransition.excludeTarget(hiddenViews.get(i), false);
1487                    }
1488                    overallTransition.excludeTarget(nonExistingView, false);
1489                    return true;
1490                }
1491            });
1492        }
1493    }
1494
1495    /**
1496     * This method removes the views from transitions that target ONLY those views.
1497     * The views list should match those added in addTargets and should contain
1498     * one view that is not in the view hierarchy (state.nonExistentView).
1499     */
1500    public static void removeTargets(Transition transition, ArrayList<View> views) {
1501        if (transition instanceof TransitionSet) {
1502            TransitionSet set = (TransitionSet) transition;
1503            int numTransitions = set.getTransitionCount();
1504            for (int i = 0; i < numTransitions; i++) {
1505                Transition child = set.getTransitionAt(i);
1506                removeTargets(child, views);
1507            }
1508        } else if (!hasSimpleTarget(transition)) {
1509            List<View> targets = transition.getTargets();
1510            if (targets != null && targets.size() == views.size() &&
1511                    targets.containsAll(views)) {
1512                // We have an exact match. We must have added these earlier in addTargets
1513                for (int i = views.size() - 1; i >= 0; i--) {
1514                    transition.removeTarget(views.get(i));
1515                }
1516            }
1517        }
1518    }
1519
1520    /**
1521     * This method adds views as targets to the transition, but only if the transition
1522     * doesn't already have a target. It is best for views to contain one View object
1523     * that does not exist in the view hierarchy (state.nonExistentView) so that
1524     * when they are removed later, a list match will suffice to remove the targets.
1525     * Otherwise, if you happened to have targeted the exact views for the transition,
1526     * the removeTargets call will remove them unexpectedly.
1527     */
1528    public static void addTargets(Transition transition, ArrayList<View> views) {
1529        if (transition instanceof TransitionSet) {
1530            TransitionSet set = (TransitionSet) transition;
1531            int numTransitions = set.getTransitionCount();
1532            for (int i = 0; i < numTransitions; i++) {
1533                Transition child = set.getTransitionAt(i);
1534                addTargets(child, views);
1535            }
1536        } else if (!hasSimpleTarget(transition)) {
1537            List<View> targets = transition.getTargets();
1538            if (isNullOrEmpty(targets)) {
1539                // We can just add the target views
1540                int numViews = views.size();
1541                for (int i = 0; i < numViews; i++) {
1542                    transition.addTarget(views.get(i));
1543                }
1544            }
1545        }
1546    }
1547
1548    private static boolean hasSimpleTarget(Transition transition) {
1549        return !isNullOrEmpty(transition.getTargetIds()) ||
1550                !isNullOrEmpty(transition.getTargetNames()) ||
1551                !isNullOrEmpty(transition.getTargetTypes());
1552    }
1553
1554    private static boolean isNullOrEmpty(List list) {
1555        return list == null || list.isEmpty();
1556    }
1557
1558    /**
1559     * Remaps a name-to-View map, substituting different names for keys.
1560     *
1561     * @param inMap A list of keys found in the map, in the order in toGoInMap
1562     * @param toGoInMap A list of keys to use for the new map, in the order of inMap
1563     * @param namedViews The current mapping
1564     * @return a new Map after it has been mapped with the new names as keys.
1565     */
1566    private static ArrayMap<String, View> remapNames(ArrayList<String> inMap,
1567            ArrayList<String> toGoInMap, ArrayMap<String, View> namedViews) {
1568        ArrayMap<String, View> remappedViews = new ArrayMap<String, View>();
1569        if (!namedViews.isEmpty()) {
1570            int numKeys = inMap.size();
1571            for (int i = 0; i < numKeys; i++) {
1572                View view = namedViews.get(inMap.get(i));
1573
1574                if (view != null) {
1575                    remappedViews.put(toGoInMap.get(i), view);
1576                }
1577            }
1578        }
1579        return remappedViews;
1580    }
1581
1582    /**
1583     * Maps shared elements to views in the entering fragment.
1584     *
1585     * @param state The transition State as returned from {@link #beginTransition(
1586     * android.util.SparseArray, android.util.SparseArray, boolean)}.
1587     * @param inFragment The last fragment to be added.
1588     * @param isBack true if this is popping the back stack or false if this is a
1589     *               forward operation.
1590     */
1591    private ArrayMap<String, View> mapEnteringSharedElements(TransitionState state,
1592            Fragment inFragment, boolean isBack) {
1593        ArrayMap<String, View> namedViews = new ArrayMap<String, View>();
1594        View root = inFragment.getView();
1595        if (root != null) {
1596            if (mSharedElementSourceNames != null) {
1597                root.findNamedViews(namedViews);
1598                if (isBack) {
1599                    namedViews = remapNames(mSharedElementSourceNames,
1600                            mSharedElementTargetNames, namedViews);
1601                } else {
1602                    namedViews.retainAll(mSharedElementTargetNames);
1603                }
1604            }
1605        }
1606        return namedViews;
1607    }
1608
1609    private void excludeHiddenFragments(final ArrayList<View> hiddenFragmentViews, int containerId,
1610            Transition transition) {
1611        if (mManager.mAdded != null) {
1612            for (int i = 0; i < mManager.mAdded.size(); i++) {
1613                Fragment fragment = mManager.mAdded.get(i);
1614                if (fragment.mView != null && fragment.mContainer != null &&
1615                        fragment.mContainerId == containerId) {
1616                    if (fragment.mHidden) {
1617                        if (!hiddenFragmentViews.contains(fragment.mView)) {
1618                            transition.excludeTarget(fragment.mView, true);
1619                            hiddenFragmentViews.add(fragment.mView);
1620                        }
1621                    } else {
1622                        transition.excludeTarget(fragment.mView, false);
1623                        hiddenFragmentViews.remove(fragment.mView);
1624                    }
1625                }
1626            }
1627        }
1628    }
1629
1630    private static void setEpicenter(Transition transition, View view) {
1631        final Rect epicenter = new Rect();
1632        view.getBoundsOnScreen(epicenter);
1633
1634        transition.setEpicenterCallback(new Transition.EpicenterCallback() {
1635            @Override
1636            public Rect onGetEpicenter(Transition transition) {
1637                return epicenter;
1638            }
1639        });
1640    }
1641
1642    private void setSharedElementEpicenter(Transition transition, final TransitionState state) {
1643        transition.setEpicenterCallback(new Transition.EpicenterCallback() {
1644            private Rect mEpicenter;
1645
1646            @Override
1647            public Rect onGetEpicenter(Transition transition) {
1648                if (mEpicenter == null && state.enteringEpicenterView != null) {
1649                    mEpicenter = new Rect();
1650                    state.enteringEpicenterView.getBoundsOnScreen(mEpicenter);
1651                }
1652                return mEpicenter;
1653            }
1654        });
1655    }
1656
1657    public TransitionState popFromBackStack(boolean doStateMove, TransitionState state,
1658            SparseArray<Fragment> firstOutFragments, SparseArray<Fragment> lastInFragments) {
1659        if (FragmentManagerImpl.DEBUG) {
1660            Log.v(TAG, "popFromBackStack: " + this);
1661            LogWriter logw = new LogWriter(Log.VERBOSE, TAG);
1662            PrintWriter pw = new FastPrintWriter(logw, false, 1024);
1663            dump("  ", null, pw, null);
1664            pw.flush();
1665        }
1666
1667        if (mManager.mCurState >= Fragment.CREATED) {
1668            if (state == null) {
1669                if (firstOutFragments.size() != 0 || lastInFragments.size() != 0) {
1670                    state = beginTransition(firstOutFragments, lastInFragments, true);
1671                }
1672            } else if (!doStateMove) {
1673                setNameOverrides(state, mSharedElementTargetNames, mSharedElementSourceNames);
1674            }
1675        }
1676
1677        bumpBackStackNesting(-1);
1678
1679        Op op = mTail;
1680        while (op != null) {
1681            switch (op.cmd) {
1682                case OP_ADD: {
1683                    Fragment f = op.fragment;
1684                    f.mNextAnim = op.popExitAnim;
1685                    mManager.removeFragment(f,
1686                            FragmentManagerImpl.reverseTransit(mTransition),
1687                            mTransitionStyle);
1688                }
1689                break;
1690                case OP_REPLACE: {
1691                    Fragment f = op.fragment;
1692                    if (f != null) {
1693                        f.mNextAnim = op.popExitAnim;
1694                        mManager.removeFragment(f,
1695                                FragmentManagerImpl.reverseTransit(mTransition),
1696                                mTransitionStyle);
1697                    }
1698                    if (op.removed != null) {
1699                        for (int i = 0; i < op.removed.size(); i++) {
1700                            Fragment old = op.removed.get(i);
1701                            old.mNextAnim = op.popEnterAnim;
1702                            mManager.addFragment(old, false);
1703                        }
1704                    }
1705                }
1706                break;
1707                case OP_REMOVE: {
1708                    Fragment f = op.fragment;
1709                    f.mNextAnim = op.popEnterAnim;
1710                    mManager.addFragment(f, false);
1711                }
1712                break;
1713                case OP_HIDE: {
1714                    Fragment f = op.fragment;
1715                    f.mNextAnim = op.popEnterAnim;
1716                    mManager.showFragment(f,
1717                            FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
1718                }
1719                break;
1720                case OP_SHOW: {
1721                    Fragment f = op.fragment;
1722                    f.mNextAnim = op.popExitAnim;
1723                    mManager.hideFragment(f,
1724                            FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
1725                }
1726                break;
1727                case OP_DETACH: {
1728                    Fragment f = op.fragment;
1729                    f.mNextAnim = op.popEnterAnim;
1730                    mManager.attachFragment(f,
1731                            FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
1732                }
1733                break;
1734                case OP_ATTACH: {
1735                    Fragment f = op.fragment;
1736                    f.mNextAnim = op.popExitAnim;
1737                    mManager.detachFragment(f,
1738                            FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
1739                }
1740                break;
1741                default: {
1742                    throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
1743                }
1744            }
1745
1746            op = op.prev;
1747        }
1748
1749        if (doStateMove) {
1750            mManager.moveToState(mManager.mCurState,
1751                    FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle, true);
1752            state = null;
1753        }
1754
1755        if (mIndex >= 0) {
1756            mManager.freeBackStackIndex(mIndex);
1757            mIndex = -1;
1758        }
1759        return state;
1760    }
1761
1762    private static void setNameOverride(ArrayMap<String, String> overrides,
1763            String source, String target) {
1764        if (source != null && target != null && !source.equals(target)) {
1765            for (int index = 0; index < overrides.size(); index++) {
1766                if (source.equals(overrides.valueAt(index))) {
1767                    overrides.setValueAt(index, target);
1768                    return;
1769                }
1770            }
1771            overrides.put(source, target);
1772        }
1773    }
1774
1775    private static void setNameOverrides(TransitionState state, ArrayList<String> sourceNames,
1776            ArrayList<String> targetNames) {
1777        if (sourceNames != null && targetNames != null) {
1778            for (int i = 0; i < sourceNames.size(); i++) {
1779                String source = sourceNames.get(i);
1780                String target = targetNames.get(i);
1781                setNameOverride(state.nameOverrides, source, target);
1782            }
1783        }
1784    }
1785
1786    private void setBackNameOverrides(TransitionState state, ArrayMap<String, View> namedViews,
1787            boolean isEnd) {
1788        int targetCount = mSharedElementTargetNames == null ? 0 : mSharedElementTargetNames.size();
1789        int sourceCount = mSharedElementSourceNames == null ? 0 : mSharedElementSourceNames.size();
1790        final int count = Math.min(targetCount, sourceCount);
1791        for (int i = 0; i < count; i++) {
1792            String source = mSharedElementSourceNames.get(i);
1793            String originalTarget = mSharedElementTargetNames.get(i);
1794            View view = namedViews.get(originalTarget);
1795            if (view != null) {
1796                String target = view.getTransitionName();
1797                if (isEnd) {
1798                    setNameOverride(state.nameOverrides, source, target);
1799                } else {
1800                    setNameOverride(state.nameOverrides, target, source);
1801                }
1802            }
1803        }
1804    }
1805
1806    private void setNameOverrides(TransitionState state, ArrayMap<String, View> namedViews,
1807            boolean isEnd) {
1808        int count = namedViews == null ? 0 : namedViews.size();
1809        for (int i = 0; i < count; i++) {
1810            String source = namedViews.keyAt(i);
1811            String target = namedViews.valueAt(i).getTransitionName();
1812            if (isEnd) {
1813                setNameOverride(state.nameOverrides, source, target);
1814            } else {
1815                setNameOverride(state.nameOverrides, target, source);
1816            }
1817        }
1818    }
1819
1820    public String getName() {
1821        return mName;
1822    }
1823
1824    public int getTransition() {
1825        return mTransition;
1826    }
1827
1828    public int getTransitionStyle() {
1829        return mTransitionStyle;
1830    }
1831
1832    public boolean isEmpty() {
1833        return mNumOp == 0;
1834    }
1835
1836    public class TransitionState {
1837        public ArrayMap<String, String> nameOverrides = new ArrayMap<String, String>();
1838        public View enteringEpicenterView;
1839        public View nonExistentView;
1840    }
1841}
1842