FragmentActivity.java revision 8b8a369d50ff43ad2a8836e66283c2bca9c2711e
1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.support.v4.app;
18
19import android.app.Activity;
20import android.content.Context;
21import android.content.Intent;
22import android.content.res.Configuration;
23import android.content.res.Resources;
24import android.content.res.TypedArray;
25import android.os.Bundle;
26import android.os.Handler;
27import android.os.Message;
28import android.os.Parcelable;
29import android.support.annotation.NonNull;
30import android.support.v4.util.SimpleArrayMap;
31import android.util.AttributeSet;
32import android.util.Log;
33import android.view.KeyEvent;
34import android.view.Menu;
35import android.view.MenuItem;
36import android.view.View;
37import android.view.ViewGroup;
38import android.view.Window;
39
40import java.io.FileDescriptor;
41import java.io.PrintWriter;
42import java.util.ArrayList;
43
44/**
45 * Base class for activities that want to use the support-based
46 * {@link android.support.v4.app.Fragment} and
47 * {@link android.support.v4.content.Loader} APIs.
48 *
49 * <p>When using this class as opposed to new platform's built-in fragment
50 * and loader support, you must use the {@link #getSupportFragmentManager()}
51 * and {@link #getSupportLoaderManager()} methods respectively to access
52 * those features.
53 *
54 * <p class="note"><strong>Note:</strong> If you want to implement an activity that includes
55 * an <a href="{@docRoot}guide/topics/ui/actionbar.html">action bar</a>, you should instead use
56 * the {@link android.support.v7.app.ActionBarActivity} class, which is a subclass of this one,
57 * so allows you to use {@link android.support.v4.app.Fragment} APIs on API level 7 and higher.</p>
58 *
59 * <p>Known limitations:</p>
60 * <ul>
61 * <li> <p>When using the <code>&lt;fragment></code> tag, this implementation can not
62 * use the parent view's ID as the new fragment's ID.  You must explicitly
63 * specify an ID (or tag) in the <code>&lt;fragment></code>.</p>
64 * <li> <p>Prior to Honeycomb (3.0), an activity's state was saved before pausing.
65 * Fragments are a significant amount of new state, and dynamic enough that one
66 * often wants them to change between pausing and stopping.  These classes
67 * throw an exception if you try to change the fragment state after it has been
68 * saved, to avoid accidental loss of UI state.  However this is too restrictive
69 * prior to Honeycomb, where the state is saved before pausing.  To address this,
70 * when running on platforms prior to Honeycomb an exception will not be thrown
71 * if you change fragments between the state save and the activity being stopped.
72 * This means that in some cases if the activity is restored from its last saved
73 * state, this may be a snapshot slightly before what the user last saw.</p>
74 * </ul>
75 */
76public class FragmentActivity extends Activity {
77    private static final String TAG = "FragmentActivity";
78
79    static final String FRAGMENTS_TAG = "android:support:fragments";
80
81    // This is the SDK API version of Honeycomb (3.0).
82    private static final int HONEYCOMB = 11;
83
84    static final int MSG_REALLY_STOPPED = 1;
85    static final int MSG_RESUME_PENDING = 2;
86
87    final Handler mHandler = new Handler() {
88        @Override
89        public void handleMessage(Message msg) {
90            switch (msg.what) {
91                case MSG_REALLY_STOPPED:
92                    if (mStopped) {
93                        doReallyStop(false);
94                    }
95                    break;
96                case MSG_RESUME_PENDING:
97                    onResumeFragments();
98                    mFragments.execPendingActions();
99                    break;
100                default:
101                    super.handleMessage(msg);
102            }
103        }
104
105    };
106    final FragmentManagerImpl mFragments = new FragmentManagerImpl();
107    final FragmentContainer mContainer = new FragmentContainer() {
108        @Override
109        public View findViewById(int id) {
110            return FragmentActivity.this.findViewById(id);
111        }
112    };
113
114    boolean mCreated;
115    boolean mResumed;
116    boolean mStopped;
117    boolean mReallyStopped;
118    boolean mRetaining;
119
120    boolean mOptionsMenuInvalidated;
121
122    boolean mCheckedForLoaderManager;
123    boolean mLoadersStarted;
124    SimpleArrayMap<String, LoaderManagerImpl> mAllLoaderManagers;
125    LoaderManagerImpl mLoaderManager;
126
127    static final class NonConfigurationInstances {
128        Object activity;
129        Object custom;
130        SimpleArrayMap<String, Object> children;
131        ArrayList<Fragment> fragments;
132        SimpleArrayMap<String, LoaderManagerImpl> loaders;
133    }
134
135    static class FragmentTag {
136        public static final int[] Fragment = {
137            0x01010003, 0x010100d0, 0x010100d1
138        };
139        public static final int Fragment_id = 1;
140        public static final int Fragment_name = 0;
141        public static final int Fragment_tag = 2;
142    }
143
144    // ------------------------------------------------------------------------
145    // HOOKS INTO ACTIVITY
146    // ------------------------------------------------------------------------
147
148    /**
149     * Dispatch incoming result to the correct fragment.
150     */
151    @Override
152    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
153        mFragments.noteStateNotSaved();
154        int index = requestCode>>16;
155        if (index != 0) {
156            index--;
157            if (mFragments.mActive == null || index < 0 || index >= mFragments.mActive.size()) {
158                Log.w(TAG, "Activity result fragment index out of range: 0x"
159                        + Integer.toHexString(requestCode));
160                return;
161            }
162            Fragment frag = mFragments.mActive.get(index);
163            if (frag == null) {
164                Log.w(TAG, "Activity result no fragment exists for index: 0x"
165                        + Integer.toHexString(requestCode));
166            } else {
167                frag.onActivityResult(requestCode&0xffff, resultCode, data);
168            }
169            return;
170        }
171
172        super.onActivityResult(requestCode, resultCode, data);
173    }
174
175    /**
176     * Take care of popping the fragment back stack or finishing the activity
177     * as appropriate.
178     */
179    public void onBackPressed() {
180        if (!mFragments.popBackStackImmediate()) {
181            supportFinishAfterTransition();
182        }
183    }
184
185    /**
186     * Reverses the Activity Scene entry Transition and triggers the calling Activity
187     * to reverse its exit Transition. When the exit Transition completes,
188     * {@link #finish()} is called. If no entry Transition was used, finish() is called
189     * immediately and the Activity exit Transition is run.
190     *
191     * <p>On Android 4.4 or lower, this method only finishes the Activity with no
192     * special exit transition.</p>
193     */
194    public void supportFinishAfterTransition() {
195        ActivityCompat.finishAfterTransition(this);
196    }
197
198    /**
199     * When {@link android.app.ActivityOptions#makeSceneTransitionAnimation(Activity,
200     * android.view.View, String)} was used to start an Activity, <var>listener</var>
201     * will be called to handle shared elements on the <i>launched</i> Activity. This requires
202     * {@link Window#FEATURE_CONTENT_TRANSITIONS}.
203     *
204     * @param listener Used to manipulate shared element transitions on the launched Activity.
205     */
206    public void setEnterSharedElementListener(SharedElementListener listener) {
207        ActivityCompat.setEnterSharedElementListener(this, listener);
208    }
209
210    /**
211     * When {@link android.app.ActivityOptions#makeSceneTransitionAnimation(Activity,
212     * android.view.View, String)} was used to start an Activity, <var>listener</var>
213     * will be called to handle shared elements on the <i>launching</i> Activity. Most
214     * calls will only come when returning from the started Activity.
215     * This requires {@link Window#FEATURE_CONTENT_TRANSITIONS}.
216     *
217     * @param listener Used to manipulate shared element transitions on the launching Activity.
218     */
219    public void setExitSharedElementListener(SharedElementListener listener) {
220        ActivityCompat.setExitSharedElementListener(this, listener);
221    }
222
223    /**
224     * Support library version of {@link android.app.Activity#postponeEnterTransition()} that works
225     * only on API 21 and later.
226     */
227    public void supportPostponeEnterTransition() {
228        ActivityCompat.postponeEnterTransition(this);
229    }
230
231    /**
232     * Support library version of {@link android.app.Activity#startPostponedEnterTransition()}
233     * that only works with API 21 and later.
234     */
235    public void supportStartPostponedEnterTransition() {
236        ActivityCompat.startPostponedEnterTransition(this);
237    }
238
239    /**
240     * Dispatch configuration change to all fragments.
241     */
242    @Override
243    public void onConfigurationChanged(Configuration newConfig) {
244        super.onConfigurationChanged(newConfig);
245        mFragments.dispatchConfigurationChanged(newConfig);
246    }
247
248    /**
249     * Perform initialization of all fragments and loaders.
250     */
251    @Override
252    protected void onCreate(Bundle savedInstanceState) {
253        mFragments.attachActivity(this, mContainer, null);
254        // Old versions of the platform didn't do this!
255        if (getLayoutInflater().getFactory() == null) {
256            getLayoutInflater().setFactory(this);
257        }
258
259        super.onCreate(savedInstanceState);
260
261        NonConfigurationInstances nc = (NonConfigurationInstances)
262                getLastNonConfigurationInstance();
263        if (nc != null) {
264            mAllLoaderManagers = nc.loaders;
265        }
266        if (savedInstanceState != null) {
267            Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
268            mFragments.restoreAllState(p, nc != null ? nc.fragments : null);
269        }
270        mFragments.dispatchCreate();
271    }
272
273    /**
274     * Dispatch to Fragment.onCreateOptionsMenu().
275     */
276    @Override
277    public boolean onCreatePanelMenu(int featureId, Menu menu) {
278        if (featureId == Window.FEATURE_OPTIONS_PANEL) {
279            boolean show = super.onCreatePanelMenu(featureId, menu);
280            show |= mFragments.dispatchCreateOptionsMenu(menu, getMenuInflater());
281            if (android.os.Build.VERSION.SDK_INT >= HONEYCOMB) {
282                return show;
283            }
284            // Prior to Honeycomb, the framework can't invalidate the options
285            // menu, so we must always say we have one in case the app later
286            // invalidates it and needs to have it shown.
287            return true;
288        }
289        return super.onCreatePanelMenu(featureId, menu);
290    }
291
292    /**
293     * Add support for inflating the &lt;fragment> tag.
294     */
295    @Override
296    public View onCreateView(String name, @NonNull Context context, @NonNull AttributeSet attrs) {
297        if (!"fragment".equals(name)) {
298            return super.onCreateView(name, context, attrs);
299        }
300
301        String fname = attrs.getAttributeValue(null, "class");
302        TypedArray a =  context.obtainStyledAttributes(attrs, FragmentTag.Fragment);
303        if (fname == null) {
304            fname = a.getString(FragmentTag.Fragment_name);
305        }
306        int id = a.getResourceId(FragmentTag.Fragment_id, View.NO_ID);
307        String tag = a.getString(FragmentTag.Fragment_tag);
308        a.recycle();
309
310        if (!Fragment.isSupportFragmentClass(this, fname)) {
311            // Invalid support lib fragment; let the device's framework handle it.
312            // This will allow android.app.Fragments to do the right thing.
313            return super.onCreateView(name, context, attrs);
314        }
315
316        View parent = null; // NOTE: no way to get parent pre-Honeycomb.
317        int containerId = parent != null ? parent.getId() : 0;
318        if (containerId == View.NO_ID && id == View.NO_ID && tag == null) {
319            throw new IllegalArgumentException(attrs.getPositionDescription()
320                    + ": Must specify unique android:id, android:tag, or have a parent with an id for " + fname);
321        }
322
323        // If we restored from a previous state, we may already have
324        // instantiated this fragment from the state and should use
325        // that instance instead of making a new one.
326        Fragment fragment = id != View.NO_ID ? mFragments.findFragmentById(id) : null;
327        if (fragment == null && tag != null) {
328            fragment = mFragments.findFragmentByTag(tag);
329        }
330        if (fragment == null && containerId != View.NO_ID) {
331            fragment = mFragments.findFragmentById(containerId);
332        }
333
334        if (FragmentManagerImpl.DEBUG) Log.v(TAG, "onCreateView: id=0x"
335                + Integer.toHexString(id) + " fname=" + fname
336                + " existing=" + fragment);
337        if (fragment == null) {
338            fragment = Fragment.instantiate(this, fname);
339            fragment.mFromLayout = true;
340            fragment.mFragmentId = id != 0 ? id : containerId;
341            fragment.mContainerId = containerId;
342            fragment.mTag = tag;
343            fragment.mInLayout = true;
344            fragment.mFragmentManager = mFragments;
345            fragment.onInflate(this, attrs, fragment.mSavedFragmentState);
346            mFragments.addFragment(fragment, true);
347
348        } else if (fragment.mInLayout) {
349            // A fragment already exists and it is not one we restored from
350            // previous state.
351            throw new IllegalArgumentException(attrs.getPositionDescription()
352                    + ": Duplicate id 0x" + Integer.toHexString(id)
353                    + ", tag " + tag + ", or parent id 0x" + Integer.toHexString(containerId)
354                    + " with another fragment for " + fname);
355        } else {
356            // This fragment was retained from a previous instance; get it
357            // going now.
358            fragment.mInLayout = true;
359            // If this fragment is newly instantiated (either right now, or
360            // from last saved state), then give it the attributes to
361            // initialize itself.
362            if (!fragment.mRetaining) {
363                fragment.onInflate(this, attrs, fragment.mSavedFragmentState);
364            }
365            mFragments.moveToState(fragment);
366        }
367
368        if (fragment.mView == null) {
369            throw new IllegalStateException("Fragment " + fname
370                    + " did not create a view.");
371        }
372        if (id != 0) {
373            fragment.mView.setId(id);
374        }
375        if (fragment.mView.getTag() == null) {
376            fragment.mView.setTag(tag);
377        }
378        return fragment.mView;
379    }
380
381    /**
382     * Destroy all fragments and loaders.
383     */
384    @Override
385    protected void onDestroy() {
386        super.onDestroy();
387
388        doReallyStop(false);
389
390        mFragments.dispatchDestroy();
391        if (mLoaderManager != null) {
392            mLoaderManager.doDestroy();
393        }
394    }
395
396    /**
397     * Take care of calling onBackPressed() for pre-Eclair platforms.
398     */
399    @Override
400    public boolean onKeyDown(int keyCode, KeyEvent event) {
401        if (android.os.Build.VERSION.SDK_INT < 5 /* ECLAIR */
402                && keyCode == KeyEvent.KEYCODE_BACK
403                && event.getRepeatCount() == 0) {
404            // Take care of calling this method on earlier versions of
405            // the platform where it doesn't exist.
406            onBackPressed();
407            return true;
408        }
409
410        return super.onKeyDown(keyCode, event);
411    }
412
413    /**
414     * Dispatch onLowMemory() to all fragments.
415     */
416    @Override
417    public void onLowMemory() {
418        super.onLowMemory();
419        mFragments.dispatchLowMemory();
420    }
421
422    /**
423     * Dispatch context and options menu to fragments.
424     */
425    @Override
426    public boolean onMenuItemSelected(int featureId, MenuItem item) {
427        if (super.onMenuItemSelected(featureId, item)) {
428            return true;
429        }
430
431        switch (featureId) {
432            case Window.FEATURE_OPTIONS_PANEL:
433                return mFragments.dispatchOptionsItemSelected(item);
434
435            case Window.FEATURE_CONTEXT_MENU:
436                return mFragments.dispatchContextItemSelected(item);
437
438            default:
439                return false;
440        }
441    }
442
443    /**
444     * Call onOptionsMenuClosed() on fragments.
445     */
446    @Override
447    public void onPanelClosed(int featureId, Menu menu) {
448        switch (featureId) {
449            case Window.FEATURE_OPTIONS_PANEL:
450                mFragments.dispatchOptionsMenuClosed(menu);
451                break;
452        }
453        super.onPanelClosed(featureId, menu);
454    }
455
456    /**
457     * Dispatch onPause() to fragments.
458     */
459    @Override
460    protected void onPause() {
461        super.onPause();
462        mResumed = false;
463        if (mHandler.hasMessages(MSG_RESUME_PENDING)) {
464            mHandler.removeMessages(MSG_RESUME_PENDING);
465            onResumeFragments();
466        }
467        mFragments.dispatchPause();
468    }
469
470    /**
471     * Handle onNewIntent() to inform the fragment manager that the
472     * state is not saved.  If you are handling new intents and may be
473     * making changes to the fragment state, you want to be sure to call
474     * through to the super-class here first.  Otherwise, if your state
475     * is saved but the activity is not stopped, you could get an
476     * onNewIntent() call which happens before onResume() and trying to
477     * perform fragment operations at that point will throw IllegalStateException
478     * because the fragment manager thinks the state is still saved.
479     */
480    @Override
481    protected void onNewIntent(Intent intent) {
482        super.onNewIntent(intent);
483        mFragments.noteStateNotSaved();
484    }
485
486    /**
487     * Dispatch onResume() to fragments.  Note that for better inter-operation
488     * with older versions of the platform, at the point of this call the
489     * fragments attached to the activity are <em>not</em> resumed.  This means
490     * that in some cases the previous state may still be saved, not allowing
491     * fragment transactions that modify the state.  To correctly interact
492     * with fragments in their proper state, you should instead override
493     * {@link #onResumeFragments()}.
494     */
495    @Override
496    protected void onResume() {
497        super.onResume();
498        mHandler.sendEmptyMessage(MSG_RESUME_PENDING);
499        mResumed = true;
500        mFragments.execPendingActions();
501    }
502
503    /**
504     * Dispatch onResume() to fragments.
505     */
506    @Override
507    protected void onPostResume() {
508        super.onPostResume();
509        mHandler.removeMessages(MSG_RESUME_PENDING);
510        onResumeFragments();
511        mFragments.execPendingActions();
512    }
513
514    /**
515     * This is the fragment-orientated version of {@link #onResume()} that you
516     * can override to perform operations in the Activity at the same point
517     * where its fragments are resumed.  Be sure to always call through to
518     * the super-class.
519     */
520    protected void onResumeFragments() {
521        mFragments.dispatchResume();
522    }
523
524    /**
525     * Dispatch onPrepareOptionsMenu() to fragments.
526     */
527    @Override
528    public boolean onPreparePanel(int featureId, View view, Menu menu) {
529        if (featureId == Window.FEATURE_OPTIONS_PANEL && menu != null) {
530            if (mOptionsMenuInvalidated) {
531                mOptionsMenuInvalidated = false;
532                menu.clear();
533                onCreatePanelMenu(featureId, menu);
534            }
535            boolean goforit = onPrepareOptionsPanel(view, menu);
536            goforit |= mFragments.dispatchPrepareOptionsMenu(menu);
537            return goforit;
538        }
539        return super.onPreparePanel(featureId, view, menu);
540    }
541
542    /**
543     * @hide
544     */
545    protected boolean onPrepareOptionsPanel(View view, Menu menu) {
546        return super.onPreparePanel(Window.FEATURE_OPTIONS_PANEL, view, menu);
547    }
548
549    /**
550     * Retain all appropriate fragment and loader state.  You can NOT
551     * override this yourself!  Use {@link #onRetainCustomNonConfigurationInstance()}
552     * if you want to retain your own state.
553     */
554    @Override
555    public final Object onRetainNonConfigurationInstance() {
556        if (mStopped) {
557            doReallyStop(true);
558        }
559
560        Object custom = onRetainCustomNonConfigurationInstance();
561
562        ArrayList<Fragment> fragments = mFragments.retainNonConfig();
563        boolean retainLoaders = false;
564        if (mAllLoaderManagers != null) {
565            // prune out any loader managers that were already stopped and so
566            // have nothing useful to retain.
567            final int N = mAllLoaderManagers.size();
568            LoaderManagerImpl loaders[] = new LoaderManagerImpl[N];
569            for (int i=N-1; i>=0; i--) {
570                loaders[i] = mAllLoaderManagers.valueAt(i);
571            }
572            for (int i=0; i<N; i++) {
573                LoaderManagerImpl lm = loaders[i];
574                if (lm.mRetaining) {
575                    retainLoaders = true;
576                } else {
577                    lm.doDestroy();
578                    mAllLoaderManagers.remove(lm.mWho);
579                }
580            }
581        }
582        if (fragments == null && !retainLoaders && custom == null) {
583            return null;
584        }
585
586        NonConfigurationInstances nci = new NonConfigurationInstances();
587        nci.activity = null;
588        nci.custom = custom;
589        nci.children = null;
590        nci.fragments = fragments;
591        nci.loaders = mAllLoaderManagers;
592        return nci;
593    }
594
595    /**
596     * Save all appropriate fragment state.
597     */
598    @Override
599    protected void onSaveInstanceState(Bundle outState) {
600        super.onSaveInstanceState(outState);
601        Parcelable p = mFragments.saveAllState();
602        if (p != null) {
603            outState.putParcelable(FRAGMENTS_TAG, p);
604        }
605    }
606
607    /**
608     * Dispatch onStart() to all fragments.  Ensure any created loaders are
609     * now started.
610     */
611    @Override
612    protected void onStart() {
613        super.onStart();
614
615        mStopped = false;
616        mReallyStopped = false;
617        mHandler.removeMessages(MSG_REALLY_STOPPED);
618
619        if (!mCreated) {
620            mCreated = true;
621            mFragments.dispatchActivityCreated();
622        }
623
624        mFragments.noteStateNotSaved();
625        mFragments.execPendingActions();
626
627        if (!mLoadersStarted) {
628            mLoadersStarted = true;
629            if (mLoaderManager != null) {
630                mLoaderManager.doStart();
631            } else if (!mCheckedForLoaderManager) {
632                mLoaderManager = getLoaderManager("(root)", mLoadersStarted, false);
633                // the returned loader manager may be a new one, so we have to start it
634                if ((mLoaderManager != null) && (!mLoaderManager.mStarted)) {
635                    mLoaderManager.doStart();
636                }
637            }
638            mCheckedForLoaderManager = true;
639        }
640        // NOTE: HC onStart goes here.
641
642        mFragments.dispatchStart();
643        if (mAllLoaderManagers != null) {
644            final int N = mAllLoaderManagers.size();
645            LoaderManagerImpl loaders[] = new LoaderManagerImpl[N];
646            for (int i=N-1; i>=0; i--) {
647                loaders[i] = mAllLoaderManagers.valueAt(i);
648            }
649            for (int i=0; i<N; i++) {
650                LoaderManagerImpl lm = loaders[i];
651                lm.finishRetain();
652                lm.doReportStart();
653            }
654        }
655    }
656
657    /**
658     * Dispatch onStop() to all fragments.  Ensure all loaders are stopped.
659     */
660    @Override
661    protected void onStop() {
662        super.onStop();
663
664        mStopped = true;
665        mHandler.sendEmptyMessage(MSG_REALLY_STOPPED);
666
667        mFragments.dispatchStop();
668    }
669
670    // ------------------------------------------------------------------------
671    // NEW METHODS
672    // ------------------------------------------------------------------------
673
674    /**
675     * Use this instead of {@link #onRetainNonConfigurationInstance()}.
676     * Retrieve later with {@link #getLastCustomNonConfigurationInstance()}.
677     */
678    public Object onRetainCustomNonConfigurationInstance() {
679        return null;
680    }
681
682    /**
683     * Return the value previously returned from
684     * {@link #onRetainCustomNonConfigurationInstance()}.
685     */
686    public Object getLastCustomNonConfigurationInstance() {
687        NonConfigurationInstances nc = (NonConfigurationInstances)
688                getLastNonConfigurationInstance();
689        return nc != null ? nc.custom : null;
690    }
691
692    /**
693     * Support library version of {@link Activity#invalidateOptionsMenu}.
694     *
695     * <p>Invalidate the activity's options menu. This will cause relevant presentations
696     * of the menu to fully update via calls to onCreateOptionsMenu and
697     * onPrepareOptionsMenu the next time the menu is requested.
698     */
699    public void supportInvalidateOptionsMenu() {
700        if (android.os.Build.VERSION.SDK_INT >= HONEYCOMB) {
701            // If we are running on HC or greater, we can use the framework
702            // API to invalidate the options menu.
703            ActivityCompatHoneycomb.invalidateOptionsMenu(this);
704            return;
705        }
706
707        // Whoops, older platform...  we'll use a hack, to manually rebuild
708        // the options menu the next time it is prepared.
709        mOptionsMenuInvalidated = true;
710    }
711
712    /**
713     * Print the Activity's state into the given stream.  This gets invoked if
714     * you run "adb shell dumpsys activity <activity_component_name>".
715     *
716     * @param prefix Desired prefix to prepend at each line of output.
717     * @param fd The raw file descriptor that the dump is being sent to.
718     * @param writer The PrintWriter to which you should dump your state.  This will be
719     * closed for you after you return.
720     * @param args additional arguments to the dump request.
721     */
722    public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
723        if (android.os.Build.VERSION.SDK_INT >= HONEYCOMB) {
724            // XXX This can only work if we can call the super-class impl. :/
725            //ActivityCompatHoneycomb.dump(this, prefix, fd, writer, args);
726        }
727        writer.print(prefix); writer.print("Local FragmentActivity ");
728                writer.print(Integer.toHexString(System.identityHashCode(this)));
729                writer.println(" State:");
730        String innerPrefix = prefix + "  ";
731        writer.print(innerPrefix); writer.print("mCreated=");
732                writer.print(mCreated); writer.print("mResumed=");
733                writer.print(mResumed); writer.print(" mStopped=");
734                writer.print(mStopped); writer.print(" mReallyStopped=");
735                writer.println(mReallyStopped);
736        writer.print(innerPrefix); writer.print("mLoadersStarted=");
737                writer.println(mLoadersStarted);
738        if (mLoaderManager != null) {
739            writer.print(prefix); writer.print("Loader Manager ");
740                    writer.print(Integer.toHexString(System.identityHashCode(mLoaderManager)));
741                    writer.println(":");
742            mLoaderManager.dump(prefix + "  ", fd, writer, args);
743        }
744        mFragments.dump(prefix, fd, writer, args);
745        writer.print(prefix); writer.println("View Hierarchy:");
746        dumpViewHierarchy(prefix + "  ", writer, getWindow().getDecorView());
747    }
748
749    private static String viewToString(View view) {
750        StringBuilder out = new StringBuilder(128);
751        out.append(view.getClass().getName());
752        out.append('{');
753        out.append(Integer.toHexString(System.identityHashCode(view)));
754        out.append(' ');
755        switch (view.getVisibility()) {
756            case View.VISIBLE: out.append('V'); break;
757            case View.INVISIBLE: out.append('I'); break;
758            case View.GONE: out.append('G'); break;
759            default: out.append('.'); break;
760        }
761        out.append(view.isFocusable() ? 'F' : '.');
762        out.append(view.isEnabled() ? 'E' : '.');
763        out.append(view.willNotDraw() ? '.' : 'D');
764        out.append(view.isHorizontalScrollBarEnabled()? 'H' : '.');
765        out.append(view.isVerticalScrollBarEnabled() ? 'V' : '.');
766        out.append(view.isClickable() ? 'C' : '.');
767        out.append(view.isLongClickable() ? 'L' : '.');
768        out.append(' ');
769        out.append(view.isFocused() ? 'F' : '.');
770        out.append(view.isSelected() ? 'S' : '.');
771        out.append(view.isPressed() ? 'P' : '.');
772        out.append(' ');
773        out.append(view.getLeft());
774        out.append(',');
775        out.append(view.getTop());
776        out.append('-');
777        out.append(view.getRight());
778        out.append(',');
779        out.append(view.getBottom());
780        final int id = view.getId();
781        if (id != View.NO_ID) {
782            out.append(" #");
783            out.append(Integer.toHexString(id));
784            final Resources r = view.getResources();
785            if (id != 0 && r != null) {
786                try {
787                    String pkgname;
788                    switch (id&0xff000000) {
789                        case 0x7f000000:
790                            pkgname="app";
791                            break;
792                        case 0x01000000:
793                            pkgname="android";
794                            break;
795                        default:
796                            pkgname = r.getResourcePackageName(id);
797                            break;
798                    }
799                    String typename = r.getResourceTypeName(id);
800                    String entryname = r.getResourceEntryName(id);
801                    out.append(" ");
802                    out.append(pkgname);
803                    out.append(":");
804                    out.append(typename);
805                    out.append("/");
806                    out.append(entryname);
807                } catch (Resources.NotFoundException e) {
808                }
809            }
810        }
811        out.append("}");
812        return out.toString();
813    }
814
815    private void dumpViewHierarchy(String prefix, PrintWriter writer, View view) {
816        writer.print(prefix);
817        if (view == null) {
818            writer.println("null");
819            return;
820        }
821        writer.println(viewToString(view));
822        if (!(view instanceof ViewGroup)) {
823            return;
824        }
825        ViewGroup grp = (ViewGroup)view;
826        final int N = grp.getChildCount();
827        if (N <= 0) {
828            return;
829        }
830        prefix = prefix + "  ";
831        for (int i=0; i<N; i++) {
832            dumpViewHierarchy(prefix, writer, grp.getChildAt(i));
833        }
834    }
835
836    void doReallyStop(boolean retaining) {
837        if (!mReallyStopped) {
838            mReallyStopped = true;
839            mRetaining = retaining;
840            mHandler.removeMessages(MSG_REALLY_STOPPED);
841            onReallyStop();
842        }
843    }
844
845    /**
846     * Pre-HC, we didn't have a way to determine whether an activity was
847     * being stopped for a config change or not until we saw
848     * onRetainNonConfigurationInstance() called after onStop().  However
849     * we need to know this, to know whether to retain fragments.  This will
850     * tell us what we need to know.
851     */
852    void onReallyStop() {
853        if (mLoadersStarted) {
854            mLoadersStarted = false;
855            if (mLoaderManager != null) {
856                if (!mRetaining) {
857                    mLoaderManager.doStop();
858                } else {
859                    mLoaderManager.doRetain();
860                }
861            }
862        }
863
864        mFragments.dispatchReallyStop();
865    }
866
867    // ------------------------------------------------------------------------
868    // FRAGMENT SUPPORT
869    // ------------------------------------------------------------------------
870
871    /**
872     * Called when a fragment is attached to the activity.
873     */
874    public void onAttachFragment(Fragment fragment) {
875    }
876
877    /**
878     * Return the FragmentManager for interacting with fragments associated
879     * with this activity.
880     */
881    public FragmentManager getSupportFragmentManager() {
882        return mFragments;
883    }
884
885    /**
886     * Modifies the standard behavior to allow results to be delivered to fragments.
887     * This imposes a restriction that requestCode be <= 0xffff.
888     */
889    @Override
890    public void startActivityForResult(Intent intent, int requestCode) {
891        if (requestCode != -1 && (requestCode&0xffff0000) != 0) {
892            throw new IllegalArgumentException("Can only use lower 16 bits for requestCode");
893        }
894        super.startActivityForResult(intent, requestCode);
895    }
896
897    /**
898     * Called by Fragment.startActivityForResult() to implement its behavior.
899     */
900    public void startActivityFromFragment(Fragment fragment, Intent intent,
901            int requestCode) {
902        if (requestCode == -1) {
903            super.startActivityForResult(intent, -1);
904            return;
905        }
906        if ((requestCode&0xffff0000) != 0) {
907            throw new IllegalArgumentException("Can only use lower 16 bits for requestCode");
908        }
909        super.startActivityForResult(intent, ((fragment.mIndex+1)<<16) + (requestCode&0xffff));
910    }
911
912    void invalidateSupportFragment(String who) {
913        //Log.v(TAG, "invalidateSupportFragment: who=" + who);
914        if (mAllLoaderManagers != null) {
915            LoaderManagerImpl lm = mAllLoaderManagers.get(who);
916            if (lm != null && !lm.mRetaining) {
917                lm.doDestroy();
918                mAllLoaderManagers.remove(who);
919            }
920        }
921    }
922
923    // ------------------------------------------------------------------------
924    // LOADER SUPPORT
925    // ------------------------------------------------------------------------
926
927    /**
928     * Return the LoaderManager for this fragment, creating it if needed.
929     */
930    public LoaderManager getSupportLoaderManager() {
931        if (mLoaderManager != null) {
932            return mLoaderManager;
933        }
934        mCheckedForLoaderManager = true;
935        mLoaderManager = getLoaderManager("(root)", mLoadersStarted, true);
936        return mLoaderManager;
937    }
938
939    LoaderManagerImpl getLoaderManager(String who, boolean started, boolean create) {
940        if (mAllLoaderManagers == null) {
941            mAllLoaderManagers = new SimpleArrayMap<String, LoaderManagerImpl>();
942        }
943        LoaderManagerImpl lm = mAllLoaderManagers.get(who);
944        if (lm == null) {
945            if (create) {
946                lm = new LoaderManagerImpl(who, this, started);
947                mAllLoaderManagers.put(who, lm);
948            }
949        } else {
950            lm.updateActivity(this);
951        }
952        return lm;
953    }
954}
955