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