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.LogWriter;
23import android.support.v4.view.ViewCompat;
24import android.text.TextUtils;
25import android.util.Log;
26import android.view.View;
27
28import java.io.FileDescriptor;
29import java.io.PrintWriter;
30import java.lang.reflect.Modifier;
31import java.util.ArrayList;
32
33final class BackStackState implements Parcelable {
34    final int[] mOps;
35    final int mTransition;
36    final int mTransitionStyle;
37    final String mName;
38    final int mIndex;
39    final int mBreadCrumbTitleRes;
40    final CharSequence mBreadCrumbTitleText;
41    final int mBreadCrumbShortTitleRes;
42    final CharSequence mBreadCrumbShortTitleText;
43    final ArrayList<String> mSharedElementSourceNames;
44    final ArrayList<String> mSharedElementTargetNames;
45    final boolean mReorderingAllowed;
46
47    public BackStackState(BackStackRecord bse) {
48        final int numOps = bse.mOps.size();
49        mOps = new int[numOps * 6];
50
51        if (!bse.mAddToBackStack) {
52            throw new IllegalStateException("Not on back stack");
53        }
54
55        int pos = 0;
56        for (int opNum = 0; opNum < numOps; opNum++) {
57            final BackStackRecord.Op op = bse.mOps.get(opNum);
58            mOps[pos++] = op.cmd;
59            mOps[pos++] = op.fragment != null ? op.fragment.mIndex : -1;
60            mOps[pos++] = op.enterAnim;
61            mOps[pos++] = op.exitAnim;
62            mOps[pos++] = op.popEnterAnim;
63            mOps[pos++] = op.popExitAnim;
64        }
65        mTransition = bse.mTransition;
66        mTransitionStyle = bse.mTransitionStyle;
67        mName = bse.mName;
68        mIndex = bse.mIndex;
69        mBreadCrumbTitleRes = bse.mBreadCrumbTitleRes;
70        mBreadCrumbTitleText = bse.mBreadCrumbTitleText;
71        mBreadCrumbShortTitleRes = bse.mBreadCrumbShortTitleRes;
72        mBreadCrumbShortTitleText = bse.mBreadCrumbShortTitleText;
73        mSharedElementSourceNames = bse.mSharedElementSourceNames;
74        mSharedElementTargetNames = bse.mSharedElementTargetNames;
75        mReorderingAllowed = bse.mReorderingAllowed;
76    }
77
78    public BackStackState(Parcel in) {
79        mOps = in.createIntArray();
80        mTransition = in.readInt();
81        mTransitionStyle = in.readInt();
82        mName = in.readString();
83        mIndex = in.readInt();
84        mBreadCrumbTitleRes = in.readInt();
85        mBreadCrumbTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
86        mBreadCrumbShortTitleRes = in.readInt();
87        mBreadCrumbShortTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
88        mSharedElementSourceNames = in.createStringArrayList();
89        mSharedElementTargetNames = in.createStringArrayList();
90        mReorderingAllowed = in.readInt() != 0;
91    }
92
93    public BackStackRecord instantiate(FragmentManagerImpl fm) {
94        BackStackRecord bse = new BackStackRecord(fm);
95        int pos = 0;
96        int num = 0;
97        while (pos < mOps.length) {
98            BackStackRecord.Op op = new BackStackRecord.Op();
99            op.cmd = mOps[pos++];
100            if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG,
101                    "Instantiate " + bse + " op #" + num + " base fragment #" + mOps[pos]);
102            int findex = mOps[pos++];
103            if (findex >= 0) {
104                Fragment f = fm.mActive.get(findex);
105                op.fragment = f;
106            } else {
107                op.fragment = null;
108            }
109            op.enterAnim = mOps[pos++];
110            op.exitAnim = mOps[pos++];
111            op.popEnterAnim = mOps[pos++];
112            op.popExitAnim = mOps[pos++];
113            bse.mEnterAnim = op.enterAnim;
114            bse.mExitAnim = op.exitAnim;
115            bse.mPopEnterAnim = op.popEnterAnim;
116            bse.mPopExitAnim = op.popExitAnim;
117            bse.addOp(op);
118            num++;
119        }
120        bse.mTransition = mTransition;
121        bse.mTransitionStyle = mTransitionStyle;
122        bse.mName = mName;
123        bse.mIndex = mIndex;
124        bse.mAddToBackStack = true;
125        bse.mBreadCrumbTitleRes = mBreadCrumbTitleRes;
126        bse.mBreadCrumbTitleText = mBreadCrumbTitleText;
127        bse.mBreadCrumbShortTitleRes = mBreadCrumbShortTitleRes;
128        bse.mBreadCrumbShortTitleText = mBreadCrumbShortTitleText;
129        bse.mSharedElementSourceNames = mSharedElementSourceNames;
130        bse.mSharedElementTargetNames = mSharedElementTargetNames;
131        bse.mReorderingAllowed = mReorderingAllowed;
132        bse.bumpBackStackNesting(1);
133        return bse;
134    }
135
136    @Override
137    public int describeContents() {
138        return 0;
139    }
140
141    @Override
142    public void writeToParcel(Parcel dest, int flags) {
143        dest.writeIntArray(mOps);
144        dest.writeInt(mTransition);
145        dest.writeInt(mTransitionStyle);
146        dest.writeString(mName);
147        dest.writeInt(mIndex);
148        dest.writeInt(mBreadCrumbTitleRes);
149        TextUtils.writeToParcel(mBreadCrumbTitleText, dest, 0);
150        dest.writeInt(mBreadCrumbShortTitleRes);
151        TextUtils.writeToParcel(mBreadCrumbShortTitleText, dest, 0);
152        dest.writeStringList(mSharedElementSourceNames);
153        dest.writeStringList(mSharedElementTargetNames);
154        dest.writeInt(mReorderingAllowed ? 1 : 0);
155    }
156
157    public static final Parcelable.Creator<BackStackState> CREATOR
158            = new Parcelable.Creator<BackStackState>() {
159        @Override
160        public BackStackState createFromParcel(Parcel in) {
161            return new BackStackState(in);
162        }
163
164        @Override
165        public BackStackState[] newArray(int size) {
166            return new BackStackState[size];
167        }
168    };
169}
170
171/**
172 * Entry of an operation on the fragment back stack.
173 */
174final class BackStackRecord extends FragmentTransaction implements
175        FragmentManager.BackStackEntry, FragmentManagerImpl.OpGenerator {
176    static final String TAG = FragmentManagerImpl.TAG;
177    static final boolean SUPPORTS_TRANSITIONS = Build.VERSION.SDK_INT >= 21;
178
179    final FragmentManagerImpl mManager;
180
181    static final int OP_NULL = 0;
182    static final int OP_ADD = 1;
183    static final int OP_REPLACE = 2;
184    static final int OP_REMOVE = 3;
185    static final int OP_HIDE = 4;
186    static final int OP_SHOW = 5;
187    static final int OP_DETACH = 6;
188    static final int OP_ATTACH = 7;
189    static final int OP_SET_PRIMARY_NAV = 8;
190    static final int OP_UNSET_PRIMARY_NAV = 9;
191
192    static final class Op {
193        int cmd;
194        Fragment fragment;
195        int enterAnim;
196        int exitAnim;
197        int popEnterAnim;
198        int popExitAnim;
199
200        Op() {
201        }
202
203        Op(int cmd, Fragment fragment) {
204            this.cmd = cmd;
205            this.fragment = fragment;
206        }
207    }
208
209    ArrayList<Op> mOps = new ArrayList<>();
210    int mEnterAnim;
211    int mExitAnim;
212    int mPopEnterAnim;
213    int mPopExitAnim;
214    int mTransition;
215    int mTransitionStyle;
216    boolean mAddToBackStack;
217    boolean mAllowAddToBackStack = true;
218    String mName;
219    boolean mCommitted;
220    int mIndex = -1;
221
222    int mBreadCrumbTitleRes;
223    CharSequence mBreadCrumbTitleText;
224    int mBreadCrumbShortTitleRes;
225    CharSequence mBreadCrumbShortTitleText;
226
227    ArrayList<String> mSharedElementSourceNames;
228    ArrayList<String> mSharedElementTargetNames;
229    boolean mReorderingAllowed = false;
230
231    ArrayList<Runnable> mCommitRunnables;
232
233    @Override
234    public String toString() {
235        StringBuilder sb = new StringBuilder(128);
236        sb.append("BackStackEntry{");
237        sb.append(Integer.toHexString(System.identityHashCode(this)));
238        if (mIndex >= 0) {
239            sb.append(" #");
240            sb.append(mIndex);
241        }
242        if (mName != null) {
243            sb.append(" ");
244            sb.append(mName);
245        }
246        sb.append("}");
247        return sb.toString();
248    }
249
250    public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
251        dump(prefix, writer, true);
252    }
253
254    public void dump(String prefix, PrintWriter writer, boolean full) {
255        if (full) {
256            writer.print(prefix); writer.print("mName="); writer.print(mName);
257                    writer.print(" mIndex="); writer.print(mIndex);
258                    writer.print(" mCommitted="); writer.println(mCommitted);
259            if (mTransition != FragmentTransaction.TRANSIT_NONE) {
260                writer.print(prefix); writer.print("mTransition=#");
261                        writer.print(Integer.toHexString(mTransition));
262                        writer.print(" mTransitionStyle=#");
263                        writer.println(Integer.toHexString(mTransitionStyle));
264            }
265            if (mEnterAnim != 0 || mExitAnim !=0) {
266                writer.print(prefix); writer.print("mEnterAnim=#");
267                        writer.print(Integer.toHexString(mEnterAnim));
268                        writer.print(" mExitAnim=#");
269                        writer.println(Integer.toHexString(mExitAnim));
270            }
271            if (mPopEnterAnim != 0 || mPopExitAnim !=0) {
272                writer.print(prefix); writer.print("mPopEnterAnim=#");
273                        writer.print(Integer.toHexString(mPopEnterAnim));
274                        writer.print(" mPopExitAnim=#");
275                        writer.println(Integer.toHexString(mPopExitAnim));
276            }
277            if (mBreadCrumbTitleRes != 0 || mBreadCrumbTitleText != null) {
278                writer.print(prefix); writer.print("mBreadCrumbTitleRes=#");
279                        writer.print(Integer.toHexString(mBreadCrumbTitleRes));
280                        writer.print(" mBreadCrumbTitleText=");
281                        writer.println(mBreadCrumbTitleText);
282            }
283            if (mBreadCrumbShortTitleRes != 0 || mBreadCrumbShortTitleText != null) {
284                writer.print(prefix); writer.print("mBreadCrumbShortTitleRes=#");
285                        writer.print(Integer.toHexString(mBreadCrumbShortTitleRes));
286                        writer.print(" mBreadCrumbShortTitleText=");
287                        writer.println(mBreadCrumbShortTitleText);
288            }
289        }
290
291        if (!mOps.isEmpty()) {
292            writer.print(prefix); writer.println("Operations:");
293            String innerPrefix = prefix + "    ";
294            final int numOps = mOps.size();
295            for (int opNum = 0; opNum < numOps; opNum++) {
296                final Op op = mOps.get(opNum);
297                String cmdStr;
298                switch (op.cmd) {
299                    case OP_NULL: cmdStr="NULL"; break;
300                    case OP_ADD: cmdStr="ADD"; break;
301                    case OP_REPLACE: cmdStr="REPLACE"; break;
302                    case OP_REMOVE: cmdStr="REMOVE"; break;
303                    case OP_HIDE: cmdStr="HIDE"; break;
304                    case OP_SHOW: cmdStr="SHOW"; break;
305                    case OP_DETACH: cmdStr="DETACH"; break;
306                    case OP_ATTACH: cmdStr="ATTACH"; break;
307                    case OP_SET_PRIMARY_NAV: cmdStr="SET_PRIMARY_NAV"; break;
308                    case OP_UNSET_PRIMARY_NAV: cmdStr="UNSET_PRIMARY_NAV";break;
309                    default: cmdStr="cmd=" + op.cmd; break;
310                }
311                writer.print(prefix); writer.print("  Op #"); writer.print(opNum);
312                        writer.print(": "); writer.print(cmdStr);
313                        writer.print(" "); writer.println(op.fragment);
314                if (full) {
315                    if (op.enterAnim != 0 || op.exitAnim != 0) {
316                        writer.print(prefix); writer.print("enterAnim=#");
317                                writer.print(Integer.toHexString(op.enterAnim));
318                                writer.print(" exitAnim=#");
319                                writer.println(Integer.toHexString(op.exitAnim));
320                    }
321                    if (op.popEnterAnim != 0 || op.popExitAnim != 0) {
322                        writer.print(prefix); writer.print("popEnterAnim=#");
323                                writer.print(Integer.toHexString(op.popEnterAnim));
324                                writer.print(" popExitAnim=#");
325                                writer.println(Integer.toHexString(op.popExitAnim));
326                    }
327                }
328            }
329        }
330    }
331
332    public BackStackRecord(FragmentManagerImpl manager) {
333        mManager = manager;
334    }
335
336    @Override
337    public int getId() {
338        return mIndex;
339    }
340
341    @Override
342    public int getBreadCrumbTitleRes() {
343        return mBreadCrumbTitleRes;
344    }
345
346    @Override
347    public int getBreadCrumbShortTitleRes() {
348        return mBreadCrumbShortTitleRes;
349    }
350
351    @Override
352    public CharSequence getBreadCrumbTitle() {
353        if (mBreadCrumbTitleRes != 0) {
354            return mManager.mHost.getContext().getText(mBreadCrumbTitleRes);
355        }
356        return mBreadCrumbTitleText;
357    }
358
359    @Override
360    public CharSequence getBreadCrumbShortTitle() {
361        if (mBreadCrumbShortTitleRes != 0) {
362            return mManager.mHost.getContext().getText(mBreadCrumbShortTitleRes);
363        }
364        return mBreadCrumbShortTitleText;
365    }
366
367    void addOp(Op op) {
368        mOps.add(op);
369        op.enterAnim = mEnterAnim;
370        op.exitAnim = mExitAnim;
371        op.popEnterAnim = mPopEnterAnim;
372        op.popExitAnim = mPopExitAnim;
373    }
374
375    @Override
376    public FragmentTransaction add(Fragment fragment, String tag) {
377        doAddOp(0, fragment, tag, OP_ADD);
378        return this;
379    }
380
381    @Override
382    public FragmentTransaction add(int containerViewId, Fragment fragment) {
383        doAddOp(containerViewId, fragment, null, OP_ADD);
384        return this;
385    }
386
387    @Override
388    public FragmentTransaction add(int containerViewId, Fragment fragment, String tag) {
389        doAddOp(containerViewId, fragment, tag, OP_ADD);
390        return this;
391    }
392
393    private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) {
394        final Class fragmentClass = fragment.getClass();
395        final int modifiers = fragmentClass.getModifiers();
396        if (fragmentClass.isAnonymousClass() || !Modifier.isPublic(modifiers)
397                || (fragmentClass.isMemberClass() && !Modifier.isStatic(modifiers))) {
398            throw new IllegalStateException("Fragment " + fragmentClass.getCanonicalName()
399                    + " must be a public static class to be  properly recreated from"
400                    + " instance state.");
401        }
402
403        fragment.mFragmentManager = mManager;
404
405        if (tag != null) {
406            if (fragment.mTag != null && !tag.equals(fragment.mTag)) {
407                throw new IllegalStateException("Can't change tag of fragment "
408                        + fragment + ": was " + fragment.mTag
409                        + " now " + tag);
410            }
411            fragment.mTag = tag;
412        }
413
414        if (containerViewId != 0) {
415            if (containerViewId == View.NO_ID) {
416                throw new IllegalArgumentException("Can't add fragment "
417                        + fragment + " with tag " + tag + " to container view with no id");
418            }
419            if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) {
420                throw new IllegalStateException("Can't change container ID of fragment "
421                        + fragment + ": was " + fragment.mFragmentId
422                        + " now " + containerViewId);
423            }
424            fragment.mContainerId = fragment.mFragmentId = containerViewId;
425        }
426
427        addOp(new Op(opcmd, fragment));
428    }
429
430    @Override
431    public FragmentTransaction replace(int containerViewId, Fragment fragment) {
432        return replace(containerViewId, fragment, null);
433    }
434
435    @Override
436    public FragmentTransaction replace(int containerViewId, Fragment fragment, String tag) {
437        if (containerViewId == 0) {
438            throw new IllegalArgumentException("Must use non-zero containerViewId");
439        }
440
441        doAddOp(containerViewId, fragment, tag, OP_REPLACE);
442        return this;
443    }
444
445    @Override
446    public FragmentTransaction remove(Fragment fragment) {
447        addOp(new Op(OP_REMOVE, fragment));
448
449        return this;
450    }
451
452    @Override
453    public FragmentTransaction hide(Fragment fragment) {
454        addOp(new Op(OP_HIDE, fragment));
455
456        return this;
457    }
458
459    @Override
460    public FragmentTransaction show(Fragment fragment) {
461        addOp(new Op(OP_SHOW, fragment));
462
463        return this;
464    }
465
466    @Override
467    public FragmentTransaction detach(Fragment fragment) {
468        addOp(new Op(OP_DETACH, fragment));
469
470        return this;
471    }
472
473    @Override
474    public FragmentTransaction attach(Fragment fragment) {
475        addOp(new Op(OP_ATTACH, fragment));
476
477        return this;
478    }
479
480    @Override
481    public FragmentTransaction setPrimaryNavigationFragment(Fragment fragment) {
482        addOp(new Op(OP_SET_PRIMARY_NAV, fragment));
483
484        return this;
485    }
486
487    @Override
488    public FragmentTransaction setCustomAnimations(int enter, int exit) {
489        return setCustomAnimations(enter, exit, 0, 0);
490    }
491
492    @Override
493    public FragmentTransaction setCustomAnimations(int enter, int exit,
494            int popEnter, int popExit) {
495        mEnterAnim = enter;
496        mExitAnim = exit;
497        mPopEnterAnim = popEnter;
498        mPopExitAnim = popExit;
499        return this;
500    }
501
502    @Override
503    public FragmentTransaction setTransition(int transition) {
504        mTransition = transition;
505        return this;
506    }
507
508    @Override
509    public FragmentTransaction addSharedElement(View sharedElement, String name) {
510        if (SUPPORTS_TRANSITIONS) {
511            String transitionName = ViewCompat.getTransitionName(sharedElement);
512            if (transitionName == null) {
513                throw new IllegalArgumentException("Unique transitionNames are required for all" +
514                        " sharedElements");
515            }
516            if (mSharedElementSourceNames == null) {
517                mSharedElementSourceNames = new ArrayList<String>();
518                mSharedElementTargetNames = new ArrayList<String>();
519            } else if (mSharedElementTargetNames.contains(name)) {
520                throw new IllegalArgumentException("A shared element with the target name '"
521                        + name + "' has already been added to the transaction.");
522            } else if (mSharedElementSourceNames.contains(transitionName)) {
523                throw new IllegalArgumentException("A shared element with the source name '"
524                        + transitionName + " has already been added to the transaction.");
525            }
526
527            mSharedElementSourceNames.add(transitionName);
528            mSharedElementTargetNames.add(name);
529        }
530        return this;
531    }
532
533    @Override
534    public FragmentTransaction setTransitionStyle(int styleRes) {
535        mTransitionStyle = styleRes;
536        return this;
537    }
538
539    @Override
540    public FragmentTransaction addToBackStack(String name) {
541        if (!mAllowAddToBackStack) {
542            throw new IllegalStateException(
543                    "This FragmentTransaction is not allowed to be added to the back stack.");
544        }
545        mAddToBackStack = true;
546        mName = name;
547        return this;
548    }
549
550    @Override
551    public boolean isAddToBackStackAllowed() {
552        return mAllowAddToBackStack;
553    }
554
555    @Override
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    @Override
566    public FragmentTransaction setBreadCrumbTitle(int res) {
567        mBreadCrumbTitleRes = res;
568        mBreadCrumbTitleText = null;
569        return this;
570    }
571
572    @Override
573    public FragmentTransaction setBreadCrumbTitle(CharSequence text) {
574        mBreadCrumbTitleRes = 0;
575        mBreadCrumbTitleText = text;
576        return this;
577    }
578
579    @Override
580    public FragmentTransaction setBreadCrumbShortTitle(int res) {
581        mBreadCrumbShortTitleRes = res;
582        mBreadCrumbShortTitleText = null;
583        return this;
584    }
585
586    @Override
587    public FragmentTransaction setBreadCrumbShortTitle(CharSequence text) {
588        mBreadCrumbShortTitleRes = 0;
589        mBreadCrumbShortTitleText = text;
590        return this;
591    }
592
593    void bumpBackStackNesting(int amt) {
594        if (!mAddToBackStack) {
595            return;
596        }
597        if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting in " + this
598                + " by " + amt);
599        final int numOps = mOps.size();
600        for (int opNum = 0; opNum < numOps; opNum++) {
601            final Op op = mOps.get(opNum);
602            if (op.fragment != null) {
603                op.fragment.mBackStackNesting += amt;
604                if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting of "
605                        + op.fragment + " to " + op.fragment.mBackStackNesting);
606            }
607        }
608    }
609
610    @Override
611    public FragmentTransaction runOnCommit(Runnable runnable) {
612        if (runnable == null) {
613            throw new IllegalArgumentException("runnable cannot be null");
614        }
615        disallowAddToBackStack();
616        if (mCommitRunnables == null) {
617            mCommitRunnables = new ArrayList<>();
618        }
619        mCommitRunnables.add(runnable);
620        return this;
621    }
622
623    public void runOnCommitRunnables() {
624        if (mCommitRunnables != null) {
625            for (int i = 0, N = mCommitRunnables.size(); i < N; i++) {
626                mCommitRunnables.get(i).run();
627            }
628            mCommitRunnables = null;
629        }
630    }
631
632    @Override
633    public int commit() {
634        return commitInternal(false);
635    }
636
637    @Override
638    public int commitAllowingStateLoss() {
639        return commitInternal(true);
640    }
641
642    @Override
643    public void commitNow() {
644        disallowAddToBackStack();
645        mManager.execSingleAction(this, false);
646    }
647
648    @Override
649    public void commitNowAllowingStateLoss() {
650        disallowAddToBackStack();
651        mManager.execSingleAction(this, true);
652    }
653
654    @Override
655    public FragmentTransaction setReorderingAllowed(boolean reorderingAllowed) {
656        mReorderingAllowed = reorderingAllowed;
657        return this;
658    }
659
660    @Override
661    public FragmentTransaction setAllowOptimization(boolean allowOptimization) {
662        return setReorderingAllowed(allowOptimization);
663    }
664
665    int commitInternal(boolean allowStateLoss) {
666        if (mCommitted) throw new IllegalStateException("commit already called");
667        if (FragmentManagerImpl.DEBUG) {
668            Log.v(TAG, "Commit: " + this);
669            LogWriter logw = new LogWriter(TAG);
670            PrintWriter pw = new PrintWriter(logw);
671            dump("  ", null, pw, null);
672            pw.close();
673        }
674        mCommitted = true;
675        if (mAddToBackStack) {
676            mIndex = mManager.allocBackStackIndex(this);
677        } else {
678            mIndex = -1;
679        }
680        mManager.enqueueAction(this, allowStateLoss);
681        return mIndex;
682    }
683
684    /**
685     * Implementation of {@link FragmentManagerImpl.OpGenerator}.
686     * This operation is added to the list of pending actions during {@link #commit()}, and
687     * will be executed on the UI thread to run this FragmentTransaction.
688     *
689     * @param records Modified to add this BackStackRecord
690     * @param isRecordPop Modified to add a false (this isn't a pop)
691     * @return true always because the records and isRecordPop will always be changed
692     */
693    @Override
694    public boolean generateOps(ArrayList<BackStackRecord> records, ArrayList<Boolean> isRecordPop) {
695        if (FragmentManagerImpl.DEBUG) {
696            Log.v(TAG, "Run: " + this);
697        }
698
699        records.add(this);
700        isRecordPop.add(false);
701        if (mAddToBackStack) {
702            mManager.addBackStackState(this);
703        }
704        return true;
705    }
706
707    boolean interactsWith(int containerId) {
708        final int numOps = mOps.size();
709        for (int opNum = 0; opNum < numOps; opNum++) {
710            final Op op = mOps.get(opNum);
711            final int fragContainer = op.fragment != null ? op.fragment.mContainerId : 0;
712            if (fragContainer != 0 && fragContainer == containerId) {
713                return true;
714            }
715        }
716        return false;
717    }
718
719    boolean interactsWith(ArrayList<BackStackRecord> records, int startIndex, int endIndex) {
720        if (endIndex == startIndex) {
721            return false;
722        }
723        final int numOps = mOps.size();
724        int lastContainer = -1;
725        for (int opNum = 0; opNum < numOps; opNum++) {
726            final Op op = mOps.get(opNum);
727            final int container = op.fragment != null ? op.fragment.mContainerId : 0;
728            if (container != 0 && container != lastContainer) {
729                lastContainer = container;
730                for (int i = startIndex; i < endIndex; i++) {
731                    BackStackRecord record = records.get(i);
732                    final int numThoseOps = record.mOps.size();
733                    for (int thoseOpIndex = 0; thoseOpIndex < numThoseOps; thoseOpIndex++) {
734                        final Op thatOp = record.mOps.get(thoseOpIndex);
735                        final int thatContainer = thatOp.fragment != null
736                                ? thatOp.fragment.mContainerId : 0;
737                        if (thatContainer == container) {
738                            return true;
739                        }
740                    }
741                }
742            }
743        }
744        return false;
745    }
746
747    /**
748     * Executes the operations contained within this transaction. The Fragment states will only
749     * be modified if optimizations are not allowed.
750     */
751    void executeOps() {
752        final int numOps = mOps.size();
753        for (int opNum = 0; opNum < numOps; opNum++) {
754            final Op op = mOps.get(opNum);
755            final Fragment f = op.fragment;
756            if (f != null) {
757                f.setNextTransition(mTransition, mTransitionStyle);
758            }
759            switch (op.cmd) {
760                case OP_ADD:
761                    f.setNextAnim(op.enterAnim);
762                    mManager.addFragment(f, false);
763                    break;
764                case OP_REMOVE:
765                    f.setNextAnim(op.exitAnim);
766                    mManager.removeFragment(f);
767                    break;
768                case OP_HIDE:
769                    f.setNextAnim(op.exitAnim);
770                    mManager.hideFragment(f);
771                    break;
772                case OP_SHOW:
773                    f.setNextAnim(op.enterAnim);
774                    mManager.showFragment(f);
775                    break;
776                case OP_DETACH:
777                    f.setNextAnim(op.exitAnim);
778                    mManager.detachFragment(f);
779                    break;
780                case OP_ATTACH:
781                    f.setNextAnim(op.enterAnim);
782                    mManager.attachFragment(f);
783                    break;
784                case OP_SET_PRIMARY_NAV:
785                    mManager.setPrimaryNavigationFragment(f);
786                    break;
787                case OP_UNSET_PRIMARY_NAV:
788                    mManager.setPrimaryNavigationFragment(null);
789                    break;
790                default:
791                    throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
792            }
793            if (!mReorderingAllowed && op.cmd != OP_ADD && f != null) {
794                mManager.moveFragmentToExpectedState(f);
795            }
796        }
797        if (!mReorderingAllowed) {
798            // Added fragments are added at the end to comply with prior behavior.
799            mManager.moveToState(mManager.mCurState, true);
800        }
801    }
802
803    /**
804     * Reverses the execution of the operations within this transaction. The Fragment states will
805     * only be modified if reordering is not allowed.
806     *
807     * @param moveToState {@code true} if added fragments should be moved to their final state
808     *                    in ordered transactions
809     */
810    void executePopOps(boolean moveToState) {
811        for (int opNum = mOps.size() - 1; opNum >= 0; opNum--) {
812            final Op op = mOps.get(opNum);
813            Fragment f = op.fragment;
814            if (f != null) {
815                f.setNextTransition(FragmentManagerImpl.reverseTransit(mTransition),
816                        mTransitionStyle);
817            }
818            switch (op.cmd) {
819                case OP_ADD:
820                    f.setNextAnim(op.popExitAnim);
821                    mManager.removeFragment(f);
822                    break;
823                case OP_REMOVE:
824                    f.setNextAnim(op.popEnterAnim);
825                    mManager.addFragment(f, false);
826                    break;
827                case OP_HIDE:
828                    f.setNextAnim(op.popEnterAnim);
829                    mManager.showFragment(f);
830                    break;
831                case OP_SHOW:
832                    f.setNextAnim(op.popExitAnim);
833                    mManager.hideFragment(f);
834                    break;
835                case OP_DETACH:
836                    f.setNextAnim(op.popEnterAnim);
837                    mManager.attachFragment(f);
838                    break;
839                case OP_ATTACH:
840                    f.setNextAnim(op.popExitAnim);
841                    mManager.detachFragment(f);
842                    break;
843                case OP_SET_PRIMARY_NAV:
844                    mManager.setPrimaryNavigationFragment(null);
845                    break;
846                case OP_UNSET_PRIMARY_NAV:
847                    mManager.setPrimaryNavigationFragment(f);
848                    break;
849                default:
850                    throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
851            }
852            if (!mReorderingAllowed && op.cmd != OP_REMOVE && f != null) {
853                mManager.moveFragmentToExpectedState(f);
854            }
855        }
856        if (!mReorderingAllowed && moveToState) {
857            mManager.moveToState(mManager.mCurState, true);
858        }
859    }
860
861    /**
862     * Expands all meta-ops into their more primitive equivalents. This must be called prior to
863     * {@link #executeOps()} or any other call that operations on mOps for forward navigation.
864     * It should not be called for pop/reverse navigation operations.
865     *
866     * <p>Removes all OP_REPLACE ops and replaces them with the proper add and remove
867     * operations that are equivalent to the replace.</p>
868     *
869     * <p>Adds OP_UNSET_PRIMARY_NAV ops to match OP_SET_PRIMARY_NAV, OP_REMOVE and OP_DETACH
870     * ops so that we can restore the old primary nav fragment later. Since callers call this
871     * method in a loop before running ops from several transactions at once, the caller should
872     * pass the return value from this method as the oldPrimaryNav parameter for the next call.
873     * The first call in such a loop should pass the value of
874     * {@link FragmentManager#getPrimaryNavigationFragment()}.</p>
875     *
876     * @param added Initialized to the fragments that are in the mManager.mAdded, this
877     *              will be modified to contain the fragments that will be in mAdded
878     *              after the execution ({@link #executeOps()}.
879     * @param oldPrimaryNav The tracked primary navigation fragment as of the beginning of
880     *                      this set of ops
881     * @return the new oldPrimaryNav fragment after this record's ops would be run
882     */
883    @SuppressWarnings("ReferenceEquality")
884    Fragment expandOps(ArrayList<Fragment> added, Fragment oldPrimaryNav) {
885        for (int opNum = 0; opNum < mOps.size(); opNum++) {
886            final Op op = mOps.get(opNum);
887            switch (op.cmd) {
888                case OP_ADD:
889                case OP_ATTACH:
890                    added.add(op.fragment);
891                    break;
892                case OP_REMOVE:
893                case OP_DETACH: {
894                    added.remove(op.fragment);
895                    if (op.fragment == oldPrimaryNav) {
896                        mOps.add(opNum, new Op(OP_UNSET_PRIMARY_NAV, op.fragment));
897                        opNum++;
898                        oldPrimaryNav = null;
899                    }
900                }
901                break;
902                case OP_REPLACE: {
903                    final Fragment f = op.fragment;
904                    final int containerId = f.mContainerId;
905                    boolean alreadyAdded = false;
906                    for (int i = added.size() - 1; i >= 0; i--) {
907                        final Fragment old = added.get(i);
908                        if (old.mContainerId == containerId) {
909                            if (old == f) {
910                                alreadyAdded = true;
911                            } else {
912                                // This is duplicated from above since we only make
913                                // a single pass for expanding ops. Unset any outgoing primary nav.
914                                if (old == oldPrimaryNav) {
915                                    mOps.add(opNum, new Op(OP_UNSET_PRIMARY_NAV, old));
916                                    opNum++;
917                                    oldPrimaryNav = null;
918                                }
919                                final Op removeOp = new Op(OP_REMOVE, old);
920                                removeOp.enterAnim = op.enterAnim;
921                                removeOp.popEnterAnim = op.popEnterAnim;
922                                removeOp.exitAnim = op.exitAnim;
923                                removeOp.popExitAnim = op.popExitAnim;
924                                mOps.add(opNum, removeOp);
925                                added.remove(old);
926                                opNum++;
927                            }
928                        }
929                    }
930                    if (alreadyAdded) {
931                        mOps.remove(opNum);
932                        opNum--;
933                    } else {
934                        op.cmd = OP_ADD;
935                        added.add(f);
936                    }
937                }
938                break;
939                case OP_SET_PRIMARY_NAV: {
940                    // It's ok if this is null, that means we will restore to no active
941                    // primary navigation fragment on a pop.
942                    mOps.add(opNum, new Op(OP_UNSET_PRIMARY_NAV, oldPrimaryNav));
943                    opNum++;
944                    // Will be set by the OP_SET_PRIMARY_NAV we inserted before when run
945                    oldPrimaryNav = op.fragment;
946                }
947                break;
948            }
949        }
950        return oldPrimaryNav;
951    }
952
953    /**
954     * Removes fragments that are added or removed during a pop operation.
955     *
956     * @param added Initialized to the fragments that are in the mManager.mAdded, this
957     *              will be modified to contain the fragments that will be in mAdded
958     *              after the execution ({@link #executeOps()}.
959     * @param oldPrimaryNav The tracked primary navigation fragment as of the beginning of
960     *                      this set of ops
961     * @return the new oldPrimaryNav fragment after this record's ops would be popped
962     */
963    Fragment trackAddedFragmentsInPop(ArrayList<Fragment> added, Fragment oldPrimaryNav) {
964        for (int opNum = 0; opNum < mOps.size(); opNum++) {
965            final Op op = mOps.get(opNum);
966            switch (op.cmd) {
967                case OP_ADD:
968                case OP_ATTACH:
969                    added.remove(op.fragment);
970                    break;
971                case OP_REMOVE:
972                case OP_DETACH:
973                    added.add(op.fragment);
974                    break;
975                case OP_UNSET_PRIMARY_NAV:
976                    oldPrimaryNav = op.fragment;
977                    break;
978                case OP_SET_PRIMARY_NAV:
979                    oldPrimaryNav = null;
980                    break;
981            }
982        }
983        return oldPrimaryNav;
984    }
985
986    boolean isPostponed() {
987        for (int opNum = 0; opNum < mOps.size(); opNum++) {
988            final Op op = mOps.get(opNum);
989            if (isFragmentPostponed(op)) {
990                return true;
991            }
992        }
993        return false;
994    }
995
996    void setOnStartPostponedListener(Fragment.OnStartEnterTransitionListener listener) {
997        for (int opNum = 0; opNum < mOps.size(); opNum++) {
998            final Op op = mOps.get(opNum);
999            if (isFragmentPostponed(op)) {
1000                op.fragment.setOnStartEnterTransitionListener(listener);
1001            }
1002        }
1003    }
1004
1005    private static boolean isFragmentPostponed(Op op) {
1006        final Fragment fragment = op.fragment;
1007        return fragment != null && fragment.mAdded && fragment.mView != null && !fragment.mDetached
1008                && !fragment.mHidden && fragment.isPostponed();
1009    }
1010
1011    @Override
1012    public String getName() {
1013        return mName;
1014    }
1015
1016    public int getTransition() {
1017        return mTransition;
1018    }
1019
1020    public int getTransitionStyle() {
1021        return mTransitionStyle;
1022    }
1023
1024    @Override
1025    public boolean isEmpty() {
1026        return mOps.isEmpty();
1027    }
1028}
1029