BackStackRecord.java revision cba2e2c881e8e16ea5025b564c94320174d65f01
1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.support.v4.app;
18
19import android.os.Parcel;
20import android.os.Parcelable;
21import android.text.TextUtils;
22import android.util.Log;
23
24import java.io.FileDescriptor;
25import java.io.PrintWriter;
26import java.util.ArrayList;
27
28final class BackStackState implements Parcelable {
29    final int[] mOps;
30    final int mTransition;
31    final int mTransitionStyle;
32    final String mName;
33    final int mIndex;
34    final int mBreadCrumbTitleRes;
35    final CharSequence mBreadCrumbTitleText;
36    final int mBreadCrumbShortTitleRes;
37    final CharSequence mBreadCrumbShortTitleText;
38
39    public BackStackState(FragmentManagerImpl fm, BackStackRecord bse) {
40        int numRemoved = 0;
41        BackStackRecord.Op op = bse.mHead;
42        while (op != null) {
43            if (op.removed != null) numRemoved += op.removed.size();
44            op = op.next;
45        }
46        mOps = new int[bse.mNumOp*5 + numRemoved];
47
48        if (!bse.mAddToBackStack) {
49            throw new IllegalStateException("Not on back stack");
50        }
51
52        op = bse.mHead;
53        int pos = 0;
54        while (op != null) {
55            mOps[pos++] = op.cmd;
56            mOps[pos++] = op.fragment.mIndex;
57            mOps[pos++] = op.enterAnim;
58            mOps[pos++] = op.exitAnim;
59            if (op.removed != null) {
60                final int N = op.removed.size();
61                mOps[pos++] = N;
62                for (int i=0; i<N; i++) {
63                    mOps[pos++] = op.removed.get(i).mIndex;
64                }
65            } else {
66                mOps[pos++] = 0;
67            }
68            op = op.next;
69        }
70        mTransition = bse.mTransition;
71        mTransitionStyle = bse.mTransitionStyle;
72        mName = bse.mName;
73        mIndex = bse.mIndex;
74        mBreadCrumbTitleRes = bse.mBreadCrumbTitleRes;
75        mBreadCrumbTitleText = bse.mBreadCrumbTitleText;
76        mBreadCrumbShortTitleRes = bse.mBreadCrumbShortTitleRes;
77        mBreadCrumbShortTitleText = bse.mBreadCrumbShortTitleText;
78    }
79
80    public BackStackState(Parcel in) {
81        mOps = in.createIntArray();
82        mTransition = in.readInt();
83        mTransitionStyle = in.readInt();
84        mName = in.readString();
85        mIndex = in.readInt();
86        mBreadCrumbTitleRes = in.readInt();
87        mBreadCrumbTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
88        mBreadCrumbShortTitleRes = in.readInt();
89        mBreadCrumbShortTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
90    }
91
92    public BackStackRecord instantiate(FragmentManagerImpl fm) {
93        BackStackRecord bse = new BackStackRecord(fm);
94        int pos = 0;
95        while (pos < mOps.length) {
96            BackStackRecord.Op op = new BackStackRecord.Op();
97            op.cmd = mOps[pos++];
98            if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG,
99                    "BSE " + bse + " set base fragment #" + mOps[pos]);
100            Fragment f = fm.mActive.get(mOps[pos++]);
101            op.fragment = f;
102            op.enterAnim = mOps[pos++];
103            op.exitAnim = mOps[pos++];
104            final int N = mOps[pos++];
105            if (N > 0) {
106                op.removed = new ArrayList<Fragment>(N);
107                for (int i=0; i<N; i++) {
108                    if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG,
109                            "BSE " + bse + " set remove fragment #" + mOps[pos]);
110                    Fragment r = fm.mActive.get(mOps[pos++]);
111                    op.removed.add(r);
112                }
113            }
114            bse.addOp(op);
115        }
116        bse.mTransition = mTransition;
117        bse.mTransitionStyle = mTransitionStyle;
118        bse.mName = mName;
119        bse.mIndex = mIndex;
120        bse.mAddToBackStack = true;
121        bse.mBreadCrumbTitleRes = mBreadCrumbTitleRes;
122        bse.mBreadCrumbTitleText = mBreadCrumbTitleText;
123        bse.mBreadCrumbShortTitleRes = mBreadCrumbShortTitleRes;
124        bse.mBreadCrumbShortTitleText = mBreadCrumbShortTitleText;
125        bse.bumpBackStackNesting(1);
126        return bse;
127    }
128
129    public int describeContents() {
130        return 0;
131    }
132
133    public void writeToParcel(Parcel dest, int flags) {
134        dest.writeIntArray(mOps);
135        dest.writeInt(mTransition);
136        dest.writeInt(mTransitionStyle);
137        dest.writeString(mName);
138        dest.writeInt(mIndex);
139        dest.writeInt(mBreadCrumbTitleRes);
140        TextUtils.writeToParcel(mBreadCrumbTitleText, dest, 0);
141        dest.writeInt(mBreadCrumbShortTitleRes);
142        TextUtils.writeToParcel(mBreadCrumbShortTitleText, dest, 0);
143    }
144
145    public static final Parcelable.Creator<BackStackState> CREATOR
146            = new Parcelable.Creator<BackStackState>() {
147        public BackStackState createFromParcel(Parcel in) {
148            return new BackStackState(in);
149        }
150
151        public BackStackState[] newArray(int size) {
152            return new BackStackState[size];
153        }
154    };
155}
156
157/**
158 * @hide Entry of an operation on the fragment back stack.
159 */
160final class BackStackRecord extends FragmentTransaction implements
161        FragmentManager.BackStackEntry, Runnable {
162    static final String TAG = "BackStackEntry";
163
164    final FragmentManagerImpl mManager;
165
166    static final int OP_NULL = 0;
167    static final int OP_ADD = 1;
168    static final int OP_REPLACE = 2;
169    static final int OP_REMOVE = 3;
170    static final int OP_HIDE = 4;
171    static final int OP_SHOW = 5;
172
173    static final class Op {
174        Op next;
175        Op prev;
176        int cmd;
177        Fragment fragment;
178        int enterAnim;
179        int exitAnim;
180        ArrayList<Fragment> removed;
181    }
182
183    Op mHead;
184    Op mTail;
185    int mNumOp;
186    int mEnterAnim;
187    int mExitAnim;
188    int mTransition;
189    int mTransitionStyle;
190    boolean mAddToBackStack;
191    boolean mAllowAddToBackStack = true;
192    String mName;
193    boolean mCommitted;
194    int mIndex;
195
196    int mBreadCrumbTitleRes;
197    CharSequence mBreadCrumbTitleText;
198    int mBreadCrumbShortTitleRes;
199    CharSequence mBreadCrumbShortTitleText;
200
201    public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
202        writer.print(prefix); writer.print("mName="); writer.print(mName);
203                writer.print(" mIndex="); writer.print(mIndex);
204                writer.print(" mCommitted="); writer.println(mCommitted);
205        if (mTransition != FragmentTransaction.TRANSIT_NONE) {
206            writer.print(prefix); writer.print("mTransition=#");
207                    writer.print(Integer.toHexString(mTransition));
208                    writer.print(" mTransitionStyle=#");
209                    writer.println(Integer.toHexString(mTransitionStyle));
210        }
211        if (mEnterAnim != 0 || mExitAnim !=0) {
212            writer.print(prefix); writer.print("mEnterAnim=#");
213                    writer.print(Integer.toHexString(mEnterAnim));
214                    writer.print(" mExitAnim=#");
215                    writer.println(Integer.toHexString(mExitAnim));
216        }
217        if (mBreadCrumbTitleRes != 0 || mBreadCrumbTitleText != null) {
218            writer.print(prefix); writer.print("mBreadCrumbTitleRes=#");
219                    writer.print(Integer.toHexString(mBreadCrumbTitleRes));
220                    writer.print(" mBreadCrumbTitleText=");
221                    writer.println(mBreadCrumbTitleText);
222        }
223        if (mBreadCrumbShortTitleRes != 0 || mBreadCrumbShortTitleText != null) {
224            writer.print(prefix); writer.print("mBreadCrumbShortTitleRes=#");
225                    writer.print(Integer.toHexString(mBreadCrumbShortTitleRes));
226                    writer.print(" mBreadCrumbShortTitleText=");
227                    writer.println(mBreadCrumbShortTitleText);
228        }
229
230        if (mHead != null) {
231            writer.print(prefix); writer.println("Operations:");
232            String innerPrefix = prefix + "    ";
233            Op op = mHead;
234            int num = 0;
235            while (op != null) {
236                writer.print(prefix); writer.print("  Op #"); writer.print(num);
237                        writer.println(":");
238                writer.print(innerPrefix); writer.print("cmd="); writer.print(op.cmd);
239                        writer.print(" fragment="); writer.println(op.fragment);
240                if (op.enterAnim != 0 || op.exitAnim != 0) {
241                    writer.print(prefix); writer.print("enterAnim="); writer.print(op.enterAnim);
242                            writer.print(" exitAnim="); writer.println(op.exitAnim);
243                }
244                if (op.removed != null && op.removed.size() > 0) {
245                    for (int i=0; i<op.removed.size(); i++) {
246                        writer.print(innerPrefix);
247                        if (op.removed.size() == 1) {
248                            writer.print("Removed: ");
249                        } else {
250                            writer.println("Removed:");
251                            writer.print(innerPrefix); writer.print("  #"); writer.print(num);
252                                    writer.print(": ");
253                        }
254                        writer.println(op.removed.get(i));
255                    }
256                }
257                op = op.next;
258            }
259        }
260    }
261
262    public BackStackRecord(FragmentManagerImpl manager) {
263        mManager = manager;
264    }
265
266    public int getId() {
267        return mIndex;
268    }
269
270    public int getBreadCrumbTitleRes() {
271        return mBreadCrumbTitleRes;
272    }
273
274    public int getBreadCrumbShortTitleRes() {
275        return mBreadCrumbShortTitleRes;
276    }
277
278    public CharSequence getBreadCrumbTitle() {
279        if (mBreadCrumbTitleRes != 0) {
280            return mManager.mActivity.getText(mBreadCrumbTitleRes);
281        }
282        return mBreadCrumbTitleText;
283    }
284
285    public CharSequence getBreadCrumbShortTitle() {
286        if (mBreadCrumbShortTitleRes != 0) {
287            return mManager.mActivity.getText(mBreadCrumbShortTitleRes);
288        }
289        return mBreadCrumbShortTitleText;
290    }
291
292    void addOp(Op op) {
293        if (mHead == null) {
294            mHead = mTail = op;
295        } else {
296            op.prev = mTail;
297            mTail.next = op;
298            mTail = op;
299        }
300        op.enterAnim = mEnterAnim;
301        op.exitAnim = mExitAnim;
302        mNumOp++;
303    }
304
305    public FragmentTransaction add(Fragment fragment, String tag) {
306        doAddOp(0, fragment, tag, OP_ADD);
307        return this;
308    }
309
310    public FragmentTransaction add(int containerViewId, Fragment fragment) {
311        doAddOp(containerViewId, fragment, null, OP_ADD);
312        return this;
313    }
314
315    public FragmentTransaction add(int containerViewId, Fragment fragment, String tag) {
316        doAddOp(containerViewId, fragment, tag, OP_ADD);
317        return this;
318    }
319
320    private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) {
321        if (fragment.mImmediateActivity != null) {
322            throw new IllegalStateException("Fragment already added: " + fragment);
323        }
324        fragment.mImmediateActivity = mManager.mActivity;
325        fragment.mFragmentManager = mManager;
326
327        if (tag != null) {
328            if (fragment.mTag != null && !tag.equals(fragment.mTag)) {
329                throw new IllegalStateException("Can't change tag of fragment "
330                        + fragment + ": was " + fragment.mTag
331                        + " now " + tag);
332            }
333            fragment.mTag = tag;
334        }
335
336        if (containerViewId != 0) {
337            if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) {
338                throw new IllegalStateException("Can't change container ID of fragment "
339                        + fragment + ": was " + fragment.mFragmentId
340                        + " now " + containerViewId);
341            }
342            fragment.mContainerId = fragment.mFragmentId = containerViewId;
343        }
344
345        Op op = new Op();
346        op.cmd = opcmd;
347        op.fragment = fragment;
348        addOp(op);
349    }
350
351    public FragmentTransaction replace(int containerViewId, Fragment fragment) {
352        return replace(containerViewId, fragment, null);
353    }
354
355    public FragmentTransaction replace(int containerViewId, Fragment fragment, String tag) {
356        if (containerViewId == 0) {
357            throw new IllegalArgumentException("Must use non-zero containerViewId");
358        }
359
360        doAddOp(containerViewId, fragment, tag, OP_REPLACE);
361        return this;
362    }
363
364    public FragmentTransaction remove(Fragment fragment) {
365        if (fragment.mImmediateActivity == null) {
366            throw new IllegalStateException("Fragment not added: " + fragment);
367        }
368        fragment.mImmediateActivity = null;
369
370        Op op = new Op();
371        op.cmd = OP_REMOVE;
372        op.fragment = fragment;
373        addOp(op);
374
375        return this;
376    }
377
378    public FragmentTransaction hide(Fragment fragment) {
379        if (fragment.mImmediateActivity == null) {
380            throw new IllegalStateException("Fragment not added: " + fragment);
381        }
382
383        Op op = new Op();
384        op.cmd = OP_HIDE;
385        op.fragment = fragment;
386        addOp(op);
387
388        return this;
389    }
390
391    public FragmentTransaction show(Fragment fragment) {
392        if (fragment.mImmediateActivity == null) {
393            throw new IllegalStateException("Fragment not added: " + fragment);
394        }
395
396        Op op = new Op();
397        op.cmd = OP_SHOW;
398        op.fragment = fragment;
399        addOp(op);
400
401        return this;
402    }
403
404    public FragmentTransaction setCustomAnimations(int enter, int exit) {
405        mEnterAnim = enter;
406        mExitAnim = exit;
407        return this;
408    }
409
410    public FragmentTransaction setTransition(int transition) {
411        mTransition = transition;
412        return this;
413    }
414
415    public FragmentTransaction setTransitionStyle(int styleRes) {
416        mTransitionStyle = styleRes;
417        return this;
418    }
419
420    public FragmentTransaction addToBackStack(String name) {
421        if (!mAllowAddToBackStack) {
422            throw new IllegalStateException(
423                    "This FragmentTransaction is not allowed to be added to the back stack.");
424        }
425        mAddToBackStack = true;
426        mName = name;
427        return this;
428    }
429
430    public boolean isAddToBackStackAllowed() {
431        return mAllowAddToBackStack;
432    }
433
434    public FragmentTransaction disallowAddToBackStack() {
435        if (mAddToBackStack) {
436            throw new IllegalStateException(
437                    "This transaction is already being added to the back stack");
438        }
439        mAllowAddToBackStack = false;
440        return this;
441    }
442
443    public FragmentTransaction setBreadCrumbTitle(int res) {
444        mBreadCrumbTitleRes = res;
445        mBreadCrumbTitleText = null;
446        return this;
447    }
448
449    public FragmentTransaction setBreadCrumbTitle(CharSequence text) {
450        mBreadCrumbTitleRes = 0;
451        mBreadCrumbTitleText = text;
452        return this;
453    }
454
455    public FragmentTransaction setBreadCrumbShortTitle(int res) {
456        mBreadCrumbShortTitleRes = res;
457        mBreadCrumbShortTitleText = null;
458        return this;
459    }
460
461    public FragmentTransaction setBreadCrumbShortTitle(CharSequence text) {
462        mBreadCrumbShortTitleRes = 0;
463        mBreadCrumbShortTitleText = text;
464        return this;
465    }
466
467    void bumpBackStackNesting(int amt) {
468        if (!mAddToBackStack) {
469            return;
470        }
471        if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting in " + this
472                + " by " + amt);
473        Op op = mHead;
474        while (op != null) {
475            op.fragment.mBackStackNesting += amt;
476            if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting of "
477                    + op.fragment + " to " + op.fragment.mBackStackNesting);
478            if (op.removed != null) {
479                for (int i=op.removed.size()-1; i>=0; i--) {
480                    Fragment r = op.removed.get(i);
481                    r.mBackStackNesting += amt;
482                    if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting of "
483                            + r + " to " + r.mBackStackNesting);
484                }
485            }
486            op = op.next;
487        }
488    }
489
490    public int commit() {
491        return commitInternal(false);
492    }
493
494    public int commitAllowingStateLoss() {
495        return commitInternal(true);
496    }
497
498    int commitInternal(boolean allowStateLoss) {
499        if (mCommitted) throw new IllegalStateException("commit already called");
500        if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Commit: " + this);
501        mCommitted = true;
502        if (mAddToBackStack) {
503            mIndex = mManager.allocBackStackIndex(this);
504        } else {
505            mIndex = -1;
506        }
507        mManager.enqueueAction(this, allowStateLoss);
508        return mIndex;
509    }
510
511    public void run() {
512        if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Run: " + this);
513
514        if (mAddToBackStack) {
515            if (mIndex < 0) {
516                throw new IllegalStateException("addToBackStack() called after commit()");
517            }
518        }
519
520        bumpBackStackNesting(1);
521
522        Op op = mHead;
523        while (op != null) {
524            switch (op.cmd) {
525                case OP_ADD: {
526                    Fragment f = op.fragment;
527                    f.mNextAnim = op.enterAnim;
528                    mManager.addFragment(f, false);
529                } break;
530                case OP_REPLACE: {
531                    Fragment f = op.fragment;
532                    if (mManager.mAdded != null) {
533                        for (int i=0; i<mManager.mAdded.size(); i++) {
534                            Fragment old = mManager.mAdded.get(i);
535                            if (FragmentManagerImpl.DEBUG) Log.v(TAG,
536                                    "OP_REPLACE: adding=" + f + " old=" + old);
537                            if (old.mContainerId == f.mContainerId) {
538                                if (op.removed == null) {
539                                    op.removed = new ArrayList<Fragment>();
540                                }
541                                op.removed.add(old);
542                                old.mNextAnim = op.exitAnim;
543                                if (mAddToBackStack) {
544                                    old.mBackStackNesting += 1;
545                                    if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting of "
546                                            + old + " to " + old.mBackStackNesting);
547                                }
548                                mManager.removeFragment(old, mTransition, mTransitionStyle);
549                            }
550                        }
551                    }
552                    f.mNextAnim = op.enterAnim;
553                    mManager.addFragment(f, false);
554                } break;
555                case OP_REMOVE: {
556                    Fragment f = op.fragment;
557                    f.mNextAnim = op.exitAnim;
558                    mManager.removeFragment(f, mTransition, mTransitionStyle);
559                } break;
560                case OP_HIDE: {
561                    Fragment f = op.fragment;
562                    f.mNextAnim = op.exitAnim;
563                    mManager.hideFragment(f, mTransition, mTransitionStyle);
564                } break;
565                case OP_SHOW: {
566                    Fragment f = op.fragment;
567                    f.mNextAnim = op.enterAnim;
568                    mManager.showFragment(f, mTransition, mTransitionStyle);
569                } break;
570                default: {
571                    throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
572                }
573            }
574
575            op = op.next;
576        }
577
578        mManager.moveToState(mManager.mCurState, mTransition,
579                mTransitionStyle, true);
580
581        if (mAddToBackStack) {
582            mManager.addBackStackState(this);
583        }
584    }
585
586    public void popFromBackStack(boolean doStateMove) {
587        if (FragmentManagerImpl.DEBUG) Log.v(TAG, "popFromBackStack: " + this);
588
589        bumpBackStackNesting(-1);
590
591        Op op = mTail;
592        while (op != null) {
593            switch (op.cmd) {
594                case OP_ADD: {
595                    Fragment f = op.fragment;
596                    f.mImmediateActivity = null;
597                    mManager.removeFragment(f,
598                            FragmentManagerImpl.reverseTransit(mTransition),
599                            mTransitionStyle);
600                } break;
601                case OP_REPLACE: {
602                    Fragment f = op.fragment;
603                    f.mImmediateActivity = null;
604                    mManager.removeFragment(f,
605                            FragmentManagerImpl.reverseTransit(mTransition),
606                            mTransitionStyle);
607                    if (op.removed != null) {
608                        for (int i=0; i<op.removed.size(); i++) {
609                            Fragment old = op.removed.get(i);
610                            f.mImmediateActivity = mManager.mActivity;
611                            mManager.addFragment(old, false);
612                        }
613                    }
614                } break;
615                case OP_REMOVE: {
616                    Fragment f = op.fragment;
617                    f.mImmediateActivity = mManager.mActivity;
618                    mManager.addFragment(f, false);
619                } break;
620                case OP_HIDE: {
621                    Fragment f = op.fragment;
622                    mManager.showFragment(f,
623                            FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
624                } break;
625                case OP_SHOW: {
626                    Fragment f = op.fragment;
627                    mManager.hideFragment(f,
628                            FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle);
629                } break;
630                default: {
631                    throw new IllegalArgumentException("Unknown cmd: " + op.cmd);
632                }
633            }
634
635            op = op.prev;
636        }
637
638        if (doStateMove) {
639            mManager.moveToState(mManager.mCurState,
640                    FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle, true);
641        }
642
643        if (mIndex >= 0) {
644            mManager.freeBackStackIndex(mIndex);
645            mIndex = -1;
646        }
647    }
648
649    public String getName() {
650        return mName;
651    }
652
653    public int getTransition() {
654        return mTransition;
655    }
656
657    public int getTransitionStyle() {
658        return mTransitionStyle;
659    }
660
661    public boolean isEmpty() {
662        return mNumOp == 0;
663    }
664}
665