FragmentActivity.java revision 13fb2b96fa8464e7b8514c57e1ad5ea782b3a52c
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.res.Configuration; 23import android.content.res.Resources; 24import android.content.res.TypedArray; 25import android.os.Bundle; 26import android.os.Handler; 27import android.os.Message; 28import android.os.Parcelable; 29import android.support.v4.util.SparseArrayCompat; 30import android.util.AttributeSet; 31import android.util.Log; 32import android.view.KeyEvent; 33import android.view.Menu; 34import android.view.MenuItem; 35import android.view.View; 36import android.view.ViewGroup; 37import android.view.Window; 38 39import java.io.FileDescriptor; 40import java.io.PrintWriter; 41import java.util.ArrayList; 42import java.util.HashMap; 43 44/** 45 * Base class for activities that want to use the support-based 46 * {@link android.support.v4.app.Fragment} and 47 * {@link android.support.v4.content.Loader} APIs. 48 * 49 * <p>When using this class as opposed to new platform's built-in fragment 50 * and loader support, you must use the {@link #getSupportFragmentManager()} 51 * and {@link #getSupportLoaderManager()} methods respectively to access 52 * those features. 53 * 54 * <p>Known limitations:</p> 55 * <ul> 56 * <li> <p>When using the <fragment> tag, this implementation can not 57 * use the parent view's ID as the new fragment's ID. You must explicitly 58 * specify an ID (or tag) in the <fragment>.</p> 59 * <li> <p>Prior to Honeycomb (3.0), an activity's state was saved before pausing. 60 * Fragments are a significant amount of new state, and dynamic enough that one 61 * often wants them to change between pausing and stopping. These classes 62 * throw an exception if you try to change the fragment state after it has been 63 * saved, to avoid accidental loss of UI state. However this is too restrictive 64 * prior to Honeycomb, where the state is saved before pausing. To address this, 65 * when running on platforms prior to Honeycomb an exception will not be thrown 66 * if you change fragments between the state save and the activity being stopped. 67 * This means that in some cases if the activity is restored from its last saved 68 * state, this may be a snapshot slightly before what the user last saw.</p> 69 * </ul> 70 */ 71public class FragmentActivity extends Activity { 72 private static final String TAG = "FragmentActivity"; 73 74 private static final String FRAGMENTS_TAG = "android:support:fragments"; 75 76 // This is the SDK API version of Honeycomb (3.0). 77 private static final int HONEYCOMB = 11; 78 79 static final int MSG_REALLY_STOPPED = 1; 80 static final int MSG_RESUME_PENDING = 2; 81 82 final Handler mHandler = new Handler() { 83 @Override 84 public void handleMessage(Message msg) { 85 switch (msg.what) { 86 case MSG_REALLY_STOPPED: 87 if (mStopped) { 88 doReallyStop(false); 89 } 90 break; 91 case MSG_RESUME_PENDING: 92 onResumeFragments(); 93 mFragments.execPendingActions(); 94 break; 95 default: 96 super.handleMessage(msg); 97 } 98 } 99 100 }; 101 final FragmentManagerImpl mFragments = new FragmentManagerImpl(); 102 103 boolean mCreated; 104 boolean mResumed; 105 boolean mStopped; 106 boolean mReallyStopped; 107 boolean mRetaining; 108 109 boolean mOptionsMenuInvalidated; 110 111 boolean mCheckedForLoaderManager; 112 boolean mLoadersStarted; 113 SparseArrayCompat<LoaderManagerImpl> mAllLoaderManagers; 114 LoaderManagerImpl mLoaderManager; 115 116 static final class NonConfigurationInstances { 117 Object activity; 118 Object custom; 119 HashMap<String, Object> children; 120 ArrayList<Fragment> fragments; 121 SparseArrayCompat<LoaderManagerImpl> loaders; 122 } 123 124 static class FragmentTag { 125 public static final int[] Fragment = { 126 0x01010003, 0x010100d0, 0x010100d1 127 }; 128 public static final int Fragment_id = 1; 129 public static final int Fragment_name = 0; 130 public static final int Fragment_tag = 2; 131 } 132 133 // ------------------------------------------------------------------------ 134 // HOOKS INTO ACTIVITY 135 // ------------------------------------------------------------------------ 136 137 /** 138 * Dispatch incoming result to the correct fragment. 139 */ 140 @Override 141 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 142 int index = requestCode>>16; 143 if (index != 0) { 144 index--; 145 if (mFragments.mActive == null || index < 0 || index >= mFragments.mActive.size()) { 146 Log.w(TAG, "Activity result fragment index out of range: 0x" 147 + Integer.toHexString(requestCode)); 148 return; 149 } 150 Fragment frag = mFragments.mActive.get(index); 151 if (frag == null) { 152 Log.w(TAG, "Activity result no fragment exists for index: 0x" 153 + Integer.toHexString(requestCode)); 154 } else { 155 frag.onActivityResult(requestCode&0xffff, resultCode, data); 156 } 157 return; 158 } 159 160 super.onActivityResult(requestCode, resultCode, data); 161 } 162 163 /** 164 * Take care of popping the fragment back stack or finishing the activity 165 * as appropriate. 166 */ 167 public void onBackPressed() { 168 if (!mFragments.popBackStackImmediate()) { 169 finish(); 170 } 171 } 172 173 /** 174 * Dispatch configuration change to all fragments. 175 */ 176 @Override 177 public void onConfigurationChanged(Configuration newConfig) { 178 super.onConfigurationChanged(newConfig); 179 mFragments.dispatchConfigurationChanged(newConfig); 180 } 181 182 /** 183 * Perform initialization of all fragments and loaders. 184 */ 185 @Override 186 protected void onCreate(Bundle savedInstanceState) { 187 mFragments.attachActivity(this); 188 // Old versions of the platform didn't do this! 189 if (getLayoutInflater().getFactory() == null) { 190 getLayoutInflater().setFactory(this); 191 } 192 193 super.onCreate(savedInstanceState); 194 195 NonConfigurationInstances nc = (NonConfigurationInstances) 196 getLastNonConfigurationInstance(); 197 if (nc != null) { 198 mAllLoaderManagers = nc.loaders; 199 } 200 if (savedInstanceState != null) { 201 Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG); 202 mFragments.restoreAllState(p, nc != null ? nc.fragments : null); 203 } 204 mFragments.dispatchCreate(); 205 } 206 207 /** 208 * Dispatch to Fragment.onCreateOptionsMenu(). 209 */ 210 @Override 211 public boolean onCreatePanelMenu(int featureId, Menu menu) { 212 if (featureId == Window.FEATURE_OPTIONS_PANEL) { 213 boolean show = super.onCreatePanelMenu(featureId, menu); 214 show |= mFragments.dispatchCreateOptionsMenu(menu, getMenuInflater()); 215 if (android.os.Build.VERSION.SDK_INT >= HONEYCOMB) { 216 return show; 217 } 218 // Prior to Honeycomb, the framework can't invalidate the options 219 // menu, so we must always say we have one in case the app later 220 // invalidates it and needs to have it shown. 221 return true; 222 } 223 return super.onCreatePanelMenu(featureId, menu); 224 } 225 226 /** 227 * Add support for inflating the <fragment> tag. 228 */ 229 @Override 230 public View onCreateView(String name, Context context, AttributeSet attrs) { 231 if (!"fragment".equals(name)) { 232 return super.onCreateView(name, context, attrs); 233 } 234 235 String fname = attrs.getAttributeValue(null, "class"); 236 TypedArray a = context.obtainStyledAttributes(attrs, FragmentTag.Fragment); 237 if (fname == null) { 238 fname = a.getString(FragmentTag.Fragment_name); 239 } 240 int id = a.getResourceId(FragmentTag.Fragment_id, View.NO_ID); 241 String tag = a.getString(FragmentTag.Fragment_tag); 242 a.recycle(); 243 244 View parent = null; // NOTE: no way to get parent pre-Honeycomb. 245 int containerId = parent != null ? parent.getId() : 0; 246 if (containerId == View.NO_ID && id == View.NO_ID && tag == null) { 247 throw new IllegalArgumentException(attrs.getPositionDescription() 248 + ": Must specify unique android:id, android:tag, or have a parent with an id for " + fname); 249 } 250 251 // If we restored from a previous state, we may already have 252 // instantiated this fragment from the state and should use 253 // that instance instead of making a new one. 254 Fragment fragment = id != View.NO_ID ? mFragments.findFragmentById(id) : null; 255 if (fragment == null && tag != null) { 256 fragment = mFragments.findFragmentByTag(tag); 257 } 258 if (fragment == null && containerId != View.NO_ID) { 259 fragment = mFragments.findFragmentById(containerId); 260 } 261 262 if (FragmentManagerImpl.DEBUG) Log.v(TAG, "onCreateView: id=0x" 263 + Integer.toHexString(id) + " fname=" + fname 264 + " existing=" + fragment); 265 if (fragment == null) { 266 fragment = Fragment.instantiate(this, fname); 267 fragment.mFromLayout = true; 268 fragment.mFragmentId = id != 0 ? id : containerId; 269 fragment.mContainerId = containerId; 270 fragment.mTag = tag; 271 fragment.mInLayout = true; 272 fragment.mFragmentManager = mFragments; 273 fragment.onInflate(this, attrs, fragment.mSavedFragmentState); 274 mFragments.addFragment(fragment, true); 275 276 } else if (fragment.mInLayout) { 277 // A fragment already exists and it is not one we restored from 278 // previous state. 279 throw new IllegalArgumentException(attrs.getPositionDescription() 280 + ": Duplicate id 0x" + Integer.toHexString(id) 281 + ", tag " + tag + ", or parent id 0x" + Integer.toHexString(containerId) 282 + " with another fragment for " + fname); 283 } else { 284 // This fragment was retained from a previous instance; get it 285 // going now. 286 fragment.mInLayout = true; 287 // If this fragment is newly instantiated (either right now, or 288 // from last saved state), then give it the attributes to 289 // initialize itself. 290 if (!fragment.mRetaining) { 291 fragment.onInflate(this, attrs, fragment.mSavedFragmentState); 292 } 293 mFragments.moveToState(fragment); 294 } 295 296 if (fragment.mView == null) { 297 throw new IllegalStateException("Fragment " + fname 298 + " did not create a view."); 299 } 300 if (id != 0) { 301 fragment.mView.setId(id); 302 } 303 if (fragment.mView.getTag() == null) { 304 fragment.mView.setTag(tag); 305 } 306 return fragment.mView; 307 } 308 309 /** 310 * Destroy all fragments and loaders. 311 */ 312 @Override 313 protected void onDestroy() { 314 super.onDestroy(); 315 316 doReallyStop(false); 317 318 mFragments.dispatchDestroy(); 319 if (mLoaderManager != null) { 320 mLoaderManager.doDestroy(); 321 } 322 } 323 324 /** 325 * Take care of calling onBackPressed() for pre-Eclair platforms. 326 */ 327 @Override 328 public boolean onKeyDown(int keyCode, KeyEvent event) { 329 if (android.os.Build.VERSION.SDK_INT < 5 /* ECLAIR */ 330 && keyCode == KeyEvent.KEYCODE_BACK 331 && event.getRepeatCount() == 0) { 332 // Take care of calling this method on earlier versions of 333 // the platform where it doesn't exist. 334 onBackPressed(); 335 return true; 336 } 337 338 return super.onKeyDown(keyCode, event); 339 } 340 341 /** 342 * Dispatch onLowMemory() to all fragments. 343 */ 344 @Override 345 public void onLowMemory() { 346 super.onLowMemory(); 347 mFragments.dispatchLowMemory(); 348 } 349 350 /** 351 * Dispatch context and options menu to fragments. 352 */ 353 @Override 354 public boolean onMenuItemSelected(int featureId, MenuItem item) { 355 if (super.onMenuItemSelected(featureId, item)) { 356 return true; 357 } 358 359 switch (featureId) { 360 case Window.FEATURE_OPTIONS_PANEL: 361 return mFragments.dispatchOptionsItemSelected(item); 362 363 case Window.FEATURE_CONTEXT_MENU: 364 return mFragments.dispatchContextItemSelected(item); 365 366 default: 367 return false; 368 } 369 } 370 371 /** 372 * Call onOptionsMenuClosed() on fragments. 373 */ 374 @Override 375 public void onPanelClosed(int featureId, Menu menu) { 376 switch (featureId) { 377 case Window.FEATURE_OPTIONS_PANEL: 378 mFragments.dispatchOptionsMenuClosed(menu); 379 break; 380 } 381 super.onPanelClosed(featureId, menu); 382 } 383 384 /** 385 * Dispatch onPause() to fragments. 386 */ 387 @Override 388 protected void onPause() { 389 super.onPause(); 390 mResumed = false; 391 if (mHandler.hasMessages(MSG_RESUME_PENDING)) { 392 mHandler.removeMessages(MSG_RESUME_PENDING); 393 onResumeFragments(); 394 } 395 mFragments.dispatchPause(); 396 } 397 398 /** 399 * Dispatch onResume() to fragments. Note that for better inter-operation 400 * with older versions of the platform, at the point of this call the 401 * fragments attached to the activity are <em>not</em> resumed. This means 402 * that in some cases the previous state may still be saved, not allowing 403 * fragment transactions that modify the state. To correctly interact 404 * with fragments in their proper state, you should instead override 405 * {@link #onResumeFragments()}. 406 */ 407 @Override 408 protected void onResume() { 409 super.onResume(); 410 mHandler.sendEmptyMessage(MSG_RESUME_PENDING); 411 mResumed = true; 412 mFragments.execPendingActions(); 413 } 414 415 /** 416 * Dispatch onResume() to fragments. 417 */ 418 @Override 419 protected void onPostResume() { 420 super.onPostResume(); 421 mHandler.removeMessages(MSG_RESUME_PENDING); 422 onResumeFragments(); 423 mFragments.execPendingActions(); 424 } 425 426 /** 427 * This is the fragment-orientated version of {@link #onResume()} that you 428 * can override to perform operations in the Activity at the same point 429 * where its fragments are resumed. Be sure to always call through to 430 * the super-class. 431 */ 432 protected void onResumeFragments() { 433 mFragments.dispatchResume(); 434 } 435 436 /** 437 * Dispatch onPrepareOptionsMenu() to fragments. 438 */ 439 @Override 440 public boolean onPreparePanel(int featureId, View view, Menu menu) { 441 if (featureId == Window.FEATURE_OPTIONS_PANEL && menu != null) { 442 if (mOptionsMenuInvalidated) { 443 mOptionsMenuInvalidated = false; 444 menu.clear(); 445 onCreatePanelMenu(featureId, menu); 446 } 447 boolean goforit = super.onPreparePanel(featureId, view, menu); 448 goforit |= mFragments.dispatchPrepareOptionsMenu(menu); 449 return goforit && menu.hasVisibleItems(); 450 } 451 return super.onPreparePanel(featureId, view, menu); 452 } 453 454 /** 455 * Retain all appropriate fragment and loader state. You can NOT 456 * override this yourself! Use {@link #onRetainCustomNonConfigurationInstance()} 457 * if you want to retain your own state. 458 */ 459 @Override 460 public final Object onRetainNonConfigurationInstance() { 461 if (mStopped) { 462 doReallyStop(true); 463 } 464 465 Object custom = onRetainCustomNonConfigurationInstance(); 466 467 ArrayList<Fragment> fragments = mFragments.retainNonConfig(); 468 boolean retainLoaders = false; 469 if (mAllLoaderManagers != null) { 470 // prune out any loader managers that were already stopped and so 471 // have nothing useful to retain. 472 for (int i=mAllLoaderManagers.size()-1; i>=0; i--) { 473 LoaderManagerImpl lm = mAllLoaderManagers.valueAt(i); 474 if (lm.mRetaining) { 475 retainLoaders = true; 476 } else { 477 lm.doDestroy(); 478 mAllLoaderManagers.removeAt(i); 479 } 480 } 481 } 482 if (fragments == null && !retainLoaders && custom == null) { 483 return null; 484 } 485 486 NonConfigurationInstances nci = new NonConfigurationInstances(); 487 nci.activity = null; 488 nci.custom = custom; 489 nci.children = null; 490 nci.fragments = fragments; 491 nci.loaders = mAllLoaderManagers; 492 return nci; 493 } 494 495 /** 496 * Save all appropriate fragment state. 497 */ 498 @Override 499 protected void onSaveInstanceState(Bundle outState) { 500 super.onSaveInstanceState(outState); 501 Parcelable p = mFragments.saveAllState(); 502 if (p != null) { 503 outState.putParcelable(FRAGMENTS_TAG, p); 504 } 505 } 506 507 /** 508 * Dispatch onStart() to all fragments. Ensure any created loaders are 509 * now started. 510 */ 511 @Override 512 protected void onStart() { 513 super.onStart(); 514 515 mStopped = false; 516 mReallyStopped = false; 517 mHandler.removeMessages(MSG_REALLY_STOPPED); 518 519 if (!mCreated) { 520 mCreated = true; 521 mFragments.dispatchActivityCreated(); 522 } 523 524 mFragments.noteStateNotSaved(); 525 mFragments.execPendingActions(); 526 527 if (!mLoadersStarted) { 528 mLoadersStarted = true; 529 if (mLoaderManager != null) { 530 mLoaderManager.doStart(); 531 } else if (!mCheckedForLoaderManager) { 532 mLoaderManager = getLoaderManager(-1, mLoadersStarted, false); 533 // the returned loader manager may be a new one, so we have to start it 534 if ((mLoaderManager != null) && (!mLoaderManager.mStarted)) { 535 mLoaderManager.doStart(); 536 } 537 } 538 mCheckedForLoaderManager = true; 539 } 540 // NOTE: HC onStart goes here. 541 542 mFragments.dispatchStart(); 543 if (mAllLoaderManagers != null) { 544 for (int i=mAllLoaderManagers.size()-1; i>=0; i--) { 545 LoaderManagerImpl lm = mAllLoaderManagers.valueAt(i); 546 lm.finishRetain(); 547 lm.doReportStart(); 548 } 549 } 550 } 551 552 /** 553 * Dispatch onStop() to all fragments. Ensure all loaders are stopped. 554 */ 555 @Override 556 protected void onStop() { 557 super.onStop(); 558 559 mStopped = true; 560 mHandler.sendEmptyMessage(MSG_REALLY_STOPPED); 561 562 mFragments.dispatchStop(); 563 } 564 565 // ------------------------------------------------------------------------ 566 // NEW METHODS 567 // ------------------------------------------------------------------------ 568 569 /** 570 * Use this instead of {@link #onRetainNonConfigurationInstance()}. 571 * Retrieve later with {@link #getLastCustomNonConfigurationInstance()}. 572 */ 573 public Object onRetainCustomNonConfigurationInstance() { 574 return null; 575 } 576 577 /** 578 * Return the value previously returned from 579 * {@link #onRetainCustomNonConfigurationInstance()}. 580 */ 581 public Object getLastCustomNonConfigurationInstance() { 582 NonConfigurationInstances nc = (NonConfigurationInstances) 583 getLastNonConfigurationInstance(); 584 return nc != null ? nc.custom : null; 585 } 586 587 /** 588 * Invalidate the activity's options menu. This will cause relevant presentations 589 * of the menu to fully update via calls to onCreateOptionsMenu and 590 * onPrepareOptionsMenu the next time the menu is requested. 591 */ 592 public void supportInvalidateOptionsMenu() { 593 if (android.os.Build.VERSION.SDK_INT >= HONEYCOMB) { 594 // If we are running on HC or greater, we can use the framework 595 // API to invalidate the options menu. 596 ActivityCompatHoneycomb.invalidateOptionsMenu(this); 597 return; 598 } 599 600 // Whoops, older platform... we'll use a hack, to manually rebuild 601 // the options menu the next time it is prepared. 602 mOptionsMenuInvalidated = true; 603 } 604 605 /** 606 * Print the Activity's state into the given stream. This gets invoked if 607 * you run "adb shell dumpsys activity <activity_component_name>". 608 * 609 * @param prefix Desired prefix to prepend at each line of output. 610 * @param fd The raw file descriptor that the dump is being sent to. 611 * @param writer The PrintWriter to which you should dump your state. This will be 612 * closed for you after you return. 613 * @param args additional arguments to the dump request. 614 */ 615 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 616 if (android.os.Build.VERSION.SDK_INT >= HONEYCOMB) { 617 // XXX This can only work if we can call the super-class impl. :/ 618 //ActivityCompatHoneycomb.dump(this, prefix, fd, writer, args); 619 } 620 writer.print(prefix); writer.print("Local FragmentActivity "); 621 writer.print(Integer.toHexString(System.identityHashCode(this))); 622 writer.println(" State:"); 623 String innerPrefix = prefix + " "; 624 writer.print(innerPrefix); writer.print("mCreated="); 625 writer.print(mCreated); writer.print("mResumed="); 626 writer.print(mResumed); writer.print(" mStopped="); 627 writer.print(mStopped); writer.print(" mReallyStopped="); 628 writer.println(mReallyStopped); 629 writer.print(innerPrefix); writer.print("mLoadersStarted="); 630 writer.println(mLoadersStarted); 631 if (mLoaderManager != null) { 632 writer.print(prefix); writer.print("Loader Manager "); 633 writer.print(Integer.toHexString(System.identityHashCode(mLoaderManager))); 634 writer.println(":"); 635 mLoaderManager.dump(prefix + " ", fd, writer, args); 636 } 637 mFragments.dump(prefix, fd, writer, args); 638 writer.print(prefix); writer.println("View Hierarchy:"); 639 dumpViewHierarchy(prefix + " ", writer, getWindow().getDecorView()); 640 } 641 642 private static String viewToString(View view) { 643 StringBuilder out = new StringBuilder(128); 644 out.append(view.getClass().getName()); 645 out.append('{'); 646 out.append(Integer.toHexString(System.identityHashCode(view))); 647 out.append(' '); 648 switch (view.getVisibility()) { 649 case View.VISIBLE: out.append('V'); break; 650 case View.INVISIBLE: out.append('I'); break; 651 case View.GONE: out.append('G'); break; 652 default: out.append('.'); break; 653 } 654 out.append(view.isFocusable() ? 'F' : '.'); 655 out.append(view.isEnabled() ? 'E' : '.'); 656 out.append(view.willNotDraw() ? '.' : 'D'); 657 out.append(view.isHorizontalScrollBarEnabled()? 'H' : '.'); 658 out.append(view.isVerticalScrollBarEnabled() ? 'V' : '.'); 659 out.append(view.isClickable() ? 'C' : '.'); 660 out.append(view.isLongClickable() ? 'L' : '.'); 661 out.append(' '); 662 out.append(view.isFocused() ? 'F' : '.'); 663 out.append(view.isSelected() ? 'S' : '.'); 664 out.append(view.isPressed() ? 'P' : '.'); 665 out.append(' '); 666 out.append(view.getLeft()); 667 out.append(','); 668 out.append(view.getTop()); 669 out.append('-'); 670 out.append(view.getRight()); 671 out.append(','); 672 out.append(view.getBottom()); 673 final int id = view.getId(); 674 if (id != View.NO_ID) { 675 out.append(" #"); 676 out.append(Integer.toHexString(id)); 677 final Resources r = view.getResources(); 678 if (id != 0 && r != null) { 679 try { 680 String pkgname; 681 switch (id&0xff000000) { 682 case 0x7f000000: 683 pkgname="app"; 684 break; 685 case 0x01000000: 686 pkgname="android"; 687 break; 688 default: 689 pkgname = r.getResourcePackageName(id); 690 break; 691 } 692 String typename = r.getResourceTypeName(id); 693 String entryname = r.getResourceEntryName(id); 694 out.append(" "); 695 out.append(pkgname); 696 out.append(":"); 697 out.append(typename); 698 out.append("/"); 699 out.append(entryname); 700 } catch (Resources.NotFoundException e) { 701 } 702 } 703 } 704 out.append("}"); 705 return out.toString(); 706 } 707 708 private void dumpViewHierarchy(String prefix, PrintWriter writer, View view) { 709 writer.print(prefix); 710 if (view == null) { 711 writer.println("null"); 712 return; 713 } 714 writer.println(viewToString(view)); 715 if (!(view instanceof ViewGroup)) { 716 return; 717 } 718 ViewGroup grp = (ViewGroup)view; 719 final int N = grp.getChildCount(); 720 if (N <= 0) { 721 return; 722 } 723 prefix = prefix + " "; 724 for (int i=0; i<N; i++) { 725 dumpViewHierarchy(prefix, writer, grp.getChildAt(i)); 726 } 727 } 728 729 void doReallyStop(boolean retaining) { 730 if (!mReallyStopped) { 731 mReallyStopped = true; 732 mRetaining = retaining; 733 mHandler.removeMessages(MSG_REALLY_STOPPED); 734 onReallyStop(); 735 } 736 } 737 738 /** 739 * Pre-HC, we didn't have a way to determine whether an activity was 740 * being stopped for a config change or not until we saw 741 * onRetainNonConfigurationInstance() called after onStop(). However 742 * we need to know this, to know whether to retain fragments. This will 743 * tell us what we need to know. 744 */ 745 void onReallyStop() { 746 if (mLoadersStarted) { 747 mLoadersStarted = false; 748 if (mLoaderManager != null) { 749 if (!mRetaining) { 750 mLoaderManager.doStop(); 751 } else { 752 mLoaderManager.doRetain(); 753 } 754 } 755 } 756 757 mFragments.dispatchReallyStop(); 758 } 759 760 // ------------------------------------------------------------------------ 761 // FRAGMENT SUPPORT 762 // ------------------------------------------------------------------------ 763 764 /** 765 * Called when a fragment is attached to the activity. 766 */ 767 public void onAttachFragment(Fragment fragment) { 768 } 769 770 /** 771 * Return the FragmentManager for interacting with fragments associated 772 * with this activity. 773 */ 774 public FragmentManager getSupportFragmentManager() { 775 return mFragments; 776 } 777 778 /** 779 * Modifies the standard behavior to allow results to be delivered to fragments. 780 * This imposes a restriction that requestCode be <= 0xffff. 781 */ 782 @Override 783 public void startActivityForResult(Intent intent, int requestCode) { 784 if (requestCode != -1 && (requestCode&0xffff0000) != 0) { 785 throw new IllegalArgumentException("Can only use lower 16 bits for requestCode"); 786 } 787 super.startActivityForResult(intent, requestCode); 788 } 789 790 /** 791 * Called by Fragment.startActivityForResult() to implement its behavior. 792 */ 793 public void startActivityFromFragment(Fragment fragment, Intent intent, 794 int requestCode) { 795 if (requestCode == -1) { 796 super.startActivityForResult(intent, -1); 797 return; 798 } 799 if ((requestCode&0xffff0000) != 0) { 800 throw new IllegalArgumentException("Can only use lower 16 bits for requestCode"); 801 } 802 super.startActivityForResult(intent, ((fragment.mIndex+1)<<16) + (requestCode&0xffff)); 803 } 804 805 void invalidateSupportFragmentIndex(int index) { 806 //Log.v(TAG, "invalidateFragmentIndex: index=" + index); 807 if (mAllLoaderManagers != null) { 808 LoaderManagerImpl lm = mAllLoaderManagers.get(index); 809 if (lm != null && !lm.mRetaining) { 810 lm.doDestroy(); 811 mAllLoaderManagers.remove(index); 812 } 813 } 814 } 815 816 // ------------------------------------------------------------------------ 817 // LOADER SUPPORT 818 // ------------------------------------------------------------------------ 819 820 /** 821 * Return the LoaderManager for this fragment, creating it if needed. 822 */ 823 public LoaderManager getSupportLoaderManager() { 824 if (mLoaderManager != null) { 825 return mLoaderManager; 826 } 827 mCheckedForLoaderManager = true; 828 mLoaderManager = getLoaderManager(-1, mLoadersStarted, true); 829 return mLoaderManager; 830 } 831 832 LoaderManagerImpl getLoaderManager(int index, boolean started, boolean create) { 833 if (mAllLoaderManagers == null) { 834 mAllLoaderManagers = new SparseArrayCompat<LoaderManagerImpl>(); 835 } 836 LoaderManagerImpl lm = mAllLoaderManagers.get(index); 837 if (lm == null) { 838 if (create) { 839 lm = new LoaderManagerImpl(this, started); 840 mAllLoaderManagers.put(index, lm); 841 } 842 } else { 843 lm.updateActivity(this); 844 } 845 return lm; 846 } 847} 848