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