FragmentManager.java revision 811ed1065f39469cf2cf6adba22cab397ed88d5e
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.animation.Animatable;
20import android.animation.PropertyAnimator;
21import android.animation.Sequencer;
22import android.content.res.TypedArray;
23import android.os.Bundle;
24import android.os.Handler;
25import android.os.Parcel;
26import android.os.Parcelable;
27import android.util.Log;
28import android.util.SparseArray;
29import android.view.Menu;
30import android.view.MenuInflater;
31import android.view.MenuItem;
32import android.view.View;
33import android.view.ViewGroup;
34import android.view.animation.AnimationUtils;
35
36import java.util.ArrayList;
37
38final class FragmentManagerState implements Parcelable {
39    FragmentState[] mActive;
40    int[] mAdded;
41    BackStackState[] mBackStack;
42
43    public FragmentManagerState() {
44    }
45
46    public FragmentManagerState(Parcel in) {
47        mActive = in.createTypedArray(FragmentState.CREATOR);
48        mAdded = in.createIntArray();
49        mBackStack = in.createTypedArray(BackStackState.CREATOR);
50    }
51
52    public int describeContents() {
53        return 0;
54    }
55
56    public void writeToParcel(Parcel dest, int flags) {
57        dest.writeTypedArray(mActive, flags);
58        dest.writeIntArray(mAdded);
59        dest.writeTypedArray(mBackStack, flags);
60    }
61
62    public static final Parcelable.Creator<FragmentManagerState> CREATOR
63            = new Parcelable.Creator<FragmentManagerState>() {
64        public FragmentManagerState createFromParcel(Parcel in) {
65            return new FragmentManagerState(in);
66        }
67
68        public FragmentManagerState[] newArray(int size) {
69            return new FragmentManagerState[size];
70        }
71    };
72}
73
74/**
75 * @hide
76 * Container for fragments associated with an activity.
77 */
78public class FragmentManager {
79    static final boolean DEBUG = true;
80    static final String TAG = "FragmentManager";
81
82    ArrayList<Runnable> mPendingActions;
83    Runnable[] mTmpActions;
84    boolean mExecutingActions;
85
86    ArrayList<Fragment> mActive;
87    ArrayList<Fragment> mAdded;
88    ArrayList<Integer> mAvailIndices;
89    ArrayList<BackStackEntry> mBackStack;
90
91    // Must be accessed while locked.
92    ArrayList<BackStackEntry> mBackStackIndices;
93    ArrayList<Integer> mAvailBackStackIndices;
94
95    int mCurState = Fragment.INITIALIZING;
96    Activity mActivity;
97
98    boolean mNeedMenuInvalidate;
99
100    // Temporary vars for state save and restore.
101    Bundle mStateBundle = null;
102    SparseArray<Parcelable> mStateArray = null;
103
104    Runnable mExecCommit = new Runnable() {
105        @Override
106        public void run() {
107            execPendingActions();
108        }
109    };
110
111    Animatable loadAnimatable(Fragment fragment, int transit, boolean enter,
112            int transitionStyle) {
113        Animatable animObj = fragment.onCreateAnimatable(transit, enter,
114                fragment.mNextAnim);
115        if (animObj != null) {
116            return animObj;
117        }
118
119        if (fragment.mNextAnim != 0) {
120            Animatable anim = AnimationUtils.loadAnimator(mActivity, fragment.mNextAnim);
121            if (anim != null) {
122                return anim;
123            }
124        }
125
126        if (transit == 0) {
127            return null;
128        }
129
130        int styleIndex = transitToStyleIndex(transit, enter);
131        if (styleIndex < 0) {
132            return null;
133        }
134
135        if (transitionStyle == 0 && mActivity.getWindow() != null) {
136            transitionStyle = mActivity.getWindow().getAttributes().windowAnimations;
137        }
138        if (transitionStyle == 0) {
139            return null;
140        }
141
142        TypedArray attrs = mActivity.obtainStyledAttributes(transitionStyle,
143                com.android.internal.R.styleable.FragmentAnimation);
144        int anim = attrs.getResourceId(styleIndex, 0);
145        attrs.recycle();
146
147        if (anim == 0) {
148            return null;
149        }
150
151        return AnimationUtils.loadAnimator(mActivity, anim);
152    }
153
154    void moveToState(Fragment f, int newState, int transit, int transitionStyle) {
155        // Fragments that are not currently added will sit in the onCreate() state.
156        if (!f.mAdded && newState > Fragment.CREATED) {
157            newState = Fragment.CREATED;
158        }
159
160        if (f.mState < newState) {
161            switch (f.mState) {
162                case Fragment.INITIALIZING:
163                    if (DEBUG) Log.v(TAG, "moveto CREATED: " + f);
164                    f.mActivity = mActivity;
165                    f.mCalled = false;
166                    f.onAttach(mActivity);
167                    if (!f.mCalled) {
168                        throw new SuperNotCalledException("Fragment " + f
169                                + " did not call through to super.onAttach()");
170                    }
171                    mActivity.onAttachFragment(f);
172
173                    if (!f.mRetaining) {
174                        f.mCalled = false;
175                        f.onCreate(f.mSavedFragmentState);
176                        if (!f.mCalled) {
177                            throw new SuperNotCalledException("Fragment " + f
178                                    + " did not call through to super.onCreate()");
179                        }
180                    }
181                    f.mRetaining = false;
182                    if (f.mFromLayout) {
183                        // For fragments that are part of the content view
184                        // layout, we need to instantiate the view immediately
185                        // and the inflater will take care of adding it.
186                        f.mView = f.onCreateView(mActivity.getLayoutInflater(),
187                                null, f.mSavedFragmentState);
188                        if (f.mView != null) {
189                            f.mView.setSaveFromParentEnabled(false);
190                            f.restoreViewState();
191                            if (f.mHidden) f.mView.setVisibility(View.GONE);
192                        }
193                    }
194                case Fragment.CREATED:
195                    if (newState > Fragment.CREATED) {
196                        if (DEBUG) Log.v(TAG, "moveto CONTENT: " + f);
197                        if (!f.mFromLayout) {
198                            ViewGroup container = null;
199                            if (f.mContainerId != 0) {
200                                container = (ViewGroup)mActivity.findViewById(f.mContainerId);
201                                if (container == null) {
202                                    throw new IllegalArgumentException("New view found for id 0x"
203                                            + Integer.toHexString(f.mContainerId)
204                                            + " for fragment " + f);
205                                }
206                            }
207                            f.mContainer = container;
208                            f.mView = f.onCreateView(mActivity.getLayoutInflater(),
209                                    container, f.mSavedFragmentState);
210                            if (f.mView != null) {
211                                f.mView.setSaveFromParentEnabled(false);
212                                if (container != null) {
213                                    Animatable anim = loadAnimatable(f, transit, true,
214                                            transitionStyle);
215                                    if (anim != null) {
216                                        if (anim instanceof Sequencer) {
217                                            ((Sequencer)anim).setTarget(f.mView);
218                                        } else if (anim instanceof PropertyAnimator) {
219                                            ((PropertyAnimator)anim).setTarget(f.mView);
220                                        }
221                                        anim.start();
222                                    }
223                                    container.addView(f.mView);
224                                    f.restoreViewState();
225                                }
226                                if (f.mHidden) f.mView.setVisibility(View.GONE);
227                            }
228                        }
229
230                        f.mCalled = false;
231                        f.onActivityCreated(f.mSavedFragmentState);
232                        if (!f.mCalled) {
233                            throw new SuperNotCalledException("Fragment " + f
234                                    + " did not call through to super.onReady()");
235                        }
236                        f.mSavedFragmentState = null;
237                    }
238                case Fragment.ACTIVITY_CREATED:
239                    if (newState > Fragment.ACTIVITY_CREATED) {
240                        if (DEBUG) Log.v(TAG, "moveto STARTED: " + f);
241                        f.mCalled = false;
242                        f.onStart();
243                        if (!f.mCalled) {
244                            throw new SuperNotCalledException("Fragment " + f
245                                    + " did not call through to super.onStart()");
246                        }
247                    }
248                case Fragment.STARTED:
249                    if (newState > Fragment.STARTED) {
250                        if (DEBUG) Log.v(TAG, "moveto RESUMED: " + f);
251                        f.mCalled = false;
252                        f.mResumed = true;
253                        f.onResume();
254                        if (!f.mCalled) {
255                            throw new SuperNotCalledException("Fragment " + f
256                                    + " did not call through to super.onResume()");
257                        }
258                    }
259            }
260        } else if (f.mState > newState) {
261            switch (f.mState) {
262                case Fragment.RESUMED:
263                    if (newState < Fragment.RESUMED) {
264                        if (DEBUG) Log.v(TAG, "movefrom RESUMED: " + f);
265                        f.mCalled = false;
266                        f.onPause();
267                        if (!f.mCalled) {
268                            throw new SuperNotCalledException("Fragment " + f
269                                    + " did not call through to super.onPause()");
270                        }
271                        f.mResumed = false;
272                    }
273                case Fragment.STARTED:
274                    if (newState < Fragment.STARTED) {
275                        if (DEBUG) Log.v(TAG, "movefrom STARTED: " + f);
276                        f.mCalled = false;
277                        f.performStop();
278                        if (!f.mCalled) {
279                            throw new SuperNotCalledException("Fragment " + f
280                                    + " did not call through to super.onStop()");
281                        }
282                    }
283                case Fragment.ACTIVITY_CREATED:
284                    if (newState < Fragment.ACTIVITY_CREATED) {
285                        if (DEBUG) Log.v(TAG, "movefrom CONTENT: " + f);
286                        if (f.mView != null) {
287                            f.mCalled = false;
288                            f.onDestroyView();
289                            if (!f.mCalled) {
290                                throw new SuperNotCalledException("Fragment " + f
291                                        + " did not call through to super.onDestroyedView()");
292                            }
293                            // Need to save the current view state if not
294                            // done already.
295                            if (!mActivity.isFinishing() && f.mSavedFragmentState == null) {
296                                saveFragmentViewState(f);
297                            }
298                            if (f.mContainer != null) {
299                                if (mCurState > Fragment.INITIALIZING) {
300                                    Animatable anim = loadAnimatable(f, transit, true,
301                                            transitionStyle);
302                                    if (anim != null) {
303                                        if (anim instanceof Sequencer) {
304                                            ((Sequencer)anim).setTarget(f.mView);
305                                        } else if (anim instanceof PropertyAnimator) {
306                                            ((PropertyAnimator)anim).setTarget(f.mView);
307                                        }
308                                        anim.start();
309                                    }
310                                }
311                                f.mContainer.removeView(f.mView);
312                            }
313                        }
314                        f.mContainer = null;
315                        f.mView = null;
316                    }
317                case Fragment.CREATED:
318                    if (newState < Fragment.CREATED) {
319                        if (DEBUG) Log.v(TAG, "movefrom CREATED: " + f);
320                        if (!f.mRetaining) {
321                            f.mCalled = false;
322                            f.onDestroy();
323                            if (!f.mCalled) {
324                                throw new SuperNotCalledException("Fragment " + f
325                                        + " did not call through to super.onDestroy()");
326                            }
327                        }
328
329                        f.mCalled = false;
330                        f.onDetach();
331                        if (!f.mCalled) {
332                            throw new SuperNotCalledException("Fragment " + f
333                                    + " did not call through to super.onDetach()");
334                        }
335                        f.mActivity = null;
336                    }
337            }
338        }
339
340        f.mState = newState;
341    }
342
343    void moveToState(int newState, boolean always) {
344        moveToState(newState, 0, 0, always);
345    }
346
347    void moveToState(int newState, int transit, int transitStyle, boolean always) {
348        if (mActivity == null && newState != Fragment.INITIALIZING) {
349            throw new IllegalStateException("No activity");
350        }
351
352        if (!always && mCurState == newState) {
353            return;
354        }
355
356        mCurState = newState;
357        if (mActive != null) {
358            for (int i=0; i<mActive.size(); i++) {
359                Fragment f = mActive.get(i);
360                if (f != null) {
361                    moveToState(f, newState, transit, transitStyle);
362                }
363            }
364        }
365    }
366
367    void makeActive(Fragment f) {
368        if (f.mIndex >= 0) {
369            return;
370        }
371
372        if (mAvailIndices == null || mAvailIndices.size() <= 0) {
373            if (mActive == null) {
374                mActive = new ArrayList<Fragment>();
375            }
376            f.setIndex(mActive.size());
377            mActive.add(f);
378
379        } else {
380            f.setIndex(mAvailIndices.remove(mAvailIndices.size()-1));
381            mActive.set(f.mIndex, f);
382        }
383    }
384
385    void makeInactive(Fragment f) {
386        if (f.mIndex < 0) {
387            return;
388        }
389
390        mActive.set(f.mIndex, null);
391        if (mAvailIndices == null) {
392            mAvailIndices = new ArrayList<Integer>();
393        }
394        mAvailIndices.add(f.mIndex);
395        mActivity.invalidateFragmentIndex(f.mIndex);
396        f.clearIndex();
397    }
398
399    public void addFragment(Fragment fragment, boolean moveToStateNow) {
400        if (mAdded == null) {
401            mAdded = new ArrayList<Fragment>();
402        }
403        mAdded.add(fragment);
404        makeActive(fragment);
405        if (DEBUG) Log.v(TAG, "add: " + fragment);
406        fragment.mAdded = true;
407        if (fragment.mHasMenu) {
408            mNeedMenuInvalidate = true;
409        }
410        if (moveToStateNow) {
411            moveToState(fragment, mCurState, 0, 0);
412        }
413    }
414
415    public void removeFragment(Fragment fragment, int transition, int transitionStyle) {
416        if (DEBUG) Log.v(TAG, "remove: " + fragment + " nesting=" + fragment.mBackStackNesting);
417        mAdded.remove(fragment);
418        final boolean inactive = fragment.mBackStackNesting <= 0;
419        if (fragment.mHasMenu) {
420            mNeedMenuInvalidate = true;
421        }
422        fragment.mAdded = false;
423        moveToState(fragment, inactive ? Fragment.INITIALIZING : Fragment.CREATED,
424                transition, transitionStyle);
425        if (inactive) {
426            makeInactive(fragment);
427        }
428    }
429
430    public void hideFragment(Fragment fragment, int transition, int transitionStyle) {
431        if (DEBUG) Log.v(TAG, "hide: " + fragment);
432        if (!fragment.mHidden) {
433            fragment.mHidden = true;
434            if (fragment.mView != null) {
435                Animatable anim = loadAnimatable(fragment, transition, true,
436                        transitionStyle);
437                if (anim != null) {
438                    if (anim instanceof Sequencer) {
439                        ((Sequencer)anim).setTarget(fragment.mView);
440                    } else if (anim instanceof PropertyAnimator) {
441                        ((PropertyAnimator)anim).setTarget(fragment.mView);
442                    }
443                    anim.start();
444                }
445                fragment.mView.setVisibility(View.GONE);
446            }
447            if (fragment.mAdded && fragment.mHasMenu) {
448                mNeedMenuInvalidate = true;
449            }
450            fragment.onHiddenChanged(true);
451        }
452    }
453
454    public void showFragment(Fragment fragment, int transition, int transitionStyle) {
455        if (DEBUG) Log.v(TAG, "show: " + fragment);
456        if (fragment.mHidden) {
457            fragment.mHidden = false;
458            if (fragment.mView != null) {
459                Animatable anim = loadAnimatable(fragment, transition, true,
460                        transitionStyle);
461                if (anim != null) {
462                    if (anim instanceof Sequencer) {
463                        ((Sequencer)anim).setTarget(fragment.mView);
464                    } else if (anim instanceof PropertyAnimator) {
465                        ((PropertyAnimator)anim).setTarget(fragment.mView);
466                    }
467                    anim.start();
468                }
469                fragment.mView.setVisibility(View.VISIBLE);
470            }
471            if (fragment.mAdded && fragment.mHasMenu) {
472                mNeedMenuInvalidate = true;
473            }
474            fragment.onHiddenChanged(false);
475        }
476    }
477
478    public Fragment findFragmentById(int id) {
479        if (mActive != null) {
480            // First look through added fragments.
481            for (int i=mAdded.size()-1; i>=0; i--) {
482                Fragment f = mAdded.get(i);
483                if (f != null && f.mFragmentId == id) {
484                    return f;
485                }
486            }
487            // Now for any known fragment.
488            for (int i=mActive.size()-1; i>=0; i--) {
489                Fragment f = mActive.get(i);
490                if (f != null && f.mFragmentId == id) {
491                    return f;
492                }
493            }
494        }
495        return null;
496    }
497
498    public Fragment findFragmentByTag(String tag) {
499        if (mActive != null && tag != null) {
500            // First look through added fragments.
501            for (int i=mAdded.size()-1; i>=0; i--) {
502                Fragment f = mAdded.get(i);
503                if (f != null && tag.equals(f.mTag)) {
504                    return f;
505                }
506            }
507            // Now for any known fragment.
508            for (int i=mActive.size()-1; i>=0; i--) {
509                Fragment f = mActive.get(i);
510                if (f != null && tag.equals(f.mTag)) {
511                    return f;
512                }
513            }
514        }
515        return null;
516    }
517
518    public Fragment findFragmentByWho(String who) {
519        if (mActive != null && who != null) {
520            for (int i=mActive.size()-1; i>=0; i--) {
521                Fragment f = mActive.get(i);
522                if (f != null && who.equals(f.mWho)) {
523                    return f;
524                }
525            }
526        }
527        return null;
528    }
529
530    public void enqueueAction(Runnable action) {
531        synchronized (this) {
532            if (mPendingActions == null) {
533                mPendingActions = new ArrayList<Runnable>();
534            }
535            mPendingActions.add(action);
536            if (mPendingActions.size() == 1) {
537                mActivity.mHandler.removeCallbacks(mExecCommit);
538                mActivity.mHandler.post(mExecCommit);
539            }
540        }
541    }
542
543    public int allocBackStackIndex(BackStackEntry bse) {
544        synchronized (this) {
545            if (mAvailBackStackIndices == null || mAvailBackStackIndices.size() <= 0) {
546                if (mBackStackIndices == null) {
547                    mBackStackIndices = new ArrayList<BackStackEntry>();
548                }
549                int index = mBackStackIndices.size();
550                if (DEBUG) Log.v(TAG, "Setting back stack index " + index + " to " + bse);
551                mBackStackIndices.add(bse);
552                return index;
553
554            } else {
555                int index = mAvailBackStackIndices.remove(mAvailBackStackIndices.size()-1);
556                if (DEBUG) Log.v(TAG, "Adding back stack index " + index + " with " + bse);
557                mBackStackIndices.set(index, bse);
558                return index;
559            }
560        }
561    }
562
563    public void setBackStackIndex(int index, BackStackEntry bse) {
564        synchronized (this) {
565            if (mBackStackIndices == null) {
566                mBackStackIndices = new ArrayList<BackStackEntry>();
567            }
568            int N = mBackStackIndices.size();
569            if (index < N) {
570                if (DEBUG) Log.v(TAG, "Setting back stack index " + index + " to " + bse);
571                mBackStackIndices.set(index, bse);
572            } else {
573                while (N < index) {
574                    mBackStackIndices.add(null);
575                    if (mAvailBackStackIndices == null) {
576                        mAvailBackStackIndices = new ArrayList<Integer>();
577                    }
578                    if (DEBUG) Log.v(TAG, "Adding available back stack index " + N);
579                    mAvailBackStackIndices.add(N);
580                    N++;
581                }
582                if (DEBUG) Log.v(TAG, "Adding back stack index " + index + " with " + bse);
583                mBackStackIndices.add(bse);
584            }
585        }
586    }
587
588    public void freeBackStackIndex(int index) {
589        synchronized (this) {
590            mBackStackIndices.set(index, null);
591            if (mAvailBackStackIndices == null) {
592                mAvailBackStackIndices = new ArrayList<Integer>();
593            }
594            if (DEBUG) Log.v(TAG, "Freeing back stack index " + index);
595            mAvailBackStackIndices.add(index);
596        }
597    }
598
599    /**
600     * Only call from main thread!
601     */
602    public void execPendingActions() {
603        if (mExecutingActions) {
604            throw new IllegalStateException("Recursive entry to execPendingActions");
605        }
606
607        while (true) {
608            int numActions;
609
610            synchronized (this) {
611                if (mPendingActions == null || mPendingActions.size() == 0) {
612                    return;
613                }
614
615                numActions = mPendingActions.size();
616                if (mTmpActions == null || mTmpActions.length < numActions) {
617                    mTmpActions = new Runnable[numActions];
618                }
619                mPendingActions.toArray(mTmpActions);
620                mPendingActions.clear();
621                mActivity.mHandler.removeCallbacks(mExecCommit);
622            }
623
624            mExecutingActions = true;
625            for (int i=0; i<numActions; i++) {
626                mTmpActions[i].run();
627            }
628            mExecutingActions = false;
629        }
630    }
631
632    public void addBackStackState(BackStackEntry state) {
633        if (mBackStack == null) {
634            mBackStack = new ArrayList<BackStackEntry>();
635        }
636        mBackStack.add(state);
637    }
638
639    public boolean popBackStackState(Handler handler, String name, int flags) {
640        return popBackStackState(handler, name, -1, flags);
641    }
642
643    public boolean popBackStackState(Handler handler, int id, int flags) {
644        if (id < 0) {
645            return false;
646        }
647        return popBackStackState(handler, null, id, flags);
648    }
649
650    boolean popBackStackState(Handler handler, String name, int id, int flags) {
651        if (mBackStack == null) {
652            return false;
653        }
654        if (name == null && id < 0 && (flags&Activity.POP_BACK_STACK_INCLUSIVE) == 0) {
655            int last = mBackStack.size()-1;
656            if (last < 0) {
657                return false;
658            }
659            final BackStackEntry bss = mBackStack.remove(last);
660            enqueueAction(new Runnable() {
661                public void run() {
662                    if (DEBUG) Log.v(TAG, "Popping back stack state: " + bss);
663                    bss.popFromBackStack();
664                    moveToState(mCurState, reverseTransit(bss.getTransition()),
665                            bss.getTransitionStyle(), true);
666                }
667            });
668        } else {
669            int index = -1;
670            if (name != null || id >= 0) {
671                // If a name or ID is specified, look for that place in
672                // the stack.
673                index = mBackStack.size()-1;
674                while (index >= 0) {
675                    BackStackEntry bss = mBackStack.get(index);
676                    if (name != null && name.equals(bss.getName())) {
677                        break;
678                    }
679                    if (id >= 0 && id == bss.mIndex) {
680                        break;
681                    }
682                    index--;
683                }
684                if (index < 0) {
685                    return false;
686                }
687                if ((flags&Activity.POP_BACK_STACK_INCLUSIVE) != 0) {
688                    index--;
689                    // Consume all following entries that match.
690                    while (index >= 0) {
691                        BackStackEntry bss = mBackStack.get(index);
692                        if ((name != null && name.equals(bss.getName()))
693                                || (id >= 0 && id == bss.mIndex)) {
694                            index--;
695                            continue;
696                        }
697                        break;
698                    }
699                }
700            }
701            if (index == mBackStack.size()-1) {
702                return false;
703            }
704            final ArrayList<BackStackEntry> states
705                    = new ArrayList<BackStackEntry>();
706            for (int i=mBackStack.size()-1; i>index; i--) {
707                states.add(mBackStack.remove(i));
708            }
709            enqueueAction(new Runnable() {
710                public void run() {
711                    for (int i=0; i<states.size(); i++) {
712                        if (DEBUG) Log.v(TAG, "Popping back stack state: " + states.get(i));
713                        states.get(i).popFromBackStack();
714                    }
715                    moveToState(mCurState, true);
716                }
717            });
718        }
719        return true;
720    }
721
722    ArrayList<Fragment> retainNonConfig() {
723        ArrayList<Fragment> fragments = null;
724        if (mActive != null) {
725            for (int i=0; i<mActive.size(); i++) {
726                Fragment f = mActive.get(i);
727                if (f != null && f.mRetainInstance) {
728                    if (fragments == null) {
729                        fragments = new ArrayList<Fragment>();
730                    }
731                    fragments.add(f);
732                    f.mRetaining = true;
733                }
734            }
735        }
736        return fragments;
737    }
738
739    void saveFragmentViewState(Fragment f) {
740        if (f.mView == null) {
741            return;
742        }
743        if (mStateArray == null) {
744            mStateArray = new SparseArray<Parcelable>();
745        }
746        f.mView.saveHierarchyState(mStateArray);
747        if (mStateArray.size() > 0) {
748            f.mSavedViewState = mStateArray;
749            mStateArray = null;
750        }
751    }
752
753    Parcelable saveAllState() {
754        if (mActive == null || mActive.size() <= 0) {
755            return null;
756        }
757
758        // First collect all active fragments.
759        int N = mActive.size();
760        FragmentState[] active = new FragmentState[N];
761        boolean haveFragments = false;
762        for (int i=0; i<N; i++) {
763            Fragment f = mActive.get(i);
764            if (f != null) {
765                haveFragments = true;
766
767                FragmentState fs = new FragmentState(f);
768                active[i] = fs;
769
770                if (mStateBundle == null) {
771                    mStateBundle = new Bundle();
772                }
773                f.onSaveInstanceState(mStateBundle);
774                if (!mStateBundle.isEmpty()) {
775                    fs.mSavedFragmentState = mStateBundle;
776                    mStateBundle = null;
777                }
778
779                if (f.mView != null) {
780                    saveFragmentViewState(f);
781                    if (f.mSavedViewState != null) {
782                        if (fs.mSavedFragmentState == null) {
783                            fs.mSavedFragmentState = new Bundle();
784                        }
785                        fs.mSavedFragmentState.putSparseParcelableArray(
786                                FragmentState.VIEW_STATE_TAG, f.mSavedViewState);
787                    }
788                }
789
790            }
791        }
792
793        if (!haveFragments) {
794            return null;
795        }
796
797        int[] added = null;
798        BackStackState[] backStack = null;
799
800        // Build list of currently added fragments.
801        N = mAdded.size();
802        if (N > 0) {
803            added = new int[N];
804            for (int i=0; i<N; i++) {
805                added[i] = mAdded.get(i).mIndex;
806            }
807        }
808
809        // Now save back stack.
810        if (mBackStack != null) {
811            N = mBackStack.size();
812            if (N > 0) {
813                backStack = new BackStackState[N];
814                for (int i=0; i<N; i++) {
815                    backStack[i] = new BackStackState(this, mBackStack.get(i));
816                }
817            }
818        }
819
820        FragmentManagerState fms = new FragmentManagerState();
821        fms.mActive = active;
822        fms.mAdded = added;
823        fms.mBackStack = backStack;
824        return fms;
825    }
826
827    void restoreAllState(Parcelable state, ArrayList<Fragment> nonConfig) {
828        // If there is no saved state at all, then there can not be
829        // any nonConfig fragments either, so that is that.
830        if (state == null) return;
831        FragmentManagerState fms = (FragmentManagerState)state;
832        if (fms.mActive == null) return;
833
834        // First re-attach any non-config instances we are retaining back
835        // to their saved state, so we don't try to instantiate them again.
836        if (nonConfig != null) {
837            for (int i=0; i<nonConfig.size(); i++) {
838                Fragment f = nonConfig.get(i);
839                FragmentState fs = fms.mActive[f.mIndex];
840                fs.mInstance = f;
841                f.mSavedViewState = null;
842                f.mBackStackNesting = 0;
843                f.mAdded = false;
844                if (fs.mSavedFragmentState != null) {
845                    f.mSavedViewState = fs.mSavedFragmentState.getSparseParcelableArray(
846                            FragmentState.VIEW_STATE_TAG);
847                }
848            }
849        }
850
851        // Build the full list of active fragments, instantiating them from
852        // their saved state.
853        mActive = new ArrayList<Fragment>(fms.mActive.length);
854        if (mAvailIndices != null) {
855            mAvailIndices.clear();
856        }
857        for (int i=0; i<fms.mActive.length; i++) {
858            FragmentState fs = fms.mActive[i];
859            if (fs != null) {
860                mActive.add(fs.instantiate(mActivity));
861            } else {
862                mActive.add(null);
863                if (mAvailIndices == null) {
864                    mAvailIndices = new ArrayList<Integer>();
865                }
866                mAvailIndices.add(i);
867            }
868        }
869
870        // Build the list of currently added fragments.
871        if (fms.mAdded != null) {
872            mAdded = new ArrayList<Fragment>(fms.mAdded.length);
873            for (int i=0; i<fms.mAdded.length; i++) {
874                Fragment f = mActive.get(fms.mAdded[i]);
875                if (f == null) {
876                    throw new IllegalStateException(
877                            "No instantiated fragment for index #" + fms.mAdded[i]);
878                }
879                f.mAdded = true;
880                f.mImmediateActivity = mActivity;
881                mAdded.add(f);
882            }
883        } else {
884            mAdded = null;
885        }
886
887        // Build the back stack.
888        if (fms.mBackStack != null) {
889            mBackStack = new ArrayList<BackStackEntry>(fms.mBackStack.length);
890            for (int i=0; i<fms.mBackStack.length; i++) {
891                BackStackEntry bse = fms.mBackStack[i].instantiate(this);
892                mBackStack.add(bse);
893                if (bse.mIndex >= 0) {
894                    setBackStackIndex(bse.mIndex, bse);
895                }
896            }
897        } else {
898            mBackStack = null;
899        }
900    }
901
902    public void attachActivity(Activity activity) {
903        if (mActivity != null) throw new IllegalStateException();
904        mActivity = activity;
905    }
906
907    public void dispatchCreate() {
908        moveToState(Fragment.CREATED, false);
909    }
910
911    public void dispatchActivityCreated() {
912        moveToState(Fragment.ACTIVITY_CREATED, false);
913    }
914
915    public void dispatchStart() {
916        moveToState(Fragment.STARTED, false);
917    }
918
919    public void dispatchResume() {
920        moveToState(Fragment.RESUMED, false);
921    }
922
923    public void dispatchPause() {
924        moveToState(Fragment.STARTED, false);
925    }
926
927    public void dispatchStop() {
928        moveToState(Fragment.ACTIVITY_CREATED, false);
929    }
930
931    public void dispatchDestroy() {
932        moveToState(Fragment.INITIALIZING, false);
933        mActivity = null;
934    }
935
936    public boolean dispatchCreateOptionsMenu(Menu menu, MenuInflater inflater) {
937        boolean show = false;
938        if (mActive != null) {
939            for (int i=0; i<mAdded.size(); i++) {
940                Fragment f = mAdded.get(i);
941                if (f != null && !f.mHidden && f.mHasMenu) {
942                    show = true;
943                    f.onCreateOptionsMenu(menu, inflater);
944                }
945            }
946        }
947        return show;
948    }
949
950    public boolean dispatchPrepareOptionsMenu(Menu menu) {
951        boolean show = false;
952        if (mActive != null) {
953            for (int i=0; i<mAdded.size(); i++) {
954                Fragment f = mAdded.get(i);
955                if (f != null && !f.mHidden && f.mHasMenu) {
956                    show = true;
957                    f.onPrepareOptionsMenu(menu);
958                }
959            }
960        }
961        return show;
962    }
963
964    public boolean dispatchOptionsItemSelected(MenuItem item) {
965        if (mActive != null) {
966            for (int i=0; i<mAdded.size(); i++) {
967                Fragment f = mAdded.get(i);
968                if (f != null && !f.mHidden && f.mHasMenu) {
969                    if (f.onOptionsItemSelected(item)) {
970                        return true;
971                    }
972                }
973            }
974        }
975        return false;
976    }
977
978    public boolean dispatchContextItemSelected(MenuItem item) {
979        if (mActive != null) {
980            for (int i=0; i<mAdded.size(); i++) {
981                Fragment f = mAdded.get(i);
982                if (f != null && !f.mHidden) {
983                    if (f.onContextItemSelected(item)) {
984                        return true;
985                    }
986                }
987            }
988        }
989        return false;
990    }
991
992    public void dispatchOptionsMenuClosed(Menu menu) {
993        if (mActive != null) {
994            for (int i=0; i<mAdded.size(); i++) {
995                Fragment f = mAdded.get(i);
996                if (f != null && !f.mHidden && f.mHasMenu) {
997                    f.onOptionsMenuClosed(menu);
998                }
999            }
1000        }
1001    }
1002
1003    public static int reverseTransit(int transit) {
1004        int rev = 0;
1005        switch (transit) {
1006            case FragmentTransaction.TRANSIT_FRAGMENT_OPEN:
1007                rev = FragmentTransaction.TRANSIT_FRAGMENT_CLOSE;
1008                break;
1009            case FragmentTransaction.TRANSIT_FRAGMENT_CLOSE:
1010                rev = FragmentTransaction.TRANSIT_FRAGMENT_OPEN;
1011                break;
1012        }
1013        return rev;
1014
1015    }
1016
1017    public static int transitToStyleIndex(int transit, boolean enter) {
1018        int animAttr = -1;
1019        switch (transit) {
1020            case FragmentTransaction.TRANSIT_FRAGMENT_OPEN:
1021                animAttr = enter
1022                    ? com.android.internal.R.styleable.FragmentAnimation_fragmentOpenEnterAnimation
1023                    : com.android.internal.R.styleable.FragmentAnimation_fragmentOpenExitAnimation;
1024                break;
1025            case FragmentTransaction.TRANSIT_FRAGMENT_CLOSE:
1026                animAttr = enter
1027                    ? com.android.internal.R.styleable.FragmentAnimation_fragmentCloseEnterAnimation
1028                    : com.android.internal.R.styleable.FragmentAnimation_fragmentCloseExitAnimation;
1029                break;
1030        }
1031        return animAttr;
1032    }
1033}
1034