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