FragmentActivity.java revision 20680c49584c26f7818c2dec8fb00633af753e4a
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.os.Bundle;
25import android.os.Handler;
26import android.os.Message;
27import android.os.Parcelable;
28import android.support.annotation.NonNull;
29import android.support.annotation.Nullable;
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 BaseFragmentActivityHoneycomb {
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        @Nullable
110        public View findViewById(int id) {
111            return FragmentActivity.this.findViewById(id);
112        }
113
114        @Override
115        public boolean hasView() {
116            Window window = FragmentActivity.this.getWindow();
117            return (window != null && window.peekDecorView() != null);
118        }
119    };
120
121    boolean mCreated;
122    boolean mResumed;
123    boolean mStopped;
124    boolean mReallyStopped;
125    boolean mRetaining;
126
127    boolean mOptionsMenuInvalidated;
128
129    boolean mCheckedForLoaderManager;
130    boolean mLoadersStarted;
131    SimpleArrayMap<String, LoaderManagerImpl> mAllLoaderManagers;
132    LoaderManagerImpl mLoaderManager;
133
134    static final class NonConfigurationInstances {
135        Object activity;
136        Object custom;
137        SimpleArrayMap<String, Object> children;
138        ArrayList<Fragment> fragments;
139        SimpleArrayMap<String, LoaderManagerImpl> loaders;
140    }
141
142    // ------------------------------------------------------------------------
143    // HOOKS INTO ACTIVITY
144    // ------------------------------------------------------------------------
145
146    /**
147     * Dispatch incoming result to the correct fragment.
148     */
149    @Override
150    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
151        mFragments.noteStateNotSaved();
152        int index = requestCode>>16;
153        if (index != 0) {
154            index--;
155            if (mFragments.mActive == null || index < 0 || index >= mFragments.mActive.size()) {
156                Log.w(TAG, "Activity result fragment index out of range: 0x"
157                        + Integer.toHexString(requestCode));
158                return;
159            }
160            Fragment frag = mFragments.mActive.get(index);
161            if (frag == null) {
162                Log.w(TAG, "Activity result no fragment exists for index: 0x"
163                        + Integer.toHexString(requestCode));
164            } else {
165                frag.onActivityResult(requestCode&0xffff, resultCode, data);
166            }
167            return;
168        }
169
170        super.onActivityResult(requestCode, resultCode, data);
171    }
172
173    /**
174     * Take care of popping the fragment back stack or finishing the activity
175     * as appropriate.
176     */
177    public void onBackPressed() {
178        if (!mFragments.popBackStackImmediate()) {
179            supportFinishAfterTransition();
180        }
181    }
182
183    /**
184     * Reverses the Activity Scene entry Transition and triggers the calling Activity
185     * to reverse its exit Transition. When the exit Transition completes,
186     * {@link #finish()} is called. If no entry Transition was used, finish() is called
187     * immediately and the Activity exit Transition is run.
188     *
189     * <p>On Android 4.4 or lower, this method only finishes the Activity with no
190     * special exit transition.</p>
191     */
192    public void supportFinishAfterTransition() {
193        ActivityCompat.finishAfterTransition(this);
194    }
195
196    /**
197     * When {@link android.app.ActivityOptions#makeSceneTransitionAnimation(Activity,
198     * android.view.View, String)} was used to start an Activity, <var>callback</var>
199     * will be called to handle shared elements on the <i>launched</i> Activity. This requires
200     * {@link Window#FEATURE_CONTENT_TRANSITIONS}.
201     *
202     * @param callback Used to manipulate shared element transitions on the launched Activity.
203     */
204    public void setEnterSharedElementCallback(SharedElementCallback callback) {
205        ActivityCompat.setEnterSharedElementCallback(this, callback);
206    }
207
208    /**
209     * When {@link android.app.ActivityOptions#makeSceneTransitionAnimation(Activity,
210     * android.view.View, String)} was used to start an Activity, <var>listener</var>
211     * will be called to handle shared elements on the <i>launching</i> Activity. Most
212     * calls will only come when returning from the started Activity.
213     * This requires {@link Window#FEATURE_CONTENT_TRANSITIONS}.
214     *
215     * @param listener Used to manipulate shared element transitions on the launching Activity.
216     */
217    public void setExitSharedElementCallback(SharedElementCallback listener) {
218        ActivityCompat.setExitSharedElementCallback(this, listener);
219    }
220
221    /**
222     * Support library version of {@link android.app.Activity#postponeEnterTransition()} that works
223     * only on API 21 and later.
224     */
225    public void supportPostponeEnterTransition() {
226        ActivityCompat.postponeEnterTransition(this);
227    }
228
229    /**
230     * Support library version of {@link android.app.Activity#startPostponedEnterTransition()}
231     * that only works with API 21 and later.
232     */
233    public void supportStartPostponedEnterTransition() {
234        ActivityCompat.startPostponedEnterTransition(this);
235    }
236
237    /**
238     * Dispatch configuration change to all fragments.
239     */
240    @Override
241    public void onConfigurationChanged(Configuration newConfig) {
242        super.onConfigurationChanged(newConfig);
243        mFragments.dispatchConfigurationChanged(newConfig);
244    }
245
246    /**
247     * Perform initialization of all fragments and loaders.
248     */
249    @Override
250    protected void onCreate(@Nullable Bundle savedInstanceState) {
251        mFragments.attachActivity(this, mContainer, null);
252
253        super.onCreate(savedInstanceState);
254
255        NonConfigurationInstances nc = (NonConfigurationInstances)
256                getLastNonConfigurationInstance();
257        if (nc != null) {
258            mAllLoaderManagers = nc.loaders;
259        }
260        if (savedInstanceState != null) {
261            Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
262            mFragments.restoreAllState(p, nc != null ? nc.fragments : null);
263        }
264        mFragments.dispatchCreate();
265    }
266
267    /**
268     * Dispatch to Fragment.onCreateOptionsMenu().
269     */
270    @Override
271    public boolean onCreatePanelMenu(int featureId, Menu menu) {
272        if (featureId == Window.FEATURE_OPTIONS_PANEL) {
273            boolean show = super.onCreatePanelMenu(featureId, menu);
274            show |= mFragments.dispatchCreateOptionsMenu(menu, getMenuInflater());
275            if (android.os.Build.VERSION.SDK_INT >= HONEYCOMB) {
276                return show;
277            }
278            // Prior to Honeycomb, the framework can't invalidate the options
279            // menu, so we must always say we have one in case the app later
280            // invalidates it and needs to have it shown.
281            return true;
282        }
283        return super.onCreatePanelMenu(featureId, menu);
284    }
285
286    @Override
287    final View dispatchFragmentsOnCreateView(View parent, String name, Context context,
288            AttributeSet attrs) {
289        return mFragments.onCreateView(parent, name, context, attrs);
290    }
291
292    /**
293     * Destroy all fragments and loaders.
294     */
295    @Override
296    protected void onDestroy() {
297        super.onDestroy();
298
299        doReallyStop(false);
300
301        mFragments.dispatchDestroy();
302        if (mLoaderManager != null) {
303            mLoaderManager.doDestroy();
304        }
305    }
306
307    /**
308     * Take care of calling onBackPressed() for pre-Eclair platforms.
309     */
310    @Override
311    public boolean onKeyDown(int keyCode, KeyEvent event) {
312        if (android.os.Build.VERSION.SDK_INT < 5 /* ECLAIR */
313                && keyCode == KeyEvent.KEYCODE_BACK
314                && event.getRepeatCount() == 0) {
315            // Take care of calling this method on earlier versions of
316            // the platform where it doesn't exist.
317            onBackPressed();
318            return true;
319        }
320
321        return super.onKeyDown(keyCode, event);
322    }
323
324    /**
325     * Dispatch onLowMemory() to all fragments.
326     */
327    @Override
328    public void onLowMemory() {
329        super.onLowMemory();
330        mFragments.dispatchLowMemory();
331    }
332
333    /**
334     * Dispatch context and options menu to fragments.
335     */
336    @Override
337    public boolean onMenuItemSelected(int featureId, MenuItem item) {
338        if (super.onMenuItemSelected(featureId, item)) {
339            return true;
340        }
341
342        switch (featureId) {
343            case Window.FEATURE_OPTIONS_PANEL:
344                return mFragments.dispatchOptionsItemSelected(item);
345
346            case Window.FEATURE_CONTEXT_MENU:
347                return mFragments.dispatchContextItemSelected(item);
348
349            default:
350                return false;
351        }
352    }
353
354    /**
355     * Call onOptionsMenuClosed() on fragments.
356     */
357    @Override
358    public void onPanelClosed(int featureId, Menu menu) {
359        switch (featureId) {
360            case Window.FEATURE_OPTIONS_PANEL:
361                mFragments.dispatchOptionsMenuClosed(menu);
362                break;
363        }
364        super.onPanelClosed(featureId, menu);
365    }
366
367    /**
368     * Dispatch onPause() to fragments.
369     */
370    @Override
371    protected void onPause() {
372        super.onPause();
373        mResumed = false;
374        if (mHandler.hasMessages(MSG_RESUME_PENDING)) {
375            mHandler.removeMessages(MSG_RESUME_PENDING);
376            onResumeFragments();
377        }
378        mFragments.dispatchPause();
379    }
380
381    /**
382     * Handle onNewIntent() to inform the fragment manager that the
383     * state is not saved.  If you are handling new intents and may be
384     * making changes to the fragment state, you want to be sure to call
385     * through to the super-class here first.  Otherwise, if your state
386     * is saved but the activity is not stopped, you could get an
387     * onNewIntent() call which happens before onResume() and trying to
388     * perform fragment operations at that point will throw IllegalStateException
389     * because the fragment manager thinks the state is still saved.
390     */
391    @Override
392    protected void onNewIntent(Intent intent) {
393        super.onNewIntent(intent);
394        mFragments.noteStateNotSaved();
395    }
396
397    /**
398     * Dispatch onResume() to fragments.  Note that for better inter-operation
399     * with older versions of the platform, at the point of this call the
400     * fragments attached to the activity are <em>not</em> resumed.  This means
401     * that in some cases the previous state may still be saved, not allowing
402     * fragment transactions that modify the state.  To correctly interact
403     * with fragments in their proper state, you should instead override
404     * {@link #onResumeFragments()}.
405     */
406    @Override
407    protected void onResume() {
408        super.onResume();
409        mHandler.sendEmptyMessage(MSG_RESUME_PENDING);
410        mResumed = true;
411        mFragments.execPendingActions();
412    }
413
414    /**
415     * Dispatch onResume() to fragments.
416     */
417    @Override
418    protected void onPostResume() {
419        super.onPostResume();
420        mHandler.removeMessages(MSG_RESUME_PENDING);
421        onResumeFragments();
422        mFragments.execPendingActions();
423    }
424
425    /**
426     * This is the fragment-orientated version of {@link #onResume()} that you
427     * can override to perform operations in the Activity at the same point
428     * where its fragments are resumed.  Be sure to always call through to
429     * the super-class.
430     */
431    protected void onResumeFragments() {
432        mFragments.dispatchResume();
433    }
434
435    /**
436     * Dispatch onPrepareOptionsMenu() to fragments.
437     */
438    @Override
439    public boolean onPreparePanel(int featureId, View view, Menu menu) {
440        if (featureId == Window.FEATURE_OPTIONS_PANEL && menu != null) {
441            if (mOptionsMenuInvalidated) {
442                mOptionsMenuInvalidated = false;
443                menu.clear();
444                onCreatePanelMenu(featureId, menu);
445            }
446            boolean goforit = onPrepareOptionsPanel(view, menu);
447            goforit |= mFragments.dispatchPrepareOptionsMenu(menu);
448            return goforit;
449        }
450        return super.onPreparePanel(featureId, view, menu);
451    }
452
453    /**
454     * @hide
455     */
456    protected boolean onPrepareOptionsPanel(View view, Menu menu) {
457        return super.onPreparePanel(Window.FEATURE_OPTIONS_PANEL, view, menu);
458    }
459
460    /**
461     * Retain all appropriate fragment and loader state.  You can NOT
462     * override this yourself!  Use {@link #onRetainCustomNonConfigurationInstance()}
463     * if you want to retain your own state.
464     */
465    @Override
466    public final Object onRetainNonConfigurationInstance() {
467        if (mStopped) {
468            doReallyStop(true);
469        }
470
471        Object custom = onRetainCustomNonConfigurationInstance();
472
473        ArrayList<Fragment> fragments = mFragments.retainNonConfig();
474        boolean retainLoaders = false;
475        if (mAllLoaderManagers != null) {
476            // prune out any loader managers that were already stopped and so
477            // have nothing useful to retain.
478            final int N = mAllLoaderManagers.size();
479            LoaderManagerImpl loaders[] = new LoaderManagerImpl[N];
480            for (int i=N-1; i>=0; i--) {
481                loaders[i] = mAllLoaderManagers.valueAt(i);
482            }
483            for (int i=0; i<N; i++) {
484                LoaderManagerImpl lm = loaders[i];
485                if (lm.mRetaining) {
486                    retainLoaders = true;
487                } else {
488                    lm.doDestroy();
489                    mAllLoaderManagers.remove(lm.mWho);
490                }
491            }
492        }
493        if (fragments == null && !retainLoaders && custom == null) {
494            return null;
495        }
496
497        NonConfigurationInstances nci = new NonConfigurationInstances();
498        nci.activity = null;
499        nci.custom = custom;
500        nci.children = null;
501        nci.fragments = fragments;
502        nci.loaders = mAllLoaderManagers;
503        return nci;
504    }
505
506    /**
507     * Save all appropriate fragment state.
508     */
509    @Override
510    protected void onSaveInstanceState(Bundle outState) {
511        super.onSaveInstanceState(outState);
512        Parcelable p = mFragments.saveAllState();
513        if (p != null) {
514            outState.putParcelable(FRAGMENTS_TAG, p);
515        }
516    }
517
518    /**
519     * Dispatch onStart() to all fragments.  Ensure any created loaders are
520     * now started.
521     */
522    @Override
523    protected void onStart() {
524        super.onStart();
525
526        mStopped = false;
527        mReallyStopped = false;
528        mHandler.removeMessages(MSG_REALLY_STOPPED);
529
530        if (!mCreated) {
531            mCreated = true;
532            mFragments.dispatchActivityCreated();
533        }
534
535        mFragments.noteStateNotSaved();
536        mFragments.execPendingActions();
537
538        if (!mLoadersStarted) {
539            mLoadersStarted = true;
540            if (mLoaderManager != null) {
541                mLoaderManager.doStart();
542            } else if (!mCheckedForLoaderManager) {
543                mLoaderManager = getLoaderManager("(root)", mLoadersStarted, false);
544                // the returned loader manager may be a new one, so we have to start it
545                if ((mLoaderManager != null) && (!mLoaderManager.mStarted)) {
546                    mLoaderManager.doStart();
547                }
548            }
549            mCheckedForLoaderManager = true;
550        }
551        // NOTE: HC onStart goes here.
552
553        mFragments.dispatchStart();
554        if (mAllLoaderManagers != null) {
555            final int N = mAllLoaderManagers.size();
556            LoaderManagerImpl loaders[] = new LoaderManagerImpl[N];
557            for (int i=N-1; i>=0; i--) {
558                loaders[i] = mAllLoaderManagers.valueAt(i);
559            }
560            for (int i=0; i<N; i++) {
561                LoaderManagerImpl lm = loaders[i];
562                lm.finishRetain();
563                lm.doReportStart();
564            }
565        }
566    }
567
568    /**
569     * Dispatch onStop() to all fragments.  Ensure all loaders are stopped.
570     */
571    @Override
572    protected void onStop() {
573        super.onStop();
574
575        mStopped = true;
576        mHandler.sendEmptyMessage(MSG_REALLY_STOPPED);
577
578        mFragments.dispatchStop();
579    }
580
581    // ------------------------------------------------------------------------
582    // NEW METHODS
583    // ------------------------------------------------------------------------
584
585    /**
586     * Use this instead of {@link #onRetainNonConfigurationInstance()}.
587     * Retrieve later with {@link #getLastCustomNonConfigurationInstance()}.
588     */
589    public Object onRetainCustomNonConfigurationInstance() {
590        return null;
591    }
592
593    /**
594     * Return the value previously returned from
595     * {@link #onRetainCustomNonConfigurationInstance()}.
596     */
597    public Object getLastCustomNonConfigurationInstance() {
598        NonConfigurationInstances nc = (NonConfigurationInstances)
599                getLastNonConfigurationInstance();
600        return nc != null ? nc.custom : null;
601    }
602
603    /**
604     * Support library version of {@link Activity#invalidateOptionsMenu}.
605     *
606     * <p>Invalidate the activity's options menu. This will cause relevant presentations
607     * of the menu to fully update via calls to onCreateOptionsMenu and
608     * onPrepareOptionsMenu the next time the menu is requested.
609     */
610    public void supportInvalidateOptionsMenu() {
611        if (android.os.Build.VERSION.SDK_INT >= HONEYCOMB) {
612            // If we are running on HC or greater, we can use the framework
613            // API to invalidate the options menu.
614            ActivityCompatHoneycomb.invalidateOptionsMenu(this);
615            return;
616        }
617
618        // Whoops, older platform...  we'll use a hack, to manually rebuild
619        // the options menu the next time it is prepared.
620        mOptionsMenuInvalidated = true;
621    }
622
623    /**
624     * Print the Activity's state into the given stream.  This gets invoked if
625     * you run "adb shell dumpsys activity <activity_component_name>".
626     *
627     * @param prefix Desired prefix to prepend at each line of output.
628     * @param fd The raw file descriptor that the dump is being sent to.
629     * @param writer The PrintWriter to which you should dump your state.  This will be
630     * closed for you after you return.
631     * @param args additional arguments to the dump request.
632     */
633    public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
634        if (android.os.Build.VERSION.SDK_INT >= HONEYCOMB) {
635            // XXX This can only work if we can call the super-class impl. :/
636            //ActivityCompatHoneycomb.dump(this, prefix, fd, writer, args);
637        }
638        writer.print(prefix); writer.print("Local FragmentActivity ");
639                writer.print(Integer.toHexString(System.identityHashCode(this)));
640                writer.println(" State:");
641        String innerPrefix = prefix + "  ";
642        writer.print(innerPrefix); writer.print("mCreated=");
643                writer.print(mCreated); writer.print("mResumed=");
644                writer.print(mResumed); writer.print(" mStopped=");
645                writer.print(mStopped); writer.print(" mReallyStopped=");
646                writer.println(mReallyStopped);
647        writer.print(innerPrefix); writer.print("mLoadersStarted=");
648                writer.println(mLoadersStarted);
649        if (mLoaderManager != null) {
650            writer.print(prefix); writer.print("Loader Manager ");
651                    writer.print(Integer.toHexString(System.identityHashCode(mLoaderManager)));
652                    writer.println(":");
653            mLoaderManager.dump(prefix + "  ", fd, writer, args);
654        }
655        mFragments.dump(prefix, fd, writer, args);
656        writer.print(prefix); writer.println("View Hierarchy:");
657        dumpViewHierarchy(prefix + "  ", writer, getWindow().getDecorView());
658    }
659
660    private static String viewToString(View view) {
661        StringBuilder out = new StringBuilder(128);
662        out.append(view.getClass().getName());
663        out.append('{');
664        out.append(Integer.toHexString(System.identityHashCode(view)));
665        out.append(' ');
666        switch (view.getVisibility()) {
667            case View.VISIBLE: out.append('V'); break;
668            case View.INVISIBLE: out.append('I'); break;
669            case View.GONE: out.append('G'); break;
670            default: out.append('.'); break;
671        }
672        out.append(view.isFocusable() ? 'F' : '.');
673        out.append(view.isEnabled() ? 'E' : '.');
674        out.append(view.willNotDraw() ? '.' : 'D');
675        out.append(view.isHorizontalScrollBarEnabled()? 'H' : '.');
676        out.append(view.isVerticalScrollBarEnabled() ? 'V' : '.');
677        out.append(view.isClickable() ? 'C' : '.');
678        out.append(view.isLongClickable() ? 'L' : '.');
679        out.append(' ');
680        out.append(view.isFocused() ? 'F' : '.');
681        out.append(view.isSelected() ? 'S' : '.');
682        out.append(view.isPressed() ? 'P' : '.');
683        out.append(' ');
684        out.append(view.getLeft());
685        out.append(',');
686        out.append(view.getTop());
687        out.append('-');
688        out.append(view.getRight());
689        out.append(',');
690        out.append(view.getBottom());
691        final int id = view.getId();
692        if (id != View.NO_ID) {
693            out.append(" #");
694            out.append(Integer.toHexString(id));
695            final Resources r = view.getResources();
696            if (id != 0 && r != null) {
697                try {
698                    String pkgname;
699                    switch (id&0xff000000) {
700                        case 0x7f000000:
701                            pkgname="app";
702                            break;
703                        case 0x01000000:
704                            pkgname="android";
705                            break;
706                        default:
707                            pkgname = r.getResourcePackageName(id);
708                            break;
709                    }
710                    String typename = r.getResourceTypeName(id);
711                    String entryname = r.getResourceEntryName(id);
712                    out.append(" ");
713                    out.append(pkgname);
714                    out.append(":");
715                    out.append(typename);
716                    out.append("/");
717                    out.append(entryname);
718                } catch (Resources.NotFoundException e) {
719                }
720            }
721        }
722        out.append("}");
723        return out.toString();
724    }
725
726    private void dumpViewHierarchy(String prefix, PrintWriter writer, View view) {
727        writer.print(prefix);
728        if (view == null) {
729            writer.println("null");
730            return;
731        }
732        writer.println(viewToString(view));
733        if (!(view instanceof ViewGroup)) {
734            return;
735        }
736        ViewGroup grp = (ViewGroup)view;
737        final int N = grp.getChildCount();
738        if (N <= 0) {
739            return;
740        }
741        prefix = prefix + "  ";
742        for (int i=0; i<N; i++) {
743            dumpViewHierarchy(prefix, writer, grp.getChildAt(i));
744        }
745    }
746
747    void doReallyStop(boolean retaining) {
748        if (!mReallyStopped) {
749            mReallyStopped = true;
750            mRetaining = retaining;
751            mHandler.removeMessages(MSG_REALLY_STOPPED);
752            onReallyStop();
753        }
754    }
755
756    /**
757     * Pre-HC, we didn't have a way to determine whether an activity was
758     * being stopped for a config change or not until we saw
759     * onRetainNonConfigurationInstance() called after onStop().  However
760     * we need to know this, to know whether to retain fragments.  This will
761     * tell us what we need to know.
762     */
763    void onReallyStop() {
764        if (mLoadersStarted) {
765            mLoadersStarted = false;
766            if (mLoaderManager != null) {
767                if (!mRetaining) {
768                    mLoaderManager.doStop();
769                } else {
770                    mLoaderManager.doRetain();
771                }
772            }
773        }
774
775        mFragments.dispatchReallyStop();
776    }
777
778    // ------------------------------------------------------------------------
779    // FRAGMENT SUPPORT
780    // ------------------------------------------------------------------------
781
782    /**
783     * Called when a fragment is attached to the activity.
784     */
785    public void onAttachFragment(Fragment fragment) {
786    }
787
788    /**
789     * Return the FragmentManager for interacting with fragments associated
790     * with this activity.
791     */
792    public FragmentManager getSupportFragmentManager() {
793        return mFragments;
794    }
795
796    /**
797     * Modifies the standard behavior to allow results to be delivered to fragments.
798     * This imposes a restriction that requestCode be <= 0xffff.
799     */
800    @Override
801    public void startActivityForResult(Intent intent, int requestCode) {
802        if (requestCode != -1 && (requestCode&0xffff0000) != 0) {
803            throw new IllegalArgumentException("Can only use lower 16 bits for requestCode");
804        }
805        super.startActivityForResult(intent, requestCode);
806    }
807
808    /**
809     * Called by Fragment.startActivityForResult() to implement its behavior.
810     */
811    public void startActivityFromFragment(Fragment fragment, Intent intent,
812            int requestCode) {
813        if (requestCode == -1) {
814            super.startActivityForResult(intent, -1);
815            return;
816        }
817        if ((requestCode&0xffff0000) != 0) {
818            throw new IllegalArgumentException("Can only use lower 16 bits for requestCode");
819        }
820        super.startActivityForResult(intent, ((fragment.mIndex+1)<<16) + (requestCode&0xffff));
821    }
822
823    void invalidateSupportFragment(String who) {
824        //Log.v(TAG, "invalidateSupportFragment: who=" + who);
825        if (mAllLoaderManagers != null) {
826            LoaderManagerImpl lm = mAllLoaderManagers.get(who);
827            if (lm != null && !lm.mRetaining) {
828                lm.doDestroy();
829                mAllLoaderManagers.remove(who);
830            }
831        }
832    }
833
834    // ------------------------------------------------------------------------
835    // LOADER SUPPORT
836    // ------------------------------------------------------------------------
837
838    /**
839     * Return the LoaderManager for this fragment, creating it if needed.
840     */
841    public LoaderManager getSupportLoaderManager() {
842        if (mLoaderManager != null) {
843            return mLoaderManager;
844        }
845        mCheckedForLoaderManager = true;
846        mLoaderManager = getLoaderManager("(root)", mLoadersStarted, true);
847        return mLoaderManager;
848    }
849
850    LoaderManagerImpl getLoaderManager(String who, boolean started, boolean create) {
851        if (mAllLoaderManagers == null) {
852            mAllLoaderManagers = new SimpleArrayMap<String, LoaderManagerImpl>();
853        }
854        LoaderManagerImpl lm = mAllLoaderManagers.get(who);
855        if (lm == null) {
856            if (create) {
857                lm = new LoaderManagerImpl(who, this, started);
858                mAllLoaderManagers.put(who, lm);
859            }
860        } else {
861            lm.updateActivity(this);
862        }
863        return lm;
864    }
865}
866