• 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.IntentSender;
23 import android.content.res.Configuration;
24 import android.content.res.Resources;
25 import android.os.Bundle;
26 import android.os.Handler;
27 import android.os.Message;
28 import android.os.Parcelable;
29 import android.support.annotation.CallSuper;
30 import android.support.annotation.NonNull;
31 import android.support.annotation.Nullable;
32 import android.support.v4.media.session.MediaControllerCompat;
33 import android.support.v4.util.SimpleArrayMap;
34 import android.support.v4.util.SparseArrayCompat;
35 import android.util.AttributeSet;
36 import android.util.Log;
37 import android.view.LayoutInflater;
38 import android.view.Menu;
39 import android.view.MenuItem;
40 import android.view.View;
41 import android.view.ViewGroup;
42 import android.view.Window;
43 
44 import java.io.FileDescriptor;
45 import java.io.PrintWriter;
46 
47 /**
48  * Base class for activities that want to use the support-based
49  * {@link android.support.v4.app.Fragment} and
50  * {@link android.support.v4.content.Loader} APIs.
51  *
52  * <p>When using this class as opposed to new platform's built-in fragment
53  * and loader support, you must use the {@link #getSupportFragmentManager()}
54  * and {@link #getSupportLoaderManager()} methods respectively to access
55  * those features.
56  *
57  * <p>Known limitations:</p>
58  * <ul>
59  * <li> <p>When using the <code>&lt;fragment></code> tag, this implementation can not
60  * use the parent view's ID as the new fragment's ID.  You must explicitly
61  * specify an ID (or tag) in the <code>&lt;fragment></code>.</p>
62  * <li> <p>Prior to Honeycomb (3.0), an activity's state was saved before pausing.
63  * Fragments are a significant amount of new state, and dynamic enough that one
64  * often wants them to change between pausing and stopping.  These classes
65  * throw an exception if you try to change the fragment state after it has been
66  * saved, to avoid accidental loss of UI state.  However this is too restrictive
67  * prior to Honeycomb, where the state is saved before pausing.  To address this,
68  * when running on platforms prior to Honeycomb an exception will not be thrown
69  * if you change fragments between the state save and the activity being stopped.
70  * This means that in some cases if the activity is restored from its last saved
71  * state, this may be a snapshot slightly before what the user last saw.</p>
72  * </ul>
73  */
74 public class FragmentActivity extends BaseFragmentActivityJB implements
75         ActivityCompat.OnRequestPermissionsResultCallback,
76         ActivityCompatApi23.RequestPermissionsRequestCodeValidator {
77     private static final String TAG = "FragmentActivity";
78 
79     static final String FRAGMENTS_TAG = "android:support:fragments";
80     static final String NEXT_CANDIDATE_REQUEST_INDEX_TAG = "android:support:next_request_index";
81     static final String ALLOCATED_REQUEST_INDICIES_TAG = "android:support:request_indicies";
82     static final String REQUEST_FRAGMENT_WHO_TAG = "android:support:request_fragment_who";
83     static final int MAX_NUM_PENDING_FRAGMENT_ACTIVITY_RESULTS = 0xffff - 1;
84 
85     // This is the SDK API version of Honeycomb (3.0).
86     private static final int HONEYCOMB = 11;
87 
88     static final int MSG_REALLY_STOPPED = 1;
89     static final int MSG_RESUME_PENDING = 2;
90 
91     final Handler mHandler = new Handler() {
92         @Override
93         public void handleMessage(Message msg) {
94             switch (msg.what) {
95                 case MSG_REALLY_STOPPED:
96                     if (mStopped) {
97                         doReallyStop(false);
98                     }
99                     break;
100                 case MSG_RESUME_PENDING:
101                     onResumeFragments();
102                     mFragments.execPendingActions();
103                     break;
104                 default:
105                     super.handleMessage(msg);
106             }
107         }
108 
109     };
110     final FragmentController mFragments = FragmentController.createController(new HostCallbacks());
111 
112     boolean mCreated;
113     boolean mResumed;
114     boolean mStopped;
115     boolean mReallyStopped;
116     boolean mRetaining;
117 
118     boolean mOptionsMenuInvalidated;
119     boolean mRequestedPermissionsFromFragment;
120 
121     // A hint for the next candidate request index. Request indicies are ints between 0 and 2^16-1
122     // which are encoded into the upper 16 bits of the requestCode for
123     // Fragment.startActivityForResult(...) calls. This allows us to dispatch onActivityResult(...)
124     // to the appropriate Fragment. Request indicies are allocated by allocateRequestIndex(...).
125     int mNextCandidateRequestIndex;
126     // A map from request index to Fragment "who" (i.e. a Fragment's unique identifier). Used to
127     // keep track of the originating Fragment for Fragment.startActivityForResult(...) calls, so we
128     // can dispatch the onActivityResult(...) to the appropriate Fragment. Will only contain entries
129     // for startActivityForResult calls where a result has not yet been delivered.
130     SparseArrayCompat<String> mPendingFragmentActivityResults;
131 
132     static final class NonConfigurationInstances {
133         Object custom;
134         FragmentManagerNonConfig fragments;
135         SimpleArrayMap<String, LoaderManager> loaders;
136     }
137 
138     MediaControllerCompat mMediaController;
139 
140     // ------------------------------------------------------------------------
141     // HOOKS INTO ACTIVITY
142     // ------------------------------------------------------------------------
143 
144     /**
145      * Dispatch incoming result to the correct fragment.
146      */
147     @Override
onActivityResult(int requestCode, int resultCode, Intent data)148     protected void onActivityResult(int requestCode, int resultCode, Intent data) {
149         mFragments.noteStateNotSaved();
150         int requestIndex = requestCode>>16;
151         if (requestIndex != 0) {
152             requestIndex--;
153 
154             String who = mPendingFragmentActivityResults.get(requestIndex);
155             mPendingFragmentActivityResults.remove(requestIndex);
156             if (who == null) {
157                 Log.w(TAG, "Activity result delivered for unknown Fragment.");
158                 return;
159             }
160             Fragment targetFragment = mFragments.findFragmentByWho(who);
161             if (targetFragment == null) {
162                 Log.w(TAG, "Activity result no fragment exists for who: " + who);
163             } else {
164                 targetFragment.onActivityResult(requestCode & 0xffff, resultCode, data);
165             }
166             return;
167         }
168 
169         super.onActivityResult(requestCode, resultCode, data);
170     }
171 
172     /**
173      * Take care of popping the fragment back stack or finishing the activity
174      * as appropriate.
175      */
176     @Override
onBackPressed()177     public void onBackPressed() {
178         if (!mFragments.getSupportFragmentManager().popBackStackImmediate()) {
179             super.onBackPressed();
180         }
181     }
182 
183     /**
184      * Sets a {@link MediaControllerCompat} for later retrieval via
185      * {@link #getSupportMediaController()}.
186      *
187      * <p>On API 21 and later, this controller will be tied to the window of the activity and
188      * media key and volume events which are received while the Activity is in the foreground
189      * will be forwarded to the controller and used to invoke transport controls or adjust the
190      * volume. Prior to API 21, the global handling of media key and volume events through an
191      * active {@link android.support.v4.media.session.MediaSessionCompat} and media button receiver
192      * will still be respected.</p>
193      *
194      * @param mediaController The controller for the session which should receive
195      *     media keys and volume changes on API 21 and later.
196      * @see #getSupportMediaController()
197      * @see #setMediaController(android.media.session.MediaController)
198      */
setSupportMediaController(MediaControllerCompat mediaController)199     final public void setSupportMediaController(MediaControllerCompat mediaController) {
200         mMediaController = mediaController;
201         if (android.os.Build.VERSION.SDK_INT >= 21) {
202             ActivityCompatApi21.setMediaController(this, mediaController.getMediaController());
203         }
204     }
205 
206     /**
207      * Retrieves the current {@link MediaControllerCompat} for sending media key and volume events.
208      *
209      * @return The controller which should receive events.
210      * @see #setSupportMediaController(MediaControllerCompat)
211      * @see #getMediaController()
212      */
getSupportMediaController()213     final public MediaControllerCompat getSupportMediaController() {
214         return mMediaController;
215     }
216 
217     /**
218      * Reverses the Activity Scene entry Transition and triggers the calling Activity
219      * to reverse its exit Transition. When the exit Transition completes,
220      * {@link #finish()} is called. If no entry Transition was used, finish() is called
221      * immediately and the Activity exit Transition is run.
222      *
223      * <p>On Android 4.4 or lower, this method only finishes the Activity with no
224      * special exit transition.</p>
225      */
supportFinishAfterTransition()226     public void supportFinishAfterTransition() {
227         ActivityCompat.finishAfterTransition(this);
228     }
229 
230     /**
231      * When {@link android.app.ActivityOptions#makeSceneTransitionAnimation(Activity,
232      * android.view.View, String)} was used to start an Activity, <var>callback</var>
233      * will be called to handle shared elements on the <i>launched</i> Activity. This requires
234      * {@link Window#FEATURE_CONTENT_TRANSITIONS}.
235      *
236      * @param callback Used to manipulate shared element transitions on the launched Activity.
237      */
setEnterSharedElementCallback(SharedElementCallback callback)238     public void setEnterSharedElementCallback(SharedElementCallback callback) {
239         ActivityCompat.setEnterSharedElementCallback(this, callback);
240     }
241 
242     /**
243      * When {@link android.app.ActivityOptions#makeSceneTransitionAnimation(Activity,
244      * android.view.View, String)} was used to start an Activity, <var>listener</var>
245      * will be called to handle shared elements on the <i>launching</i> Activity. Most
246      * calls will only come when returning from the started Activity.
247      * This requires {@link Window#FEATURE_CONTENT_TRANSITIONS}.
248      *
249      * @param listener Used to manipulate shared element transitions on the launching Activity.
250      */
setExitSharedElementCallback(SharedElementCallback listener)251     public void setExitSharedElementCallback(SharedElementCallback listener) {
252         ActivityCompat.setExitSharedElementCallback(this, listener);
253     }
254 
255     /**
256      * Support library version of {@link android.app.Activity#postponeEnterTransition()} that works
257      * only on API 21 and later.
258      */
supportPostponeEnterTransition()259     public void supportPostponeEnterTransition() {
260         ActivityCompat.postponeEnterTransition(this);
261     }
262 
263     /**
264      * Support library version of {@link android.app.Activity#startPostponedEnterTransition()}
265      * that only works with API 21 and later.
266      */
supportStartPostponedEnterTransition()267     public void supportStartPostponedEnterTransition() {
268         ActivityCompat.startPostponedEnterTransition(this);
269     }
270 
271     /**
272      * {@inheritDoc}
273      *
274      * <p><strong>Note:</strong> If you override this method you must call
275      * <code>super.onMultiWindowModeChanged</code> to correctly dispatch the event
276      * to support fragments attached to this activity.</p>
277      *
278      * @param isInMultiWindowMode True if the activity is in multi-window mode.
279      */
280     @CallSuper
onMultiWindowModeChanged(boolean isInMultiWindowMode)281     public void onMultiWindowModeChanged(boolean isInMultiWindowMode) {
282         mFragments.dispatchMultiWindowModeChanged(isInMultiWindowMode);
283     }
284 
285     /**
286      * {@inheritDoc}
287      *
288      * <p><strong>Note:</strong> If you override this method you must call
289      * <code>super.onPictureInPictureModeChanged</code> to correctly dispatch the event
290      * to support fragments attached to this activity.</p>
291      *
292      * @param isInPictureInPictureMode True if the activity is in picture-in-picture mode.
293      */
294     @CallSuper
onPictureInPictureModeChanged(boolean isInPictureInPictureMode)295     public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
296         mFragments.dispatchPictureInPictureModeChanged(isInPictureInPictureMode);
297     }
298 
299     /**
300      * Dispatch configuration change to all fragments.
301      */
302     @Override
onConfigurationChanged(Configuration newConfig)303     public void onConfigurationChanged(Configuration newConfig) {
304         super.onConfigurationChanged(newConfig);
305         mFragments.dispatchConfigurationChanged(newConfig);
306     }
307 
308     /**
309      * Perform initialization of all fragments and loaders.
310      */
311     @SuppressWarnings("deprecation")
312     @Override
onCreate(@ullable Bundle savedInstanceState)313     protected void onCreate(@Nullable Bundle savedInstanceState) {
314         mFragments.attachHost(null /*parent*/);
315 
316         super.onCreate(savedInstanceState);
317 
318         NonConfigurationInstances nc =
319                 (NonConfigurationInstances) getLastNonConfigurationInstance();
320         if (nc != null) {
321             mFragments.restoreLoaderNonConfig(nc.loaders);
322         }
323         if (savedInstanceState != null) {
324             Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
325             mFragments.restoreAllState(p, nc != null ? nc.fragments : null);
326 
327             // Check if there are any pending onActivityResult calls to descendent Fragments.
328             if (savedInstanceState.containsKey(NEXT_CANDIDATE_REQUEST_INDEX_TAG)) {
329                 mNextCandidateRequestIndex =
330                         savedInstanceState.getInt(NEXT_CANDIDATE_REQUEST_INDEX_TAG);
331                 int[] requestCodes = savedInstanceState.getIntArray(ALLOCATED_REQUEST_INDICIES_TAG);
332                 String[] fragmentWhos = savedInstanceState.getStringArray(REQUEST_FRAGMENT_WHO_TAG);
333                 if (requestCodes == null || fragmentWhos == null ||
334                             requestCodes.length != fragmentWhos.length) {
335                     Log.w(TAG, "Invalid requestCode mapping in savedInstanceState.");
336                 } else {
337                     mPendingFragmentActivityResults = new SparseArrayCompat<>(requestCodes.length);
338                     for (int i = 0; i < requestCodes.length; i++) {
339                         mPendingFragmentActivityResults.put(requestCodes[i], fragmentWhos[i]);
340                     }
341                 }
342             }
343         }
344 
345         if (mPendingFragmentActivityResults == null) {
346             mPendingFragmentActivityResults = new SparseArrayCompat<>();
347             mNextCandidateRequestIndex = 0;
348         }
349 
350         mFragments.dispatchCreate();
351     }
352 
353     /**
354      * Dispatch to Fragment.onCreateOptionsMenu().
355      */
356     @Override
onCreatePanelMenu(int featureId, Menu menu)357     public boolean onCreatePanelMenu(int featureId, Menu menu) {
358         if (featureId == Window.FEATURE_OPTIONS_PANEL) {
359             boolean show = super.onCreatePanelMenu(featureId, menu);
360             show |= mFragments.dispatchCreateOptionsMenu(menu, getMenuInflater());
361             if (android.os.Build.VERSION.SDK_INT >= HONEYCOMB) {
362                 return show;
363             }
364             // Prior to Honeycomb, the framework can't invalidate the options
365             // menu, so we must always say we have one in case the app later
366             // invalidates it and needs to have it shown.
367             return true;
368         }
369         return super.onCreatePanelMenu(featureId, menu);
370     }
371 
372     @Override
dispatchFragmentsOnCreateView(View parent, String name, Context context, AttributeSet attrs)373     final View dispatchFragmentsOnCreateView(View parent, String name, Context context,
374             AttributeSet attrs) {
375         return mFragments.onCreateView(parent, name, context, attrs);
376     }
377 
378     /**
379      * Destroy all fragments and loaders.
380      */
381     @Override
onDestroy()382     protected void onDestroy() {
383         super.onDestroy();
384 
385         doReallyStop(false);
386 
387         mFragments.dispatchDestroy();
388         mFragments.doLoaderDestroy();
389     }
390 
391     /**
392      * Dispatch onLowMemory() to all fragments.
393      */
394     @Override
onLowMemory()395     public void onLowMemory() {
396         super.onLowMemory();
397         mFragments.dispatchLowMemory();
398     }
399 
400     /**
401      * Dispatch context and options menu to fragments.
402      */
403     @Override
onMenuItemSelected(int featureId, MenuItem item)404     public boolean onMenuItemSelected(int featureId, MenuItem item) {
405         if (super.onMenuItemSelected(featureId, item)) {
406             return true;
407         }
408 
409         switch (featureId) {
410             case Window.FEATURE_OPTIONS_PANEL:
411                 return mFragments.dispatchOptionsItemSelected(item);
412 
413             case Window.FEATURE_CONTEXT_MENU:
414                 return mFragments.dispatchContextItemSelected(item);
415 
416             default:
417                 return false;
418         }
419     }
420 
421     /**
422      * Call onOptionsMenuClosed() on fragments.
423      */
424     @Override
onPanelClosed(int featureId, Menu menu)425     public void onPanelClosed(int featureId, Menu menu) {
426         switch (featureId) {
427             case Window.FEATURE_OPTIONS_PANEL:
428                 mFragments.dispatchOptionsMenuClosed(menu);
429                 break;
430         }
431         super.onPanelClosed(featureId, menu);
432     }
433 
434     /**
435      * Dispatch onPause() to fragments.
436      */
437     @Override
onPause()438     protected void onPause() {
439         super.onPause();
440         mResumed = false;
441         if (mHandler.hasMessages(MSG_RESUME_PENDING)) {
442             mHandler.removeMessages(MSG_RESUME_PENDING);
443             onResumeFragments();
444         }
445         mFragments.dispatchPause();
446     }
447 
448     /**
449      * Handle onNewIntent() to inform the fragment manager that the
450      * state is not saved.  If you are handling new intents and may be
451      * making changes to the fragment state, you want to be sure to call
452      * through to the super-class here first.  Otherwise, if your state
453      * is saved but the activity is not stopped, you could get an
454      * onNewIntent() call which happens before onResume() and trying to
455      * perform fragment operations at that point will throw IllegalStateException
456      * because the fragment manager thinks the state is still saved.
457      */
458     @Override
onNewIntent(Intent intent)459     protected void onNewIntent(Intent intent) {
460         super.onNewIntent(intent);
461         mFragments.noteStateNotSaved();
462     }
463 
464     /**
465      * Hook in to note that fragment state is no longer saved.
466      */
onStateNotSaved()467     public void onStateNotSaved() {
468         mFragments.noteStateNotSaved();
469     }
470 
471     /**
472      * Dispatch onResume() to fragments.  Note that for better inter-operation
473      * with older versions of the platform, at the point of this call the
474      * fragments attached to the activity are <em>not</em> resumed.  This means
475      * that in some cases the previous state may still be saved, not allowing
476      * fragment transactions that modify the state.  To correctly interact
477      * with fragments in their proper state, you should instead override
478      * {@link #onResumeFragments()}.
479      */
480     @Override
onResume()481     protected void onResume() {
482         super.onResume();
483         mHandler.sendEmptyMessage(MSG_RESUME_PENDING);
484         mResumed = true;
485         mFragments.execPendingActions();
486     }
487 
488     /**
489      * Dispatch onResume() to fragments.
490      */
491     @Override
onPostResume()492     protected void onPostResume() {
493         super.onPostResume();
494         mHandler.removeMessages(MSG_RESUME_PENDING);
495         onResumeFragments();
496         mFragments.execPendingActions();
497     }
498 
499     /**
500      * This is the fragment-orientated version of {@link #onResume()} that you
501      * can override to perform operations in the Activity at the same point
502      * where its fragments are resumed.  Be sure to always call through to
503      * the super-class.
504      */
onResumeFragments()505     protected void onResumeFragments() {
506         mFragments.dispatchResume();
507     }
508 
509     /**
510      * Dispatch onPrepareOptionsMenu() to fragments.
511      */
512     @Override
onPreparePanel(int featureId, View view, Menu menu)513     public boolean onPreparePanel(int featureId, View view, Menu menu) {
514         if (featureId == Window.FEATURE_OPTIONS_PANEL && menu != null) {
515             if (mOptionsMenuInvalidated) {
516                 mOptionsMenuInvalidated = false;
517                 menu.clear();
518                 onCreatePanelMenu(featureId, menu);
519             }
520             boolean goforit = onPrepareOptionsPanel(view, menu);
521             goforit |= mFragments.dispatchPrepareOptionsMenu(menu);
522             return goforit;
523         }
524         return super.onPreparePanel(featureId, view, menu);
525     }
526 
527     /**
528      * @hide
529      */
onPrepareOptionsPanel(View view, Menu menu)530     protected boolean onPrepareOptionsPanel(View view, Menu menu) {
531         return super.onPreparePanel(Window.FEATURE_OPTIONS_PANEL, view, menu);
532     }
533 
534     /**
535      * Retain all appropriate fragment and loader state.  You can NOT
536      * override this yourself!  Use {@link #onRetainCustomNonConfigurationInstance()}
537      * if you want to retain your own state.
538      */
539     @Override
onRetainNonConfigurationInstance()540     public final Object onRetainNonConfigurationInstance() {
541         if (mStopped) {
542             doReallyStop(true);
543         }
544 
545         Object custom = onRetainCustomNonConfigurationInstance();
546 
547         FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();
548         SimpleArrayMap<String, LoaderManager> loaders = mFragments.retainLoaderNonConfig();
549 
550         if (fragments == null && loaders == null && custom == null) {
551             return null;
552         }
553 
554         NonConfigurationInstances nci = new NonConfigurationInstances();
555         nci.custom = custom;
556         nci.fragments = fragments;
557         nci.loaders = loaders;
558         return nci;
559     }
560 
561     /**
562      * Save all appropriate fragment state.
563      */
564     @Override
onSaveInstanceState(Bundle outState)565     protected void onSaveInstanceState(Bundle outState) {
566         super.onSaveInstanceState(outState);
567         Parcelable p = mFragments.saveAllState();
568         if (p != null) {
569             outState.putParcelable(FRAGMENTS_TAG, p);
570         }
571         if (mPendingFragmentActivityResults.size() > 0) {
572             outState.putInt(NEXT_CANDIDATE_REQUEST_INDEX_TAG, mNextCandidateRequestIndex);
573 
574             int[] requestCodes = new int[mPendingFragmentActivityResults.size()];
575             String[] fragmentWhos = new String[mPendingFragmentActivityResults.size()];
576             for (int i = 0; i < mPendingFragmentActivityResults.size(); i++) {
577                 requestCodes[i] = mPendingFragmentActivityResults.keyAt(i);
578                 fragmentWhos[i] = mPendingFragmentActivityResults.valueAt(i);
579             }
580             outState.putIntArray(ALLOCATED_REQUEST_INDICIES_TAG, requestCodes);
581             outState.putStringArray(REQUEST_FRAGMENT_WHO_TAG, fragmentWhos);
582         }
583     }
584 
585     /**
586      * Dispatch onStart() to all fragments.  Ensure any created loaders are
587      * now started.
588      */
589     @Override
onStart()590     protected void onStart() {
591         super.onStart();
592 
593         mStopped = false;
594         mReallyStopped = false;
595         mHandler.removeMessages(MSG_REALLY_STOPPED);
596 
597         if (!mCreated) {
598             mCreated = true;
599             mFragments.dispatchActivityCreated();
600         }
601 
602         mFragments.noteStateNotSaved();
603         mFragments.execPendingActions();
604 
605         mFragments.doLoaderStart();
606 
607         // NOTE: HC onStart goes here.
608 
609         mFragments.dispatchStart();
610         mFragments.reportLoaderStart();
611     }
612 
613     /**
614      * Dispatch onStop() to all fragments.  Ensure all loaders are stopped.
615      */
616     @Override
onStop()617     protected void onStop() {
618         super.onStop();
619 
620         mStopped = true;
621         mHandler.sendEmptyMessage(MSG_REALLY_STOPPED);
622 
623         mFragments.dispatchStop();
624     }
625 
626     // ------------------------------------------------------------------------
627     // NEW METHODS
628     // ------------------------------------------------------------------------
629 
630     /**
631      * Use this instead of {@link #onRetainNonConfigurationInstance()}.
632      * Retrieve later with {@link #getLastCustomNonConfigurationInstance()}.
633      */
onRetainCustomNonConfigurationInstance()634     public Object onRetainCustomNonConfigurationInstance() {
635         return null;
636     }
637 
638     /**
639      * Return the value previously returned from
640      * {@link #onRetainCustomNonConfigurationInstance()}.
641      */
642     @SuppressWarnings("deprecation")
getLastCustomNonConfigurationInstance()643     public Object getLastCustomNonConfigurationInstance() {
644         NonConfigurationInstances nc = (NonConfigurationInstances)
645                 getLastNonConfigurationInstance();
646         return nc != null ? nc.custom : null;
647     }
648 
649     /**
650      * Support library version of {@link Activity#invalidateOptionsMenu}.
651      *
652      * <p>Invalidate the activity's options menu. This will cause relevant presentations
653      * of the menu to fully update via calls to onCreateOptionsMenu and
654      * onPrepareOptionsMenu the next time the menu is requested.
655      */
supportInvalidateOptionsMenu()656     public void supportInvalidateOptionsMenu() {
657         if (android.os.Build.VERSION.SDK_INT >= HONEYCOMB) {
658             // If we are running on HC or greater, we can use the framework
659             // API to invalidate the options menu.
660             ActivityCompatHoneycomb.invalidateOptionsMenu(this);
661             return;
662         }
663 
664         // Whoops, older platform...  we'll use a hack, to manually rebuild
665         // the options menu the next time it is prepared.
666         mOptionsMenuInvalidated = true;
667     }
668 
669     /**
670      * Print the Activity's state into the given stream.  This gets invoked if
671      * you run "adb shell dumpsys activity <activity_component_name>".
672      *
673      * @param prefix Desired prefix to prepend at each line of output.
674      * @param fd The raw file descriptor that the dump is being sent to.
675      * @param writer The PrintWriter to which you should dump your state.  This will be
676      * closed for you after you return.
677      * @param args additional arguments to the dump request.
678      */
dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)679     public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
680         if (android.os.Build.VERSION.SDK_INT >= HONEYCOMB) {
681             // XXX This can only work if we can call the super-class impl. :/
682             //ActivityCompatHoneycomb.dump(this, prefix, fd, writer, args);
683         }
684         writer.print(prefix); writer.print("Local FragmentActivity ");
685                 writer.print(Integer.toHexString(System.identityHashCode(this)));
686                 writer.println(" State:");
687         String innerPrefix = prefix + "  ";
688         writer.print(innerPrefix); writer.print("mCreated=");
689                 writer.print(mCreated); writer.print("mResumed=");
690                 writer.print(mResumed); writer.print(" mStopped=");
691                 writer.print(mStopped); writer.print(" mReallyStopped=");
692                 writer.println(mReallyStopped);
693         mFragments.dumpLoaders(innerPrefix, fd, writer, args);
694         mFragments.getSupportFragmentManager().dump(prefix, fd, writer, args);
695         writer.print(prefix); writer.println("View Hierarchy:");
696         dumpViewHierarchy(prefix + "  ", writer, getWindow().getDecorView());
697     }
698 
viewToString(View view)699     private static String viewToString(View view) {
700         StringBuilder out = new StringBuilder(128);
701         out.append(view.getClass().getName());
702         out.append('{');
703         out.append(Integer.toHexString(System.identityHashCode(view)));
704         out.append(' ');
705         switch (view.getVisibility()) {
706             case View.VISIBLE: out.append('V'); break;
707             case View.INVISIBLE: out.append('I'); break;
708             case View.GONE: out.append('G'); break;
709             default: out.append('.'); break;
710         }
711         out.append(view.isFocusable() ? 'F' : '.');
712         out.append(view.isEnabled() ? 'E' : '.');
713         out.append(view.willNotDraw() ? '.' : 'D');
714         out.append(view.isHorizontalScrollBarEnabled()? 'H' : '.');
715         out.append(view.isVerticalScrollBarEnabled() ? 'V' : '.');
716         out.append(view.isClickable() ? 'C' : '.');
717         out.append(view.isLongClickable() ? 'L' : '.');
718         out.append(' ');
719         out.append(view.isFocused() ? 'F' : '.');
720         out.append(view.isSelected() ? 'S' : '.');
721         out.append(view.isPressed() ? 'P' : '.');
722         out.append(' ');
723         out.append(view.getLeft());
724         out.append(',');
725         out.append(view.getTop());
726         out.append('-');
727         out.append(view.getRight());
728         out.append(',');
729         out.append(view.getBottom());
730         final int id = view.getId();
731         if (id != View.NO_ID) {
732             out.append(" #");
733             out.append(Integer.toHexString(id));
734             final Resources r = view.getResources();
735             if (id != 0 && r != null) {
736                 try {
737                     String pkgname;
738                     switch (id&0xff000000) {
739                         case 0x7f000000:
740                             pkgname="app";
741                             break;
742                         case 0x01000000:
743                             pkgname="android";
744                             break;
745                         default:
746                             pkgname = r.getResourcePackageName(id);
747                             break;
748                     }
749                     String typename = r.getResourceTypeName(id);
750                     String entryname = r.getResourceEntryName(id);
751                     out.append(" ");
752                     out.append(pkgname);
753                     out.append(":");
754                     out.append(typename);
755                     out.append("/");
756                     out.append(entryname);
757                 } catch (Resources.NotFoundException e) {
758                 }
759             }
760         }
761         out.append("}");
762         return out.toString();
763     }
764 
dumpViewHierarchy(String prefix, PrintWriter writer, View view)765     private void dumpViewHierarchy(String prefix, PrintWriter writer, View view) {
766         writer.print(prefix);
767         if (view == null) {
768             writer.println("null");
769             return;
770         }
771         writer.println(viewToString(view));
772         if (!(view instanceof ViewGroup)) {
773             return;
774         }
775         ViewGroup grp = (ViewGroup)view;
776         final int N = grp.getChildCount();
777         if (N <= 0) {
778             return;
779         }
780         prefix = prefix + "  ";
781         for (int i=0; i<N; i++) {
782             dumpViewHierarchy(prefix, writer, grp.getChildAt(i));
783         }
784     }
785 
doReallyStop(boolean retaining)786     void doReallyStop(boolean retaining) {
787         if (!mReallyStopped) {
788             mReallyStopped = true;
789             mRetaining = retaining;
790             mHandler.removeMessages(MSG_REALLY_STOPPED);
791             onReallyStop();
792         } else if (retaining) {
793             // We're already really stopped, but we've been asked to retain.
794             // Our fragments are taken care of but we need to mark the loaders for retention.
795             // In order to do this correctly we need to restart the loaders first before
796             // handing them off to the next activity.
797             mFragments.doLoaderStart();
798             mFragments.doLoaderStop(true);
799         }
800     }
801 
802     /**
803      * Pre-HC, we didn't have a way to determine whether an activity was
804      * being stopped for a config change or not until we saw
805      * onRetainNonConfigurationInstance() called after onStop().  However
806      * we need to know this, to know whether to retain fragments.  This will
807      * tell us what we need to know.
808      */
onReallyStop()809     void onReallyStop() {
810         mFragments.doLoaderStop(mRetaining);
811 
812         mFragments.dispatchReallyStop();
813     }
814 
815     // ------------------------------------------------------------------------
816     // FRAGMENT SUPPORT
817     // ------------------------------------------------------------------------
818 
819     /**
820      * Called when a fragment is attached to the activity.
821      *
822      * <p>This is called after the attached fragment's <code>onAttach</code> and before
823      * the attached fragment's <code>onCreate</code> if the fragment has not yet had a previous
824      * call to <code>onCreate</code>.</p>
825      */
826     @SuppressWarnings("unused")
onAttachFragment(Fragment fragment)827     public void onAttachFragment(Fragment fragment) {
828     }
829 
830     /**
831      * Return the FragmentManager for interacting with fragments associated
832      * with this activity.
833      */
getSupportFragmentManager()834     public FragmentManager getSupportFragmentManager() {
835         return mFragments.getSupportFragmentManager();
836     }
837 
getSupportLoaderManager()838     public LoaderManager getSupportLoaderManager() {
839         return mFragments.getSupportLoaderManager();
840     }
841 
842     /**
843      * Modifies the standard behavior to allow results to be delivered to fragments.
844      * This imposes a restriction that requestCode be <= 0xffff.
845      */
846     @Override
startActivityForResult(Intent intent, int requestCode)847     public void startActivityForResult(Intent intent, int requestCode) {
848         // If this was started from a Fragment we've already checked the upper 16 bits were not in
849         // use, and then repurposed them for the Fragment's index.
850         if (!mStartedActivityFromFragment) {
851             if (requestCode != -1) {
852                 checkForValidRequestCode(requestCode);
853             }
854         }
855         super.startActivityForResult(intent, requestCode);
856     }
857 
858     @Override
validateRequestPermissionsRequestCode(int requestCode)859     public final void validateRequestPermissionsRequestCode(int requestCode) {
860         // We use 16 bits of the request code to encode the fragment id when
861         // requesting permissions from a fragment. Hence, requestPermissions()
862         // should validate the code against that but we cannot override it as
863         // we can not then call super and also the ActivityCompat would call
864         // back to this override. To handle this we use dependency inversion
865         // where we are the validator of request codes when requesting
866         // permissions in ActivityCompat.
867         if (!mRequestedPermissionsFromFragment
868                 && requestCode != -1) {
869             checkForValidRequestCode(requestCode);
870         }
871     }
872 
873     /**
874      * Callback for the result from requesting permissions. This method
875      * is invoked for every call on {@link #requestPermissions(String[], int)}.
876      * <p>
877      * <strong>Note:</strong> It is possible that the permissions request interaction
878      * with the user is interrupted. In this case you will receive empty permissions
879      * and results arrays which should be treated as a cancellation.
880      * </p>
881      *
882      * @param requestCode The request code passed in {@link #requestPermissions(String[], int)}.
883      * @param permissions The requested permissions. Never null.
884      * @param grantResults The grant results for the corresponding permissions
885      *     which is either {@link android.content.pm.PackageManager#PERMISSION_GRANTED}
886      *     or {@link android.content.pm.PackageManager#PERMISSION_DENIED}. Never null.
887      *
888      * @see #requestPermissions(String[], int)
889      */
890     @Override
onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults)891     public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
892             @NonNull int[] grantResults) {
893         int index = (requestCode >> 16) & 0xffff;
894         if (index != 0) {
895             index--;
896 
897             String who = mPendingFragmentActivityResults.get(index);
898             mPendingFragmentActivityResults.remove(index);
899             if (who == null) {
900                 Log.w(TAG, "Activity result delivered for unknown Fragment.");
901                 return;
902             }
903             Fragment frag = mFragments.findFragmentByWho(who);
904             if (frag == null) {
905                 Log.w(TAG, "Activity result no fragment exists for who: " + who);
906             } else {
907                 frag.onRequestPermissionsResult(requestCode & 0xffff, permissions, grantResults);
908             }
909         }
910     }
911 
912     /**
913      * Called by Fragment.startActivityForResult() to implement its behavior.
914      */
startActivityFromFragment(Fragment fragment, Intent intent, int requestCode)915     public void startActivityFromFragment(Fragment fragment, Intent intent,
916             int requestCode) {
917         startActivityFromFragment(fragment, intent, requestCode, null);
918     }
919 
920     /**
921      * Called by Fragment.startActivityForResult() to implement its behavior.
922      */
startActivityFromFragment(Fragment fragment, Intent intent, int requestCode, @Nullable Bundle options)923     public void startActivityFromFragment(Fragment fragment, Intent intent,
924             int requestCode, @Nullable Bundle options) {
925         mStartedActivityFromFragment = true;
926         try {
927             if (requestCode == -1) {
928                 ActivityCompat.startActivityForResult(this, intent, -1, options);
929                 return;
930             }
931             checkForValidRequestCode(requestCode);
932             int requestIndex = allocateRequestIndex(fragment);
933             ActivityCompat.startActivityForResult(
934                     this, intent, ((requestIndex + 1) << 16) + (requestCode & 0xffff), options);
935         } finally {
936             mStartedActivityFromFragment = false;
937         }
938     }
939 
940     /**
941      * Called by Fragment.startIntentSenderForResult() to implement its behavior.
942      */
startIntentSenderFromFragment(Fragment fragment, IntentSender intent, int requestCode, @Nullable Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, Bundle options)943     public void startIntentSenderFromFragment(Fragment fragment, IntentSender intent,
944             int requestCode, @Nullable Intent fillInIntent, int flagsMask, int flagsValues,
945             int extraFlags, Bundle options) throws IntentSender.SendIntentException {
946         mStartedIntentSenderFromFragment = true;
947         try {
948             if (requestCode == -1) {
949                 ActivityCompat.startIntentSenderForResult(this, intent, requestCode, fillInIntent,
950                         flagsMask, flagsValues, extraFlags, options);
951                 return;
952             }
953             checkForValidRequestCode(requestCode);
954             int requestIndex = allocateRequestIndex(fragment);
955             ActivityCompat.startIntentSenderForResult(this, intent,
956                     ((requestIndex + 1) << 16) + (requestCode & 0xffff), fillInIntent,
957                     flagsMask, flagsValues, extraFlags, options);
958         } finally {
959             mStartedIntentSenderFromFragment = false;
960         }
961     }
962 
963     // Allocates the next available startActivityForResult request index.
allocateRequestIndex(Fragment fragment)964     private int allocateRequestIndex(Fragment fragment) {
965         // Sanity check that we havn't exhaused the request index space.
966         if (mPendingFragmentActivityResults.size() >= MAX_NUM_PENDING_FRAGMENT_ACTIVITY_RESULTS) {
967             throw new IllegalStateException("Too many pending Fragment activity results.");
968         }
969 
970         // Find an unallocated request index in the mPendingFragmentActivityResults map.
971         while (mPendingFragmentActivityResults.indexOfKey(mNextCandidateRequestIndex) >= 0) {
972             mNextCandidateRequestIndex =
973                     (mNextCandidateRequestIndex + 1) % MAX_NUM_PENDING_FRAGMENT_ACTIVITY_RESULTS;
974         }
975 
976         int requestIndex = mNextCandidateRequestIndex;
977         mPendingFragmentActivityResults.put(requestIndex, fragment.mWho);
978         mNextCandidateRequestIndex =
979                 (mNextCandidateRequestIndex + 1) % MAX_NUM_PENDING_FRAGMENT_ACTIVITY_RESULTS;
980 
981         return requestIndex;
982     }
983 
984     /**
985      * Called by Fragment.requestPermissions() to implement its behavior.
986      */
requestPermissionsFromFragment(Fragment fragment, String[] permissions, int requestCode)987     private void requestPermissionsFromFragment(Fragment fragment, String[] permissions,
988             int requestCode) {
989         if (requestCode == -1) {
990             ActivityCompat.requestPermissions(this, permissions, requestCode);
991             return;
992         }
993         checkForValidRequestCode(requestCode);
994         try {
995             mRequestedPermissionsFromFragment = true;
996             int requestIndex = allocateRequestIndex(fragment);
997             ActivityCompat.requestPermissions(this, permissions,
998                     ((requestIndex + 1) << 16) + (requestCode & 0xffff));
999         } finally {
1000             mRequestedPermissionsFromFragment = false;
1001         }
1002     }
1003 
1004     class HostCallbacks extends FragmentHostCallback<FragmentActivity> {
HostCallbacks()1005         public HostCallbacks() {
1006             super(FragmentActivity.this /*fragmentActivity*/);
1007         }
1008 
1009         @Override
onDump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)1010         public void onDump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
1011             FragmentActivity.this.dump(prefix, fd, writer, args);
1012         }
1013 
1014         @Override
onShouldSaveFragmentState(Fragment fragment)1015         public boolean onShouldSaveFragmentState(Fragment fragment) {
1016             return !isFinishing();
1017         }
1018 
1019         @Override
onGetLayoutInflater()1020         public LayoutInflater onGetLayoutInflater() {
1021             return FragmentActivity.this.getLayoutInflater().cloneInContext(FragmentActivity.this);
1022         }
1023 
1024         @Override
onGetHost()1025         public FragmentActivity onGetHost() {
1026             return FragmentActivity.this;
1027         }
1028 
1029         @Override
onSupportInvalidateOptionsMenu()1030         public void onSupportInvalidateOptionsMenu() {
1031             FragmentActivity.this.supportInvalidateOptionsMenu();
1032         }
1033 
1034         @Override
onStartActivityFromFragment(Fragment fragment, Intent intent, int requestCode)1035         public void onStartActivityFromFragment(Fragment fragment, Intent intent, int requestCode) {
1036             FragmentActivity.this.startActivityFromFragment(fragment, intent, requestCode);
1037         }
1038 
1039         @Override
onStartActivityFromFragment( Fragment fragment, Intent intent, int requestCode, @Nullable Bundle options)1040         public void onStartActivityFromFragment(
1041                 Fragment fragment, Intent intent, int requestCode, @Nullable Bundle options) {
1042             FragmentActivity.this.startActivityFromFragment(fragment, intent, requestCode, options);
1043         }
1044 
1045         @Override
onStartIntentSenderFromFragment(Fragment fragment, IntentSender intent, int requestCode, @Nullable Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags, Bundle options)1046         public void onStartIntentSenderFromFragment(Fragment fragment, IntentSender intent,
1047                 int requestCode, @Nullable Intent fillInIntent, int flagsMask, int flagsValues,
1048                 int extraFlags, Bundle options) throws IntentSender.SendIntentException {
1049             FragmentActivity.this.startIntentSenderFromFragment(fragment, intent, requestCode,
1050                     fillInIntent, flagsMask, flagsValues, extraFlags, options);
1051         }
1052 
1053         @Override
onRequestPermissionsFromFragment(@onNull Fragment fragment, @NonNull String[] permissions, int requestCode)1054         public void onRequestPermissionsFromFragment(@NonNull Fragment fragment,
1055                 @NonNull String[] permissions, int requestCode) {
1056             FragmentActivity.this.requestPermissionsFromFragment(fragment, permissions,
1057                     requestCode);
1058         }
1059 
1060         @Override
onShouldShowRequestPermissionRationale(@onNull String permission)1061         public boolean onShouldShowRequestPermissionRationale(@NonNull String permission) {
1062             return ActivityCompat.shouldShowRequestPermissionRationale(
1063                     FragmentActivity.this, permission);
1064         }
1065 
1066         @Override
onHasWindowAnimations()1067         public boolean onHasWindowAnimations() {
1068             return getWindow() != null;
1069         }
1070 
1071         @Override
onGetWindowAnimations()1072         public int onGetWindowAnimations() {
1073             final Window w = getWindow();
1074             return (w == null) ? 0 : w.getAttributes().windowAnimations;
1075         }
1076 
1077         @Override
onAttachFragment(Fragment fragment)1078         public void onAttachFragment(Fragment fragment) {
1079             FragmentActivity.this.onAttachFragment(fragment);
1080         }
1081 
1082         @Nullable
1083         @Override
onFindViewById(int id)1084         public View onFindViewById(int id) {
1085             return FragmentActivity.this.findViewById(id);
1086         }
1087 
1088         @Override
onHasView()1089         public boolean onHasView() {
1090             final Window w = getWindow();
1091             return (w != null && w.peekDecorView() != null);
1092         }
1093     }
1094 }
1095