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