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