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