BackStackRecord.java revision 5f3a05c15fa636e911a646e35765ba8bbed7d5e1
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.content.Context;
20import android.content.pm.ApplicationInfo;
21import android.os.Build;
22import android.os.Parcel;
23import android.os.Parcelable;
24import android.text.TextUtils;
25import android.util.Log;
26import android.util.LogWriter;
27import android.view.View;
28
29import com.android.internal.util.FastPrintWriter;
30
31import java.io.FileDescriptor;
32import java.io.PrintWriter;
33import java.lang.reflect.Modifier;
34import java.util.ArrayList;
35
36final class BackStackState implements Parcelable {
37    final int[] mOps;
38    final int mTransition;
39    final int mTransitionStyle;
40    final String mName;
41    final int mIndex;
42    final int mBreadCrumbTitleRes;
43    final CharSequence mBreadCrumbTitleText;
44    final int mBreadCrumbShortTitleRes;
45    final CharSequence mBreadCrumbShortTitleText;
46    final ArrayList<String> mSharedElementSourceNames;
47    final ArrayList<String> mSharedElementTargetNames;
48    final boolean mAllowOptimization;
49
50    public BackStackState(FragmentManagerImpl fm, BackStackRecord bse) {
51        final int numOps = bse.mOps.size();
52        mOps = new int[numOps * 6];
53
54        if (!bse.mAddToBackStack) {
55            throw new IllegalStateException("Not on back stack");
56        }
57
58        int pos = 0;
59        for (int opNum = 0; opNum < numOps; opNum++) {
60            final BackStackRecord.Op op = bse.mOps.get(opNum);
61            mOps[pos++] = op.cmd;
62            mOps[pos++] = op.fragment != null ? op.fragment.mIndex : -1;
63            mOps[pos++] = op.enterAnim;
64            mOps[pos++] = op.exitAnim;
65            mOps[pos++] = op.popEnterAnim;
66            mOps[pos++] = op.popExitAnim;
67        }
68        mTransition = bse.mTransition;
69        mTransitionStyle = bse.mTransitionStyle;
70        mName = bse.mName;
71        mIndex = bse.mIndex;
72        mBreadCrumbTitleRes = bse.mBreadCrumbTitleRes;
73        mBreadCrumbTitleText = bse.mBreadCrumbTitleText;
74        mBreadCrumbShortTitleRes = bse.mBreadCrumbShortTitleRes;
75        mBreadCrumbShortTitleText = bse.mBreadCrumbShortTitleText;
76        mSharedElementSourceNames = bse.mSharedElementSourceNames;
77        mSharedElementTargetNames = bse.mSharedElementTargetNames;
78        mAllowOptimization = bse.mAllowOptimization;
79    }
80
81    public BackStackState(Parcel in) {
82        mOps = in.createIntArray();
83        mTransition = in.readInt();
84        mTransitionStyle = in.readInt();
85        mName = in.readString();
86        mIndex = in.readInt();
87        mBreadCrumbTitleRes = in.readInt();
88        mBreadCrumbTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
89        mBreadCrumbShortTitleRes = in.readInt();
90        mBreadCrumbShortTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
91        mSharedElementSourceNames = in.createStringArrayList();
92        mSharedElementTargetNames = in.createStringArrayList();
93        mAllowOptimization = in.readInt() != 0;
94    }
95
96    public BackStackRecord instantiate(FragmentManagerImpl fm) {
97        BackStackRecord bse = new BackStackRecord(fm);
98        int pos = 0;
99        int num = 0;
100        while (pos < mOps.length) {
101            BackStackRecord.Op op = new BackStackRecord.Op();
102            op.cmd = mOps[pos++];
103            if (FragmentManagerImpl.DEBUG) {
104                Log.v(FragmentManagerImpl.TAG,
105                        "Instantiate " + bse + " op #" + num + " base fragment #" + mOps[pos]);
106            }
107            int findex = mOps[pos++];
108            if (findex >= 0) {
109                Fragment f = fm.mActive.get(findex);
110                op.fragment = f;
111            } else {
112                op.fragment = null;
113            }
114            op.enterAnim = mOps[pos++];
115            op.exitAnim = mOps[pos++];
116            op.popEnterAnim = mOps[pos++];
117            op.popExitAnim = mOps[pos++];
118            bse.mEnterAnim = op.enterAnim;
119            bse.mExitAnim = op.exitAnim;
120            bse.mPopEnterAnim = op.popEnterAnim;
121            bse.mPopExitAnim = op.popExitAnim;
122            bse.addOp(op);
123            num++;
124        }
125        bse.mTransition = mTransition;
126        bse.mTransitionStyle = mTransitionStyle;
127        bse.mName = mName;
128        bse.mIndex = mIndex;
129        bse.mAddToBackStack = true;
130        bse.mBreadCrumbTitleRes = mBreadCrumbTitleRes;
131        bse.mBreadCrumbTitleText = mBreadCrumbTitleText;
132        bse.mBreadCrumbShortTitleRes = mBreadCrumbShortTitleRes;
133        bse.mBreadCrumbShortTitleText = mBreadCrumbShortTitleText;
134        bse.mSharedElementSourceNames = mSharedElementSourceNames;
135        bse.mSharedElementTargetNames = mSharedElementTargetNames;
136        bse.mAllowOptimization = mAllowOptimization;
137        bse.bumpBackStackNesting(1);
138        return bse;
139    }
140
141    public int describeContents() {
142        return 0;
143    }
144
145    public void writeToParcel(Parcel dest, int flags) {
146        dest.writeIntArray(mOps);
147        dest.writeInt(mTransition);
148        dest.writeInt(mTransitionStyle);
149        dest.writeString(mName);
150        dest.writeInt(mIndex);
151        dest.writeInt(mBreadCrumbTitleRes);
152        TextUtils.writeToParcel(mBreadCrumbTitleText, dest, 0);
153        dest.writeInt(mBreadCrumbShortTitleRes);
154        TextUtils.writeToParcel(mBreadCrumbShortTitleText, dest, 0);
155        dest.writeStringList(mSharedElementSourceNames);
156        dest.writeStringList(mSharedElementTargetNames);
157        dest.writeInt(mAllowOptimization ? 1 : 0);
158    }
159
160    public static final Parcelable.Creator<BackStackState> CREATOR
161            = new Parcelable.Creator<BackStackState>() {
162        public BackStackState createFromParcel(Parcel in) {
163            return new BackStackState(in);
164        }
165
166        public BackStackState[] newArray(int size) {
167            return new BackStackState[size];
168        }
169    };
170}
171
172/**
173 * @hide Entry of an operation on the fragment back stack.
174 */
175final class BackStackRecord extends FragmentTransaction implements
176        FragmentManager.BackStackEntry, FragmentManagerImpl.OpGenerator {
177    static final String TAG = FragmentManagerImpl.TAG;
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    boolean mAllowOptimization;
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        mAllowOptimization = 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 (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    public int commit() {
624        return commitInternal(false);
625    }
626
627    public int commitAllowingStateLoss() {
628        return commitInternal(true);
629    }
630
631    @Override
632    public void commitNow() {
633        disallowAddToBackStack();
634        mManager.execSingleAction(this, false);
635    }
636
637    @Override
638    public void commitNowAllowingStateLoss() {
639        disallowAddToBackStack();
640        mManager.execSingleAction(this, true);
641    }
642
643    @Override
644    public FragmentTransaction setAllowOptimization(boolean allowOptimization) {
645        mAllowOptimization = allowOptimization;
646        return this;
647    }
648
649    int commitInternal(boolean allowStateLoss) {
650        if (mCommitted) {
651            throw new IllegalStateException("commit already called");
652        }
653        if (FragmentManagerImpl.DEBUG) {
654            Log.v(TAG, "Commit: " + this);
655            LogWriter logw = new LogWriter(Log.VERBOSE, TAG);
656            PrintWriter pw = new FastPrintWriter(logw, false, 1024);
657            dump("  ", null, pw, null);
658            pw.flush();
659        }
660        mCommitted = true;
661        if (mAddToBackStack) {
662            mIndex = mManager.allocBackStackIndex(this);
663        } else {
664            mIndex = -1;
665        }
666        mManager.enqueueAction(this, allowStateLoss);
667        return mIndex;
668    }
669
670    /**
671     * Implementation of {@link android.app.FragmentManagerImpl.OpGenerator}.
672     * This operation is added to the list of pending actions during {@link #commit()}, and
673     * will be executed on the UI thread to run this FragmentTransaction.
674     *
675     * @param records Modified to add this BackStackRecord
676     * @param isRecordPop Modified to add a false (this isn't a pop)
677     * @return true always because the records and isRecordPop will always be changed
678     */
679    @Override
680    public boolean generateOps(ArrayList<BackStackRecord> records, ArrayList<Boolean> isRecordPop) {
681        if (FragmentManagerImpl.DEBUG) {
682            Log.v(TAG, "Run: " + this);
683        }
684
685        records.add(this);
686        isRecordPop.add(false);
687        if (mAddToBackStack) {
688            mManager.addBackStackState(this);
689        }
690        return true;
691    }
692
693    boolean interactsWith(int containerId) {
694        final int numOps = mOps.size();
695        for (int opNum = 0; opNum < numOps; opNum++) {
696            final Op op = mOps.get(opNum);
697            final int fragContainer = op.fragment != null ? op.fragment.mContainerId : 0;
698            if (fragContainer != 0 && fragContainer == containerId) {
699                return true;
700            }
701        }
702        return false;
703    }
704
705    boolean interactsWith(ArrayList<BackStackRecord> records, int startIndex, int endIndex) {
706        if (endIndex == startIndex) {
707            return false;
708        }
709        final int numOps = mOps.size();
710        int lastContainer = -1;
711        for (int opNum = 0; opNum < numOps; opNum++) {
712            final Op op = mOps.get(opNum);
713            final int container = op.fragment != null ? op.fragment.mContainerId : 0;
714            if (container != 0 && container != lastContainer) {
715                lastContainer = container;
716                for (int i = startIndex; i < endIndex; i++) {
717                    BackStackRecord record = records.get(i);
718                    final int numThoseOps = record.mOps.size();
719                    for (int thoseOpIndex = 0; thoseOpIndex < numThoseOps; thoseOpIndex++) {
720                        final Op thatOp = record.mOps.get(thoseOpIndex);
721                        final int thatContainer = thatOp.fragment != null
722                                ? thatOp.fragment.mContainerId : 0;
723                        if (thatContainer == container) {
724                            return true;
725                        }
726                    }
727                }
728            }
729        }
730        return false;
731    }
732
733    /**
734     * Executes the operations contained within this transaction. The Fragment states will only
735     * be modified if optimizations are not allowed.
736     */
737    void executeOps() {
738        final int numOps = mOps.size();
739        for (int opNum = 0; opNum < numOps; opNum++) {
740            final Op op = mOps.get(opNum);
741            final Fragment f = op.fragment;
742            if (f != null) {
743                f.setNextTransition(mTransition, mTransitionStyle);
744            }
745            switch (op.cmd) {
746                case OP_ADD:
747                    f.setNextAnim(op.enterAnim);
748                    mManager.addFragment(f, false);
749                    break;
750                case OP_REMOVE:
751                    f.setNextAnim(op.exitAnim);
752                    mManager.removeFragment(f);
753                    break;
754                case OP_HIDE:
755                    f.setNextAnim(op.exitAnim);
756                    mManager.hideFragment(f);
757                    break;
758                case OP_SHOW:
759                    f.setNextAnim(op.enterAnim);
760                    mManager.showFragment(f);
761                    break;
762                case OP_DETACH:
763                    f.setNextAnim(op.exitAnim);
764                    mManager.detachFragment(f);
765                    break;
766                case OP_ATTACH:
767                    f.setNextAnim(op.enterAnim);
768                    mManager.attachFragment(f);
769                    break;
770                case OP_SET_PRIMARY_NAV:
771                    mManager.setPrimaryNavigationFragment(f);
772                    break;
773                case OP_UNSET_PRIMARY_NAV:
774                    mManager.setPrimaryNavigationFragment(null);
775                    break;
776                default:
777                    throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
778            }
779            if (!mAllowOptimization && op.cmd != OP_ADD && f != null) {
780                mManager.moveFragmentToExpectedState(f);
781            }
782        }
783        if (!mAllowOptimization) {
784            // Added fragments are added at the end to comply with prior behavior.
785            mManager.moveToState(mManager.mCurState, true);
786        }
787    }
788
789    /**
790     * Reverses the execution of the operations within this transaction. The Fragment states will
791     * only be modified if optimizations are not allowed.
792     */
793    void executePopOps() {
794        for (int opNum = mOps.size() - 1; opNum >= 0; opNum--) {
795            final Op op = mOps.get(opNum);
796            Fragment f = op.fragment;
797            if (f != null) {
798                f.setNextTransition(FragmentManagerImpl.reverseTransit(mTransition),
799                        mTransitionStyle);
800            }
801            switch (op.cmd) {
802                case OP_ADD:
803                    f.setNextAnim(op.popExitAnim);
804                    mManager.removeFragment(f);
805                    break;
806                case OP_REMOVE:
807                    f.setNextAnim(op.popEnterAnim);
808                    mManager.addFragment(f, false);
809                    break;
810                case OP_HIDE:
811                    f.setNextAnim(op.popEnterAnim);
812                    mManager.showFragment(f);
813                    break;
814                case OP_SHOW:
815                    f.setNextAnim(op.popExitAnim);
816                    mManager.hideFragment(f);
817                    break;
818                case OP_DETACH:
819                    f.setNextAnim(op.popEnterAnim);
820                    mManager.attachFragment(f);
821                    break;
822                case OP_ATTACH:
823                    f.setNextAnim(op.popExitAnim);
824                    mManager.detachFragment(f);
825                    break;
826                case OP_SET_PRIMARY_NAV:
827                    mManager.setPrimaryNavigationFragment(null);
828                    break;
829                case OP_UNSET_PRIMARY_NAV:
830                    mManager.setPrimaryNavigationFragment(f);
831                    break;
832                default:
833                    throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
834            }
835            if (!mAllowOptimization && op.cmd != OP_REMOVE && f != null) {
836                mManager.moveFragmentToExpectedState(f);
837            }
838        }
839        if (!mAllowOptimization) {
840            mManager.moveToState(mManager.mCurState, true);
841        }
842    }
843
844    /**
845     * Expands all meta-ops into their more primitive equivalents. This must be called prior to
846     * {@link #executeOps()} or any other call that operations on mOps for forward navigation.
847     * It should not be called for pop/reverse navigation operations.
848     *
849     * <p>Removes all OP_REPLACE ops and replaces them with the proper add and remove
850     * operations that are equivalent to the replace.</p>
851     *
852     * <p>Adds OP_UNSET_PRIMARY_NAV ops to match OP_SET_PRIMARY_NAV, OP_REMOVE and OP_DETACH
853     * ops so that we can restore the old primary nav fragment later. Since callers call this
854     * method in a loop before running ops from several transactions at once, the caller should
855     * pass the return value from this method as the oldPrimaryNav parameter for the next call.
856     * The first call in such a loop should pass the value of
857     * {@link FragmentManager#getPrimaryNavigationFragment()}.</p>
858     *
859     * @param added Initialized to the fragments that are in the mManager.mAdded, this
860     *              will be modified to contain the fragments that will be in mAdded
861     *              after the execution ({@link #executeOps()}.
862     * @param oldPrimaryNav The tracked primary navigation fragment as of the beginning of
863     *                      this set of ops
864     * @return the new oldPrimaryNav fragment after this record's ops would be run
865     */
866    Fragment expandOps(ArrayList<Fragment> added, Fragment oldPrimaryNav) {
867        for (int opNum = 0; opNum < mOps.size(); opNum++) {
868            final Op op = mOps.get(opNum);
869            switch (op.cmd) {
870                case OP_ADD:
871                case OP_ATTACH:
872                    added.add(op.fragment);
873                    break;
874                case OP_REMOVE:
875                case OP_DETACH: {
876                    added.remove(op.fragment);
877                    if (op.fragment == oldPrimaryNav) {
878                        mOps.add(opNum, new Op(OP_UNSET_PRIMARY_NAV, op.fragment));
879                        opNum++;
880                        oldPrimaryNav = null;
881                    }
882                }
883                break;
884                case OP_REPLACE: {
885                    final Fragment f = op.fragment;
886                    final int containerId = f.mContainerId;
887                    boolean alreadyAdded = false;
888                    for (int i = added.size() - 1; i >= 0; i--) {
889                        final Fragment old = added.get(i);
890                        if (old.mContainerId == containerId) {
891                            if (old == f) {
892                                alreadyAdded = true;
893                            } else {
894                                // This is duplicated from above since we only make
895                                // a single pass for expanding ops. Unset any outgoing primary nav.
896                                if (old == oldPrimaryNav) {
897                                    mOps.add(opNum, new Op(OP_UNSET_PRIMARY_NAV, old));
898                                    opNum++;
899                                    oldPrimaryNav = null;
900                                }
901                                final Op removeOp = new Op(OP_REMOVE, old);
902                                removeOp.enterAnim = op.enterAnim;
903                                removeOp.popEnterAnim = op.popEnterAnim;
904                                removeOp.exitAnim = op.exitAnim;
905                                removeOp.popExitAnim = op.popExitAnim;
906                                mOps.add(opNum, removeOp);
907                                added.remove(old);
908                                opNum++;
909                            }
910                        }
911                    }
912                    if (alreadyAdded) {
913                        mOps.remove(opNum);
914                        opNum--;
915                    } else {
916                        op.cmd = OP_ADD;
917                        added.add(f);
918                    }
919                }
920                break;
921                case OP_SET_PRIMARY_NAV: {
922                    // It's ok if this is null, that means we will restore to no active
923                    // primary navigation fragment on a pop.
924                    mOps.add(opNum, new Op(OP_UNSET_PRIMARY_NAV, oldPrimaryNav));
925                    opNum++;
926                    // Will be set by the OP_SET_PRIMARY_NAV we inserted before when run
927                    oldPrimaryNav = op.fragment;
928                }
929                break;
930            }
931        }
932        return oldPrimaryNav;
933    }
934
935    /**
936     * Removes fragments that are added or removed during a pop operation.
937     *
938     * @param added Initialized to the fragments that are in the mManager.mAdded, this
939     *              will be modified to contain the fragments that will be in mAdded
940     *              after the execution ({@link #executeOps()}.
941     */
942    void trackAddedFragmentsInPop(ArrayList<Fragment> added) {
943        for (int opNum = 0; opNum < mOps.size(); opNum++) {
944            final Op op = mOps.get(opNum);
945            switch (op.cmd) {
946                case OP_ADD:
947                case OP_ATTACH:
948                    added.remove(op.fragment);
949                    break;
950                case OP_REMOVE:
951                case OP_DETACH:
952                    added.add(op.fragment);
953                    break;
954            }
955        }
956    }
957
958    boolean isPostponed() {
959        for (int opNum = 0; opNum < mOps.size(); opNum++) {
960            final Op op = mOps.get(opNum);
961            if (isFragmentPostponed(op)) {
962                return true;
963            }
964        }
965        return false;
966    }
967
968    void setOnStartPostponedListener(Fragment.OnStartEnterTransitionListener listener) {
969        for (int opNum = 0; opNum < mOps.size(); opNum++) {
970            final Op op = mOps.get(opNum);
971            if (isFragmentPostponed(op)) {
972                op.fragment.setOnStartEnterTransitionListener(listener);
973            }
974        }
975    }
976
977    private static boolean isFragmentPostponed(Op op) {
978        final Fragment fragment = op.fragment;
979        return fragment != null && fragment.mAdded && fragment.mView != null && !fragment.mDetached
980                && !fragment.mHidden && fragment.isPostponed();
981    }
982
983    public String getName() {
984        return mName;
985    }
986
987    public int getTransition() {
988        return mTransition;
989    }
990
991    public int getTransitionStyle() {
992        return mTransitionStyle;
993    }
994
995    public boolean isEmpty() {
996        return mOps.isEmpty();
997    }
998
999    /**
1000     * @return the target SDK of the FragmentManager's application info. If the
1001     * FragmentManager has been torn down, then 0 is returned.
1002     */
1003    private int getTargetSdk() {
1004        FragmentHostCallback host = mManager.mHost;
1005        if (host != null) {
1006            Context context = host.getContext();
1007            if (context != null) {
1008                ApplicationInfo info = context.getApplicationInfo();
1009                if (info != null) {
1010                    return info.targetSdkVersion;
1011                }
1012            }
1013        }
1014        return 0;
1015    }
1016}
1017