[go: nahoru, domu]

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