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