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