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