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