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