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