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