BackStackRecord.java revision 7ce5d75dc920823d83e2e34fefc3875dc8868411
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.List;
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        calculateFragments(firstOutFragments, lastInFragments);
749        beginTransition(firstOutFragments, lastInFragments, false);
750
751        Op op = mHead;
752        while (op != null) {
753            switch (op.cmd) {
754                case OP_ADD: {
755                    Fragment f = op.fragment;
756                    f.mNextAnim = op.enterAnim;
757                    mManager.addFragment(f, false);
758                }
759                break;
760                case OP_REPLACE: {
761                    Fragment f = op.fragment;
762                    if (mManager.mAdded != null) {
763                        for (int i = 0; i < mManager.mAdded.size(); i++) {
764                            Fragment old = mManager.mAdded.get(i);
765                            if (FragmentManagerImpl.DEBUG) {
766                                Log.v(TAG,
767                                        "OP_REPLACE: adding=" + f + " old=" + old);
768                            }
769                            if (f == null || old.mContainerId == f.mContainerId) {
770                                if (old == f) {
771                                    op.fragment = f = null;
772                                } else {
773                                    if (op.removed == null) {
774                                        op.removed = new ArrayList<Fragment>();
775                                    }
776                                    op.removed.add(old);
777                                    old.mNextAnim = op.exitAnim;
778                                    if (mAddToBackStack) {
779                                        old.mBackStackNesting += 1;
780                                        if (FragmentManagerImpl.DEBUG) {
781                                            Log.v(TAG, "Bump nesting of "
782                                                    + old + " to " + old.mBackStackNesting);
783                                        }
784                                    }
785                                    mManager.removeFragment(old, mTransition, mTransitionStyle);
786                                }
787                            }
788                        }
789                    }
790                    if (f != null) {
791                        f.mNextAnim = op.enterAnim;
792                        mManager.addFragment(f, false);
793                    }
794                }
795                break;
796                case OP_REMOVE: {
797                    Fragment f = op.fragment;
798                    f.mNextAnim = op.exitAnim;
799                    mManager.removeFragment(f, mTransition, mTransitionStyle);
800                }
801                break;
802                case OP_HIDE: {
803                    Fragment f = op.fragment;
804                    f.mNextAnim = op.exitAnim;
805                    mManager.hideFragment(f, mTransition, mTransitionStyle);
806                }
807                break;
808                case OP_SHOW: {
809                    Fragment f = op.fragment;
810                    f.mNextAnim = op.enterAnim;
811                    mManager.showFragment(f, mTransition, mTransitionStyle);
812                }
813                break;
814                case OP_DETACH: {
815                    Fragment f = op.fragment;
816                    f.mNextAnim = op.exitAnim;
817                    mManager.detachFragment(f, mTransition, mTransitionStyle);
818                }
819                break;
820                case OP_ATTACH: {
821                    Fragment f = op.fragment;
822                    f.mNextAnim = op.enterAnim;
823                    mManager.attachFragment(f, mTransition, mTransitionStyle);
824                }
825                break;
826                default: {
827                    throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
828                }
829            }
830
831            op = op.next;
832        }
833
834        mManager.moveToState(mManager.mCurState, mTransition,
835                mTransitionStyle, true);
836
837        if (mAddToBackStack) {
838            mManager.addBackStackState(this);
839        }
840    }
841
842    private static void setFirstOut(SparseArray<Fragment> fragments, Fragment fragment) {
843        if (fragment != null) {
844            int containerId = fragment.mContainerId;
845            if (containerId != 0 && !fragment.isHidden() && fragment.isAdded() &&
846                    fragment.getView() != null && fragments.get(containerId) == null) {
847                fragments.put(containerId, fragment);
848            }
849        }
850    }
851
852    private void setLastIn(SparseArray<Fragment> fragments, Fragment fragment) {
853        if (fragment != null) {
854            int containerId = fragment.mContainerId;
855            if (containerId != 0) {
856                fragments.put(containerId, fragment);
857            }
858        }
859    }
860
861    /**
862     * Finds the first removed fragment and last added fragments when going forward.
863     * If none of the fragments have transitions, then both lists will be empty.
864     *
865     * @param firstOutFragments The list of first fragments to be removed, keyed on the
866     *                          container ID. This list will be modified by the method.
867     * @param lastInFragments The list of last fragments to be added, keyed on the
868     *                        container ID. This list will be modified by the method.
869     */
870    private void calculateFragments(SparseArray<Fragment> firstOutFragments,
871            SparseArray<Fragment> lastInFragments) {
872        if (!mManager.mContainer.hasView()) {
873            return; // nothing to see, so no transitions
874        }
875        Op op = mHead;
876        while (op != null) {
877            switch (op.cmd) {
878                case OP_ADD:
879                    setLastIn(lastInFragments, op.fragment);
880                    break;
881                case OP_REPLACE: {
882                    Fragment f = op.fragment;
883                    if (mManager.mAdded != null) {
884                        for (int i = 0; i < mManager.mAdded.size(); i++) {
885                            Fragment old = mManager.mAdded.get(i);
886                            if (f == null || old.mContainerId == f.mContainerId) {
887                                if (old == f) {
888                                    f = null;
889                                } else {
890                                    setFirstOut(firstOutFragments, old);
891                                }
892                            }
893                        }
894                    }
895                    setLastIn(lastInFragments, f);
896                    break;
897                }
898                case OP_REMOVE:
899                    setFirstOut(firstOutFragments, op.fragment);
900                    break;
901                case OP_HIDE:
902                    setFirstOut(firstOutFragments, op.fragment);
903                    break;
904                case OP_SHOW:
905                    setLastIn(lastInFragments, op.fragment);
906                    break;
907                case OP_DETACH:
908                    setFirstOut(firstOutFragments, op.fragment);
909                    break;
910                case OP_ATTACH:
911                    setLastIn(lastInFragments, op.fragment);
912                    break;
913            }
914
915            op = op.next;
916        }
917    }
918
919    /**
920     * Finds the first removed fragment and last added fragments when popping the back stack.
921     * If none of the fragments have transitions, then both lists will be empty.
922     *
923     * @param firstOutFragments The list of first fragments to be removed, keyed on the
924     *                          container ID. This list will be modified by the method.
925     * @param lastInFragments The list of last fragments to be added, keyed on the
926     *                        container ID. This list will be modified by the method.
927     */
928    public void calculateBackFragments(SparseArray<Fragment> firstOutFragments,
929            SparseArray<Fragment> lastInFragments) {
930        if (!mManager.mContainer.hasView()) {
931            return; // nothing to see, so no transitions
932        }
933        Op op = mHead;
934        while (op != null) {
935            switch (op.cmd) {
936                case OP_ADD:
937                    setFirstOut(firstOutFragments, op.fragment);
938                    break;
939                case OP_REPLACE:
940                    if (op.removed != null) {
941                        for (int i = op.removed.size() - 1; i >= 0; i--) {
942                            setLastIn(lastInFragments, op.removed.get(i));
943                        }
944                    }
945                    setFirstOut(firstOutFragments, op.fragment);
946                    break;
947                case OP_REMOVE:
948                    setLastIn(lastInFragments, op.fragment);
949                    break;
950                case OP_HIDE:
951                    setLastIn(lastInFragments, op.fragment);
952                    break;
953                case OP_SHOW:
954                    setFirstOut(firstOutFragments, op.fragment);
955                    break;
956                case OP_DETACH:
957                    setLastIn(lastInFragments, op.fragment);
958                    break;
959                case OP_ATTACH:
960                    setFirstOut(firstOutFragments, op.fragment);
961                    break;
962            }
963
964            op = op.next;
965        }
966    }
967
968    /**
969     * When custom fragment transitions are used, this sets up the state for each transition
970     * and begins the transition. A different transition is started for each fragment container
971     * and consists of up to 3 different transitions: the exit transition, a shared element
972     * transition and an enter transition.
973     *
974     * <p>The exit transition operates against the leaf nodes of the first fragment
975     * with a view that was removed. If no such fragment was removed, then no exit
976     * transition is executed. The exit transition comes from the outgoing fragment.</p>
977     *
978     * <p>The enter transition operates against the last fragment that was added. If
979     * that fragment does not have a view or no fragment was added, then no enter
980     * transition is executed. The enter transition comes from the incoming fragment.</p>
981     *
982     * <p>The shared element transition operates against all views and comes either
983     * from the outgoing fragment or the incoming fragment, depending on whether this
984     * is going forward or popping the back stack. When going forward, the incoming
985     * fragment's enter shared element transition is used, but when going back, the
986     * outgoing fragment's return shared element transition is used. Shared element
987     * transitions only operate if there is both an incoming and outgoing fragment.</p>
988     *
989     * @param firstOutFragments The list of first fragments to be removed, keyed on the
990     *                          container ID.
991     * @param lastInFragments The list of last fragments to be added, keyed on the
992     *                        container ID.
993     * @param isBack true if this is popping the back stack or false if this is a
994     *               forward operation.
995     * @return The TransitionState used to complete the operation of the transition
996     * in {@link #setNameOverrides(android.app.BackStackRecord.TransitionState, java.util.ArrayList,
997     * java.util.ArrayList)}.
998     */
999    private TransitionState beginTransition(SparseArray<Fragment> firstOutFragments,
1000            SparseArray<Fragment> lastInFragments, boolean isBack) {
1001        TransitionState state = new TransitionState();
1002
1003        // Adding a non-existent target view makes sure that the transitions don't target
1004        // any views by default. They'll only target the views we tell add. If we don't
1005        // add any, then no views will be targeted.
1006        state.nonExistentView = new View(mManager.mActivity);
1007
1008        // Go over all leaving fragments.
1009        for (int i = 0; i < firstOutFragments.size(); i++) {
1010            int containerId = firstOutFragments.keyAt(i);
1011            configureTransitions(containerId, state, isBack, firstOutFragments,
1012                    lastInFragments);
1013        }
1014
1015        // Now go over all entering fragments that didn't have a leaving fragment.
1016        for (int i = 0; i < lastInFragments.size(); i++) {
1017            int containerId = lastInFragments.keyAt(i);
1018            if (firstOutFragments.get(containerId) == null) {
1019                configureTransitions(containerId, state, isBack, firstOutFragments,
1020                        lastInFragments);
1021            }
1022        }
1023        return state;
1024    }
1025
1026    private static Transition cloneTransition(Transition transition) {
1027        if (transition != null) {
1028            transition = transition.clone();
1029        }
1030        return transition;
1031    }
1032
1033    private static Transition getEnterTransition(Fragment inFragment, boolean isBack) {
1034        if (inFragment == null) {
1035            return null;
1036        }
1037        return cloneTransition(isBack ? inFragment.getReenterTransition() :
1038                inFragment.getEnterTransition());
1039    }
1040
1041    private static Transition getExitTransition(Fragment outFragment, boolean isBack) {
1042        if (outFragment == null) {
1043            return null;
1044        }
1045        return cloneTransition(isBack ? outFragment.getReturnTransition() :
1046                outFragment.getExitTransition());
1047    }
1048
1049    private static Transition getSharedElementTransition(Fragment inFragment, Fragment outFragment,
1050            boolean isBack) {
1051        if (inFragment == null || outFragment == null) {
1052            return null;
1053        }
1054        return cloneTransition(isBack ? outFragment.getSharedElementReturnTransition() :
1055                inFragment.getSharedElementEnterTransition());
1056    }
1057
1058    private static ArrayList<View> captureExitingViews(Transition exitTransition,
1059            Fragment outFragment, ArrayMap<String, View> namedViews, View nonExistentView) {
1060        ArrayList<View> viewList = null;
1061        if (exitTransition != null) {
1062            viewList = new ArrayList<View>();
1063            View root = outFragment.getView();
1064            root.captureTransitioningViews(viewList);
1065            if (namedViews != null) {
1066                viewList.removeAll(namedViews.values());
1067            }
1068            if (!viewList.isEmpty()) {
1069                viewList.add(nonExistentView);
1070                addTargets(exitTransition, viewList);
1071            }
1072        }
1073        return viewList;
1074    }
1075
1076    private ArrayMap<String, View> remapSharedElements(TransitionState state, Fragment outFragment,
1077            boolean isBack) {
1078        ArrayMap<String, View> namedViews = new ArrayMap<String, View>();
1079        if (mSharedElementSourceNames != null) {
1080            outFragment.getView().findNamedViews(namedViews);
1081            if (isBack) {
1082                namedViews.retainAll(mSharedElementTargetNames);
1083            } else {
1084                namedViews = remapNames(mSharedElementSourceNames, mSharedElementTargetNames,
1085                        namedViews);
1086            }
1087        }
1088
1089        if (isBack) {
1090            outFragment.mEnterTransitionCallback.onMapSharedElements(
1091                    mSharedElementTargetNames, namedViews);
1092            setBackNameOverrides(state, namedViews, false);
1093        } else {
1094            outFragment.mExitTransitionCallback.onMapSharedElements(
1095                    mSharedElementTargetNames, namedViews);
1096            setNameOverrides(state, namedViews, false);
1097        }
1098
1099        return namedViews;
1100    }
1101
1102    /**
1103     * Prepares the enter transition by adding a non-existent view to the transition's target list
1104     * and setting it epicenter callback. By adding a non-existent view to the target list,
1105     * we can prevent any view from being targeted at the beginning of the transition.
1106     * We will add to the views before the end state of the transition is captured so that the
1107     * views will appear. At the start of the transition, we clear the list of targets so that
1108     * we can restore the state of the transition and use it again.
1109     *
1110     * <p>The shared element transition maps its shared elements immediately prior to
1111     * capturing the final state of the Transition.</p>
1112     */
1113    private ArrayList<View> addTransitionTargets(final TransitionState state,
1114            final Transition enterTransition, final Transition sharedElementTransition,
1115            final Transition overallTransition, final View container,
1116            final Fragment inFragment, final Fragment outFragment,
1117            final ArrayList<View> hiddenFragmentViews, final boolean isBack,
1118            final ArrayList<View> sharedElementTargets) {
1119        if (enterTransition == null && sharedElementTransition == null &&
1120                overallTransition == null) {
1121            return null;
1122        }
1123        final ArrayList<View> enteringViews = new ArrayList<View>();
1124        container.getViewTreeObserver().addOnPreDrawListener(
1125                new ViewTreeObserver.OnPreDrawListener() {
1126                    @Override
1127                    public boolean onPreDraw() {
1128                        container.getViewTreeObserver().removeOnPreDrawListener(this);
1129
1130                        // Don't include any newly-hidden fragments in the transition.
1131                        excludeHiddenFragments(hiddenFragmentViews, inFragment.mContainerId,
1132                                overallTransition);
1133
1134                        ArrayMap<String, View> namedViews = null;
1135                        if (sharedElementTransition != null) {
1136                            namedViews = mapSharedElementsIn(state, isBack, inFragment);
1137                            removeTargets(sharedElementTransition, sharedElementTargets);
1138                            sharedElementTargets.clear();
1139                            sharedElementTargets.add(state.nonExistentView);
1140                            sharedElementTargets.addAll(namedViews.values());
1141
1142                            addTargets(sharedElementTransition, sharedElementTargets);
1143
1144                            setEpicenterIn(namedViews, state);
1145
1146                            callSharedElementEnd(state, inFragment, outFragment, isBack,
1147                                    namedViews);
1148                        }
1149
1150                        if (enterTransition != null) {
1151                            View view = inFragment.getView();
1152                            if (view != null) {
1153                                view.captureTransitioningViews(enteringViews);
1154                                if (namedViews != null) {
1155                                    enteringViews.removeAll(namedViews.values());
1156                                }
1157                                enteringViews.add(state.nonExistentView);
1158                                // We added this earlier to prevent any views being targeted.
1159                                enterTransition.removeTarget(state.nonExistentView);
1160                                addTargets(enterTransition, enteringViews);
1161                            }
1162                            setSharedElementEpicenter(enterTransition, state);
1163                        }
1164                        return true;
1165                    }
1166                });
1167        return enteringViews;
1168    }
1169
1170    private void callSharedElementEnd(TransitionState state, Fragment inFragment,
1171            Fragment outFragment, boolean isBack, ArrayMap<String, View> namedViews) {
1172        SharedElementCallback sharedElementCallback = isBack ?
1173                outFragment.mEnterTransitionCallback :
1174                inFragment.mEnterTransitionCallback;
1175        ArrayList<String> names = new ArrayList<String>(namedViews.keySet());
1176        ArrayList<View> views = new ArrayList<View>(namedViews.values());
1177        sharedElementCallback.onSharedElementEnd(names, views, null);
1178    }
1179
1180    private void setEpicenterIn(ArrayMap<String, View> namedViews, TransitionState state) {
1181        if (mSharedElementTargetNames != null && !namedViews.isEmpty()) {
1182            // now we know the epicenter of the entering transition.
1183            View epicenter = namedViews
1184                    .get(mSharedElementTargetNames.get(0));
1185            if (epicenter != null) {
1186                state.enteringEpicenterView = epicenter;
1187            }
1188        }
1189    }
1190
1191    private ArrayMap<String, View> mapSharedElementsIn(TransitionState state,
1192            boolean isBack, Fragment inFragment) {
1193        // Now map the shared elements in the incoming fragment
1194        ArrayMap<String, View> namedViews = mapEnteringSharedElements(state, inFragment, isBack);
1195
1196        // remap shared elements and set the name mapping used
1197        // in the shared element transition.
1198        if (isBack) {
1199            inFragment.mExitTransitionCallback.onMapSharedElements(
1200                    mSharedElementTargetNames, namedViews);
1201            setBackNameOverrides(state, namedViews, true);
1202        } else {
1203            inFragment.mEnterTransitionCallback.onMapSharedElements(
1204                    mSharedElementTargetNames, namedViews);
1205            setNameOverrides(state, namedViews, true);
1206        }
1207        return namedViews;
1208    }
1209
1210    private static Transition mergeTransitions(Transition enterTransition,
1211            Transition exitTransition, Transition sharedElementTransition, Fragment inFragment,
1212            boolean isBack) {
1213        boolean overlap = true;
1214        if (enterTransition != null && exitTransition != null) {
1215            overlap = isBack ? inFragment.getAllowReturnTransitionOverlap() :
1216                    inFragment.getAllowEnterTransitionOverlap();
1217        }
1218
1219        // Wrap the transitions. Explicit targets like in enter and exit will cause the
1220        // views to be targeted regardless of excluded views. If that happens, then the
1221        // excluded fragments views (hidden fragments) will still be in the transition.
1222
1223        Transition transition;
1224        if (overlap) {
1225            // Regular transition -- do it all together
1226            TransitionSet transitionSet = new TransitionSet();
1227            if (enterTransition != null) {
1228                transitionSet.addTransition(enterTransition);
1229            }
1230            if (exitTransition != null) {
1231                transitionSet.addTransition(exitTransition);
1232            }
1233            if (sharedElementTransition != null) {
1234                transitionSet.addTransition(sharedElementTransition);
1235            }
1236            transition = transitionSet;
1237        } else {
1238            // First do exit, then enter, but allow shared element transition to happen
1239            // during both.
1240            Transition staggered = null;
1241            if (exitTransition != null && enterTransition != null) {
1242                staggered = new TransitionSet()
1243                        .addTransition(exitTransition)
1244                        .addTransition(enterTransition)
1245                        .setOrdering(TransitionSet.ORDERING_SEQUENTIAL);
1246            } else if (exitTransition != null) {
1247                staggered = exitTransition;
1248            } else if (enterTransition != null) {
1249                staggered = enterTransition;
1250            }
1251            if (sharedElementTransition != null) {
1252                TransitionSet together = new TransitionSet();
1253                if (staggered != null) {
1254                    together.addTransition(staggered);
1255                }
1256                together.addTransition(sharedElementTransition);
1257                transition = together;
1258            } else {
1259                transition = staggered;
1260            }
1261        }
1262        return transition;
1263    }
1264
1265    /**
1266     * Configures custom transitions for a specific fragment container.
1267     *
1268     * @param containerId The container ID of the fragments to configure the transition for.
1269     * @param state The Transition State keeping track of the executing transitions.
1270     * @param firstOutFragments The list of first fragments to be removed, keyed on the
1271     *                          container ID.
1272     * @param lastInFragments The list of last fragments to be added, keyed on the
1273     *                        container ID.
1274     * @param isBack true if this is popping the back stack or false if this is a
1275     *               forward operation.
1276     */
1277    private void configureTransitions(int containerId, TransitionState state, boolean isBack,
1278            SparseArray<Fragment> firstOutFragments, SparseArray<Fragment> lastInFragments) {
1279        ViewGroup sceneRoot = (ViewGroup) mManager.mContainer.findViewById(containerId);
1280        if (sceneRoot != null) {
1281            Fragment inFragment = lastInFragments.get(containerId);
1282            Fragment outFragment = firstOutFragments.get(containerId);
1283
1284            Transition enterTransition = getEnterTransition(inFragment, isBack);
1285            Transition sharedElementTransition = getSharedElementTransition(inFragment, outFragment,
1286                    isBack);
1287            Transition exitTransition = getExitTransition(outFragment, isBack);
1288
1289            if (enterTransition == null && sharedElementTransition == null &&
1290                    exitTransition == null) {
1291                return; // no transitions!
1292            }
1293            if (enterTransition != null) {
1294                enterTransition.addTarget(state.nonExistentView);
1295            }
1296            ArrayMap<String, View> namedViews = null;
1297            ArrayList<View> sharedElementTargets = new ArrayList<View>();
1298            if (sharedElementTransition != null) {
1299                namedViews = remapSharedElements(state, outFragment, isBack);
1300                sharedElementTargets.add(state.nonExistentView);
1301                sharedElementTargets.addAll(namedViews.values());
1302                addTargets(sharedElementTransition, sharedElementTargets);
1303
1304                // Notify the start of the transition.
1305                SharedElementCallback callback = isBack ?
1306                        outFragment.mEnterTransitionCallback :
1307                        inFragment.mEnterTransitionCallback;
1308                ArrayList<String> names = new ArrayList<String>(namedViews.keySet());
1309                ArrayList<View> views = new ArrayList<View>(namedViews.values());
1310                callback.onSharedElementStart(names, views, null);
1311            }
1312
1313            ArrayList<View> exitingViews = captureExitingViews(exitTransition, outFragment,
1314                    namedViews, state.nonExistentView);
1315            if (exitingViews == null || exitingViews.isEmpty()) {
1316                exitTransition = null;
1317            }
1318
1319            // Set the epicenter of the exit transition
1320            if (mSharedElementTargetNames != null && namedViews != null) {
1321                View epicenterView = namedViews.get(mSharedElementTargetNames.get(0));
1322                if (epicenterView != null) {
1323                    if (exitTransition != null) {
1324                        setEpicenter(exitTransition, epicenterView);
1325                    }
1326                    if (sharedElementTransition != null) {
1327                        setEpicenter(sharedElementTransition, epicenterView);
1328                    }
1329                }
1330            }
1331
1332            Transition transition = mergeTransitions(enterTransition, exitTransition,
1333                    sharedElementTransition, inFragment, isBack);
1334
1335            if (transition != null) {
1336                ArrayList<View> hiddenFragments = new ArrayList<View>();
1337                ArrayList<View> enteringViews = addTransitionTargets(state, enterTransition,
1338                        sharedElementTransition, transition, sceneRoot, inFragment, outFragment,
1339                        hiddenFragments, isBack, sharedElementTargets);
1340
1341                transition.setNameOverrides(state.nameOverrides);
1342                // We want to exclude hidden views later, so we need a non-null list in the
1343                // transition now.
1344                transition.excludeTarget(state.nonExistentView, true);
1345                // Now exclude all currently hidden fragments.
1346                excludeHiddenFragments(hiddenFragments, containerId, transition);
1347                TransitionManager.beginDelayedTransition(sceneRoot, transition);
1348                // Remove the view targeting after the transition starts
1349                removeTargetedViewsFromTransitions(sceneRoot, state.nonExistentView,
1350                        enterTransition, enteringViews, exitTransition, exitingViews,
1351                        sharedElementTransition, sharedElementTargets, transition, hiddenFragments);
1352            }
1353        }
1354    }
1355
1356    /**
1357     * After the transition has started, remove all targets that we added to the transitions
1358     * so that the transitions are left in a clean state.
1359     */
1360    private void removeTargetedViewsFromTransitions(
1361            final ViewGroup sceneRoot, final View nonExistingView,
1362            final Transition enterTransition, final ArrayList<View> enteringViews,
1363            final Transition exitTransition, final ArrayList<View> exitingViews,
1364            final Transition sharedElementTransition, final ArrayList<View> sharedElementTargets,
1365            final Transition overallTransition, final ArrayList<View> hiddenViews) {
1366        if (overallTransition != null) {
1367            sceneRoot.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
1368                @Override
1369                public boolean onPreDraw() {
1370                    sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
1371                    if (enterTransition != null) {
1372                        enterTransition.removeTarget(nonExistingView);
1373                        removeTargets(enterTransition, enteringViews);
1374                    }
1375                    if (exitTransition != null) {
1376                        removeTargets(exitTransition, exitingViews);
1377                    }
1378                    if (sharedElementTransition != null) {
1379                        removeTargets(sharedElementTransition, sharedElementTargets);
1380                    }
1381                    int numViews = hiddenViews.size();
1382                    for (int i = 0; i < numViews; i++) {
1383                        overallTransition.excludeTarget(hiddenViews.get(i), false);
1384                    }
1385                    overallTransition.excludeTarget(nonExistingView, false);
1386                    return true;
1387                }
1388            });
1389        }
1390    }
1391
1392    /**
1393     * This method removes the views from transitions that target ONLY those views.
1394     * The views list should match those added in addTargets and should contain
1395     * one view that is not in the view hierarchy (state.nonExistentView).
1396     */
1397    public static void removeTargets(Transition transition, ArrayList<View> views) {
1398        if (transition instanceof TransitionSet) {
1399            TransitionSet set = (TransitionSet) transition;
1400            int numTransitions = set.getTransitionCount();
1401            for (int i = 0; i < numTransitions; i++) {
1402                Transition child = set.getTransitionAt(i);
1403                removeTargets(child, views);
1404            }
1405        } else if (!hasSimpleTarget(transition)) {
1406            List<View> targets = transition.getTargets();
1407            if (targets != null && targets.size() == views.size() &&
1408                    targets.containsAll(views)) {
1409                // We have an exact match. We must have added these earlier in addTargets
1410                for (int i = views.size() - 1; i >= 0; i--) {
1411                    transition.removeTarget(views.get(i));
1412                }
1413            }
1414        }
1415    }
1416
1417    /**
1418     * This method adds views as targets to the transition, but only if the transition
1419     * doesn't already have a target. It is best for views to contain one View object
1420     * that does not exist in the view hierarchy (state.nonExistentView) so that
1421     * when they are removed later, a list match will suffice to remove the targets.
1422     * Otherwise, if you happened to have targeted the exact views for the transition,
1423     * the removeTargets call will remove them unexpectedly.
1424     */
1425    public static void addTargets(Transition transition, ArrayList<View> views) {
1426        if (transition instanceof TransitionSet) {
1427            TransitionSet set = (TransitionSet) transition;
1428            int numTransitions = set.getTransitionCount();
1429            for (int i = 0; i < numTransitions; i++) {
1430                Transition child = set.getTransitionAt(i);
1431                addTargets(child, views);
1432            }
1433        } else if (!hasSimpleTarget(transition)) {
1434            List<View> targets = transition.getTargets();
1435            if (isNullOrEmpty(targets)) {
1436                // We can just add the target views
1437                int numViews = views.size();
1438                for (int i = 0; i < numViews; i++) {
1439                    transition.addTarget(views.get(i));
1440                }
1441            }
1442        }
1443    }
1444
1445    private static boolean hasSimpleTarget(Transition transition) {
1446        return !isNullOrEmpty(transition.getTargetIds()) ||
1447                !isNullOrEmpty(transition.getTargetNames()) ||
1448                !isNullOrEmpty(transition.getTargetTypes());
1449    }
1450
1451    private static boolean isNullOrEmpty(List list) {
1452        return list == null || list.isEmpty();
1453    }
1454
1455    /**
1456     * Remaps a name-to-View map, substituting different names for keys.
1457     *
1458     * @param inMap A list of keys found in the map, in the order in toGoInMap
1459     * @param toGoInMap A list of keys to use for the new map, in the order of inMap
1460     * @param namedViews The current mapping
1461     * @return a new Map after it has been mapped with the new names as keys.
1462     */
1463    private static ArrayMap<String, View> remapNames(ArrayList<String> inMap,
1464            ArrayList<String> toGoInMap, ArrayMap<String, View> namedViews) {
1465        ArrayMap<String, View> remappedViews = new ArrayMap<String, View>();
1466        if (!namedViews.isEmpty()) {
1467            int numKeys = inMap.size();
1468            for (int i = 0; i < numKeys; i++) {
1469                View view = namedViews.get(inMap.get(i));
1470
1471                if (view != null) {
1472                    remappedViews.put(toGoInMap.get(i), view);
1473                }
1474            }
1475        }
1476        return remappedViews;
1477    }
1478
1479    /**
1480     * Maps shared elements to views in the entering fragment.
1481     *
1482     * @param state The transition State as returned from {@link #beginTransition(
1483     * android.util.SparseArray, android.util.SparseArray, boolean)}.
1484     * @param inFragment The last fragment to be added.
1485     * @param isBack true if this is popping the back stack or false if this is a
1486     *               forward operation.
1487     */
1488    private ArrayMap<String, View> mapEnteringSharedElements(TransitionState state,
1489            Fragment inFragment, boolean isBack) {
1490        ArrayMap<String, View> namedViews = new ArrayMap<String, View>();
1491        View root = inFragment.getView();
1492        if (root != null) {
1493            if (mSharedElementSourceNames != null) {
1494                root.findNamedViews(namedViews);
1495                if (isBack) {
1496                    namedViews = remapNames(mSharedElementSourceNames,
1497                            mSharedElementTargetNames, namedViews);
1498                } else {
1499                    namedViews.retainAll(mSharedElementTargetNames);
1500                }
1501            }
1502        }
1503        return namedViews;
1504    }
1505
1506    private void excludeHiddenFragments(final ArrayList<View> hiddenFragmentViews, int containerId,
1507            Transition transition) {
1508        if (mManager.mAdded != null) {
1509            for (int i = 0; i < mManager.mAdded.size(); i++) {
1510                Fragment fragment = mManager.mAdded.get(i);
1511                if (fragment.mView != null && fragment.mContainer != null &&
1512                        fragment.mContainerId == containerId) {
1513                    if (fragment.mHidden) {
1514                        if (!hiddenFragmentViews.contains(fragment.mView)) {
1515                            transition.excludeTarget(fragment.mView, true);
1516                            hiddenFragmentViews.add(fragment.mView);
1517                        }
1518                    } else {
1519                        transition.excludeTarget(fragment.mView, false);
1520                        hiddenFragmentViews.remove(fragment.mView);
1521                    }
1522                }
1523            }
1524        }
1525    }
1526
1527    private static void setEpicenter(Transition transition, View view) {
1528        final Rect epicenter = new Rect();
1529        view.getBoundsOnScreen(epicenter);
1530
1531        transition.setEpicenterCallback(new Transition.EpicenterCallback() {
1532            @Override
1533            public Rect onGetEpicenter(Transition transition) {
1534                return epicenter;
1535            }
1536        });
1537    }
1538
1539    private void setSharedElementEpicenter(Transition transition, final TransitionState state) {
1540        transition.setEpicenterCallback(new Transition.EpicenterCallback() {
1541            private Rect mEpicenter;
1542
1543            @Override
1544            public Rect onGetEpicenter(Transition transition) {
1545                if (mEpicenter == null && state.enteringEpicenterView != null) {
1546                    mEpicenter = new Rect();
1547                    state.enteringEpicenterView.getBoundsOnScreen(mEpicenter);
1548                }
1549                return mEpicenter;
1550            }
1551        });
1552    }
1553
1554    public TransitionState popFromBackStack(boolean doStateMove, TransitionState state,
1555            SparseArray<Fragment> firstOutFragments, SparseArray<Fragment> lastInFragments) {
1556        if (FragmentManagerImpl.DEBUG) {
1557            Log.v(TAG, "popFromBackStack: " + this);
1558            LogWriter logw = new LogWriter(Log.VERBOSE, TAG);
1559            PrintWriter pw = new FastPrintWriter(logw, false, 1024);
1560            dump("  ", null, pw, null);
1561            pw.flush();
1562        }
1563
1564        if (state == null) {
1565            if (firstOutFragments.size() != 0 || lastInFragments.size() != 0) {
1566                state = beginTransition(firstOutFragments, lastInFragments, true);
1567            }
1568        } else if (!doStateMove) {
1569            setNameOverrides(state, mSharedElementTargetNames, mSharedElementSourceNames);
1570        }
1571
1572        bumpBackStackNesting(-1);
1573
1574        Op op = mTail;
1575        while (op != null) {
1576            switch (op.cmd) {
1577                case OP_ADD: {
1578                    Fragment f = op.fragment;
1579                    f.mNextAnim = op.popExitAnim;
1580                    mManager.removeFragment(f,
1581                            FragmentManagerImpl.reverseTransit(mTransition),
1582                            mTransitionStyle);
1583                }
1584                break;
1585                case OP_REPLACE: {
1586                    Fragment f = op.fragment;
1587                    if (f != null) {
1588                        f.mNextAnim = op.popExitAnim;
1589                        mManager.removeFragment(f,
1590                                FragmentManagerImpl.reverseTransit(mTransition),
1591                                mTransitionStyle);
1592                    }
1593                    if (op.removed != null) {
1594                        for (int i = 0; i < op.removed.size(); i++) {
1595                            Fragment old = op.removed.get(i);
1596                            old.mNextAnim = op.popEnterAnim;
1597                            mManager.addFragment(old, false);
1598                        }
1599                    }
1600                }
1601                break;
1602                case OP_REMOVE: {
1603                    Fragment f = op.fragment;
1604                    f.mNextAnim = op.popEnterAnim;
1605                    mManager.addFragment(f, false);
1606                }
1607                break;
1608                case OP_HIDE: {
1609                    Fragment f = op.fragment;
1610                    f.mNextAnim = op.popEnterAnim;
1611                    mManager.showFragment(f,
1612                            FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
1613                }
1614                break;
1615                case OP_SHOW: {
1616                    Fragment f = op.fragment;
1617                    f.mNextAnim = op.popExitAnim;
1618                    mManager.hideFragment(f,
1619                            FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
1620                }
1621                break;
1622                case OP_DETACH: {
1623                    Fragment f = op.fragment;
1624                    f.mNextAnim = op.popEnterAnim;
1625                    mManager.attachFragment(f,
1626                            FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
1627                }
1628                break;
1629                case OP_ATTACH: {
1630                    Fragment f = op.fragment;
1631                    f.mNextAnim = op.popExitAnim;
1632                    mManager.detachFragment(f,
1633                            FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
1634                }
1635                break;
1636                default: {
1637                    throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
1638                }
1639            }
1640
1641            op = op.prev;
1642        }
1643
1644        if (doStateMove) {
1645            mManager.moveToState(mManager.mCurState,
1646                    FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle, true);
1647            state = null;
1648        }
1649
1650        if (mIndex >= 0) {
1651            mManager.freeBackStackIndex(mIndex);
1652            mIndex = -1;
1653        }
1654        return state;
1655    }
1656
1657    private static void setNameOverride(ArrayMap<String, String> overrides,
1658            String source, String target) {
1659        if (source != null && target != null && !source.equals(target)) {
1660            for (int index = 0; index < overrides.size(); index++) {
1661                if (source.equals(overrides.valueAt(index))) {
1662                    overrides.setValueAt(index, target);
1663                    return;
1664                }
1665            }
1666            overrides.put(source, target);
1667        }
1668    }
1669
1670    private static void setNameOverrides(TransitionState state, ArrayList<String> sourceNames,
1671            ArrayList<String> targetNames) {
1672        if (sourceNames != null) {
1673            for (int i = 0; i < sourceNames.size(); i++) {
1674                String source = sourceNames.get(i);
1675                String target = targetNames.get(i);
1676                setNameOverride(state.nameOverrides, source, target);
1677            }
1678        }
1679    }
1680
1681    private void setBackNameOverrides(TransitionState state, ArrayMap<String, View> namedViews,
1682            boolean isEnd) {
1683        int count = mSharedElementTargetNames.size();
1684        for (int i = 0; i < count; i++) {
1685            String source = mSharedElementSourceNames.get(i);
1686            String originalTarget = mSharedElementTargetNames.get(i);
1687            View view = namedViews.get(originalTarget);
1688            if (view != null) {
1689                String target = view.getTransitionName();
1690                if (isEnd) {
1691                    setNameOverride(state.nameOverrides, source, target);
1692                } else {
1693                    setNameOverride(state.nameOverrides, target, source);
1694                }
1695            }
1696        }
1697    }
1698
1699    private void setNameOverrides(TransitionState state, ArrayMap<String, View> namedViews,
1700            boolean isEnd) {
1701        int count = namedViews.size();
1702        for (int i = 0; i < count; i++) {
1703            String source = namedViews.keyAt(i);
1704            String target = namedViews.valueAt(i).getTransitionName();
1705            if (isEnd) {
1706                setNameOverride(state.nameOverrides, source, target);
1707            } else {
1708                setNameOverride(state.nameOverrides, target, source);
1709            }
1710        }
1711    }
1712
1713    public String getName() {
1714        return mName;
1715    }
1716
1717    public int getTransition() {
1718        return mTransition;
1719    }
1720
1721    public int getTransitionStyle() {
1722        return mTransitionStyle;
1723    }
1724
1725    public boolean isEmpty() {
1726        return mNumOp == 0;
1727    }
1728
1729    public class TransitionState {
1730        public ArrayMap<String, String> nameOverrides = new ArrayMap<String, String>();
1731        public View enteringEpicenterView;
1732        public View nonExistentView;
1733    }
1734}
1735