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