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