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