FragmentManager.java revision 3e449ce00ed2d3b271e50bc7a52798f630973bf1
1/* 2 * Copyright (C) 2010 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.app; 18 19import android.animation.Animator; 20import android.animation.AnimatorInflater; 21import android.animation.AnimatorListenerAdapter; 22import android.content.res.TypedArray; 23import android.os.Bundle; 24import android.os.Handler; 25import android.os.Parcel; 26import android.os.Parcelable; 27import android.util.Log; 28import android.util.SparseArray; 29import android.view.Menu; 30import android.view.MenuInflater; 31import android.view.MenuItem; 32import android.view.View; 33import android.view.ViewGroup; 34 35import java.util.ArrayList; 36 37/** 38 * Interface for interacting with {@link Fragment} objects inside of an 39 * {@link Activity} 40 */ 41public interface FragmentManager { 42 /** 43 * Start a series of edit operations on the Fragments associated with 44 * this FragmentManager. 45 */ 46 public FragmentTransaction openTransaction(); 47 48 /** 49 * Finds a fragment that was identified by the given id either when inflated 50 * from XML or as the container ID when added in a transaction. This first 51 * searches through fragments that are currently added to the manager's 52 * activity; if no such fragment is found, then all fragments currently 53 * on the back stack associated with this ID are searched. 54 * @return The fragment if found or null otherwise. 55 */ 56 public Fragment findFragmentById(int id); 57 58 /** 59 * Finds a fragment that was identified by the given tag either when inflated 60 * from XML or as supplied when added in a transaction. This first 61 * searches through fragments that are currently added to the manager's 62 * activity; if no such fragment is found, then all fragments currently 63 * on the back stack are searched. 64 * @return The fragment if found or null otherwise. 65 */ 66 public Fragment findFragmentByTag(String tag); 67 68 /** 69 * Flag for {@link #popBackStack(String, int)} 70 * and {@link #popBackStack(int, int)}: If set, and the name or ID of 71 * a back stack entry has been supplied, then all matching entries will 72 * be consumed until one that doesn't match is found or the bottom of 73 * the stack is reached. Otherwise, all entries up to but not including that entry 74 * will be removed. 75 */ 76 public static final int POP_BACK_STACK_INCLUSIVE = 1<<0; 77 78 /** 79 * Pop the top state off the back stack. Returns true if there was one 80 * to pop, else false. 81 */ 82 public boolean popBackStack(); 83 84 /** 85 * Pop the last fragment transition from the manager's fragment 86 * back stack. If there is nothing to pop, false is returned. 87 * @param name If non-null, this is the name of a previous back state 88 * to look for; if found, all states up to that state will be popped. The 89 * {@link #POP_BACK_STACK_INCLUSIVE} flag can be used to control whether 90 * the named state itself is popped. If null, only the top state is popped. 91 * @param flags Either 0 or {@link #POP_BACK_STACK_INCLUSIVE}. 92 */ 93 public boolean popBackStack(String name, int flags); 94 95 /** 96 * Pop all back stack states up to the one with the given identifier. 97 * @param id Identifier of the stated to be popped. If no identifier exists, 98 * false is returned. 99 * The identifier is the number returned by 100 * {@link FragmentTransaction#commit() FragmentTransaction.commit()}. The 101 * {@link #POP_BACK_STACK_INCLUSIVE} flag can be used to control whether 102 * the named state itself is popped. 103 * @param flags Either 0 or {@link #POP_BACK_STACK_INCLUSIVE}. 104 */ 105 public boolean popBackStack(int id, int flags); 106 107 /** 108 * Put a reference to a fragment in a Bundle. This Bundle can be 109 * persisted as saved state, and when later restoring 110 * {@link #getFragment(Bundle, String)} will return the current 111 * instance of the same fragment. 112 * 113 * @param bundle The bundle in which to put the fragment reference. 114 * @param key The name of the entry in the bundle. 115 * @param fragment The Fragment whose reference is to be stored. 116 */ 117 public void putFragment(Bundle bundle, String key, Fragment fragment); 118 119 /** 120 * Retrieve the current Fragment instance for a reference previously 121 * placed with {@link #putFragment(Bundle, String, Fragment)}. 122 * 123 * @param bundle The bundle from which to retrieve the fragment reference. 124 * @param key The name of the entry in the bundle. 125 * @return Returns the current Fragment instance that is associated with 126 * the given reference. 127 */ 128 public Fragment getFragment(Bundle bundle, String key); 129} 130 131final class FragmentManagerState implements Parcelable { 132 FragmentState[] mActive; 133 int[] mAdded; 134 BackStackState[] mBackStack; 135 136 public FragmentManagerState() { 137 } 138 139 public FragmentManagerState(Parcel in) { 140 mActive = in.createTypedArray(FragmentState.CREATOR); 141 mAdded = in.createIntArray(); 142 mBackStack = in.createTypedArray(BackStackState.CREATOR); 143 } 144 145 public int describeContents() { 146 return 0; 147 } 148 149 public void writeToParcel(Parcel dest, int flags) { 150 dest.writeTypedArray(mActive, flags); 151 dest.writeIntArray(mAdded); 152 dest.writeTypedArray(mBackStack, flags); 153 } 154 155 public static final Parcelable.Creator<FragmentManagerState> CREATOR 156 = new Parcelable.Creator<FragmentManagerState>() { 157 public FragmentManagerState createFromParcel(Parcel in) { 158 return new FragmentManagerState(in); 159 } 160 161 public FragmentManagerState[] newArray(int size) { 162 return new FragmentManagerState[size]; 163 } 164 }; 165} 166 167/** 168 * Container for fragments associated with an activity. 169 */ 170final class FragmentManagerImpl implements FragmentManager { 171 static final boolean DEBUG = true; 172 static final String TAG = "FragmentManager"; 173 174 static final String TARGET_REQUEST_CODE_STATE_TAG = "android:target_req_state"; 175 static final String TARGET_STATE_TAG = "android:target_state"; 176 static final String VIEW_STATE_TAG = "android:view_state"; 177 178 ArrayList<Runnable> mPendingActions; 179 Runnable[] mTmpActions; 180 boolean mExecutingActions; 181 182 ArrayList<Fragment> mActive; 183 ArrayList<Fragment> mAdded; 184 ArrayList<Integer> mAvailIndices; 185 ArrayList<BackStackEntry> mBackStack; 186 187 // Must be accessed while locked. 188 ArrayList<BackStackEntry> mBackStackIndices; 189 ArrayList<Integer> mAvailBackStackIndices; 190 191 int mCurState = Fragment.INITIALIZING; 192 Activity mActivity; 193 194 boolean mNeedMenuInvalidate; 195 boolean mStateSaved; 196 197 // Temporary vars for state save and restore. 198 Bundle mStateBundle = null; 199 SparseArray<Parcelable> mStateArray = null; 200 201 Runnable mExecCommit = new Runnable() { 202 @Override 203 public void run() { 204 execPendingActions(); 205 } 206 }; 207 public FragmentTransaction openTransaction() { 208 return new BackStackEntry(this); 209 } 210 211 public boolean popBackStack() { 212 return popBackStackState(mActivity.mHandler, null, -1, 0); 213 } 214 215 public boolean popBackStack(String name, int flags) { 216 return popBackStackState(mActivity.mHandler, name, -1, flags); 217 } 218 219 public boolean popBackStack(int id, int flags) { 220 if (id < 0) { 221 throw new IllegalArgumentException("Bad id: " + id); 222 } 223 return popBackStackState(mActivity.mHandler, null, id, flags); 224 } 225 226 public void putFragment(Bundle bundle, String key, Fragment fragment) { 227 if (fragment.mIndex < 0) { 228 throw new IllegalStateException("Fragment " + fragment 229 + " is not currently in the FragmentManager"); 230 } 231 bundle.putInt(key, fragment.mIndex); 232 } 233 234 public Fragment getFragment(Bundle bundle, String key) { 235 int index = bundle.getInt(key, -1); 236 if (index == -1) { 237 return null; 238 } 239 if (index >= mActive.size()) { 240 throw new IllegalStateException("Fragement no longer exists for key " 241 + key + ": index " + index); 242 } 243 Fragment f = mActive.get(index); 244 if (f == null) { 245 throw new IllegalStateException("Fragement no longer exists for key " 246 + key + ": index " + index); 247 } 248 return f; 249 } 250 251 Animator loadAnimator(Fragment fragment, int transit, boolean enter, 252 int transitionStyle) { 253 Animator animObj = fragment.onCreateAnimator(transit, enter, 254 fragment.mNextAnim); 255 if (animObj != null) { 256 return animObj; 257 } 258 259 if (fragment.mNextAnim != 0) { 260 Animator anim = AnimatorInflater.loadAnimator(mActivity, fragment.mNextAnim); 261 if (anim != null) { 262 return anim; 263 } 264 } 265 266 if (transit == 0) { 267 return null; 268 } 269 270 int styleIndex = transitToStyleIndex(transit, enter); 271 if (styleIndex < 0) { 272 return null; 273 } 274 275 if (transitionStyle == 0 && mActivity.getWindow() != null) { 276 transitionStyle = mActivity.getWindow().getAttributes().windowAnimations; 277 } 278 if (transitionStyle == 0) { 279 return null; 280 } 281 282 TypedArray attrs = mActivity.obtainStyledAttributes(transitionStyle, 283 com.android.internal.R.styleable.FragmentAnimation); 284 int anim = attrs.getResourceId(styleIndex, 0); 285 attrs.recycle(); 286 287 if (anim == 0) { 288 return null; 289 } 290 291 return AnimatorInflater.loadAnimator(mActivity, anim); 292 } 293 294 void moveToState(Fragment f, int newState, int transit, int transitionStyle) { 295 // Fragments that are not currently added will sit in the onCreate() state. 296 if (!f.mAdded && newState > Fragment.CREATED) { 297 newState = Fragment.CREATED; 298 } 299 300 if (f.mState < newState) { 301 switch (f.mState) { 302 case Fragment.INITIALIZING: 303 if (DEBUG) Log.v(TAG, "moveto CREATED: " + f); 304 if (f.mSavedFragmentState != null) { 305 f.mSavedViewState = f.mSavedFragmentState.getSparseParcelableArray( 306 FragmentManagerImpl.VIEW_STATE_TAG); 307 f.mTarget = getFragment(f.mSavedFragmentState, 308 FragmentManagerImpl.TARGET_STATE_TAG); 309 if (f.mTarget != null) { 310 f.mTargetRequestCode = f.mSavedFragmentState.getInt( 311 FragmentManagerImpl.TARGET_REQUEST_CODE_STATE_TAG, 0); 312 } 313 } 314 f.mActivity = mActivity; 315 f.mCalled = false; 316 f.onAttach(mActivity); 317 if (!f.mCalled) { 318 throw new SuperNotCalledException("Fragment " + f 319 + " did not call through to super.onAttach()"); 320 } 321 mActivity.onAttachFragment(f); 322 323 if (!f.mRetaining) { 324 f.mCalled = false; 325 f.onCreate(f.mSavedFragmentState); 326 if (!f.mCalled) { 327 throw new SuperNotCalledException("Fragment " + f 328 + " did not call through to super.onCreate()"); 329 } 330 } 331 f.mRetaining = false; 332 if (f.mFromLayout) { 333 // For fragments that are part of the content view 334 // layout, we need to instantiate the view immediately 335 // and the inflater will take care of adding it. 336 f.mView = f.onCreateView(mActivity.getLayoutInflater(), 337 null, f.mSavedFragmentState); 338 if (f.mView != null) { 339 f.mView.setSaveFromParentEnabled(false); 340 f.restoreViewState(); 341 if (f.mHidden) f.mView.setVisibility(View.GONE); 342 } 343 } 344 case Fragment.CREATED: 345 if (newState > Fragment.CREATED) { 346 if (DEBUG) Log.v(TAG, "moveto CONTENT: " + f); 347 if (!f.mFromLayout) { 348 ViewGroup container = null; 349 if (f.mContainerId != 0) { 350 container = (ViewGroup)mActivity.findViewById(f.mContainerId); 351 if (container == null) { 352 throw new IllegalArgumentException("New view found for id 0x" 353 + Integer.toHexString(f.mContainerId) 354 + " for fragment " + f); 355 } 356 } 357 f.mContainer = container; 358 f.mView = f.onCreateView(mActivity.getLayoutInflater(), 359 container, f.mSavedFragmentState); 360 if (f.mView != null) { 361 f.mView.setSaveFromParentEnabled(false); 362 if (container != null) { 363 Animator anim = loadAnimator(f, transit, true, 364 transitionStyle); 365 if (anim != null) { 366 anim.setTarget(f.mView); 367 anim.start(); 368 } 369 container.addView(f.mView); 370 f.restoreViewState(); 371 } 372 if (f.mHidden) f.mView.setVisibility(View.GONE); 373 } 374 } 375 376 f.mCalled = false; 377 f.onActivityCreated(f.mSavedFragmentState); 378 if (!f.mCalled) { 379 throw new SuperNotCalledException("Fragment " + f 380 + " did not call through to super.onReady()"); 381 } 382 f.mSavedFragmentState = null; 383 } 384 case Fragment.ACTIVITY_CREATED: 385 if (newState > Fragment.ACTIVITY_CREATED) { 386 if (DEBUG) Log.v(TAG, "moveto STARTED: " + f); 387 f.mCalled = false; 388 f.onStart(); 389 if (!f.mCalled) { 390 throw new SuperNotCalledException("Fragment " + f 391 + " did not call through to super.onStart()"); 392 } 393 } 394 case Fragment.STARTED: 395 if (newState > Fragment.STARTED) { 396 if (DEBUG) Log.v(TAG, "moveto RESUMED: " + f); 397 f.mCalled = false; 398 f.mResumed = true; 399 f.onResume(); 400 if (!f.mCalled) { 401 throw new SuperNotCalledException("Fragment " + f 402 + " did not call through to super.onResume()"); 403 } 404 } 405 } 406 } else if (f.mState > newState) { 407 switch (f.mState) { 408 case Fragment.RESUMED: 409 if (newState < Fragment.RESUMED) { 410 if (DEBUG) Log.v(TAG, "movefrom RESUMED: " + f); 411 f.mCalled = false; 412 f.onPause(); 413 if (!f.mCalled) { 414 throw new SuperNotCalledException("Fragment " + f 415 + " did not call through to super.onPause()"); 416 } 417 f.mResumed = false; 418 } 419 case Fragment.STARTED: 420 if (newState < Fragment.STARTED) { 421 if (DEBUG) Log.v(TAG, "movefrom STARTED: " + f); 422 f.mCalled = false; 423 f.performStop(); 424 if (!f.mCalled) { 425 throw new SuperNotCalledException("Fragment " + f 426 + " did not call through to super.onStop()"); 427 } 428 } 429 case Fragment.ACTIVITY_CREATED: 430 if (newState < Fragment.ACTIVITY_CREATED) { 431 if (DEBUG) Log.v(TAG, "movefrom CONTENT: " + f); 432 if (f.mView != null) { 433 // Need to save the current view state if not 434 // done already. 435 if (!mActivity.isFinishing() && f.mSavedFragmentState == null) { 436 saveFragmentViewState(f); 437 } 438 } 439 f.mCalled = false; 440 f.onDestroyView(); 441 if (!f.mCalled) { 442 throw new SuperNotCalledException("Fragment " + f 443 + " did not call through to super.onDestroyedView()"); 444 } 445 if (f.mView != null && f.mContainer != null) { 446 Animator anim = null; 447 if (mCurState > Fragment.INITIALIZING) { 448 anim = loadAnimator(f, transit, false, 449 transitionStyle); 450 } 451 if (anim != null) { 452 final ViewGroup container = f.mContainer; 453 final View view = f.mView; 454 container.startViewTransition(view); 455 anim.addListener(new AnimatorListenerAdapter() { 456 @Override 457 public void onAnimationEnd(Animator anim) { 458 container.endViewTransition(view); 459 } 460 }); 461 anim.setTarget(f.mView); 462 anim.start(); 463 464 } 465 f.mContainer.removeView(f.mView); 466 } 467 f.mContainer = null; 468 f.mView = null; 469 } 470 case Fragment.CREATED: 471 if (newState < Fragment.CREATED) { 472 if (DEBUG) Log.v(TAG, "movefrom CREATED: " + f); 473 if (!f.mRetaining) { 474 f.mCalled = false; 475 f.onDestroy(); 476 if (!f.mCalled) { 477 throw new SuperNotCalledException("Fragment " + f 478 + " did not call through to super.onDestroy()"); 479 } 480 } 481 482 f.mCalled = false; 483 f.onDetach(); 484 if (!f.mCalled) { 485 throw new SuperNotCalledException("Fragment " + f 486 + " did not call through to super.onDetach()"); 487 } 488 f.mImmediateActivity = null; 489 f.mActivity = null; 490 } 491 } 492 } 493 494 f.mState = newState; 495 } 496 497 void moveToState(int newState, boolean always) { 498 moveToState(newState, 0, 0, always); 499 } 500 501 void moveToState(int newState, int transit, int transitStyle, boolean always) { 502 if (mActivity == null && newState != Fragment.INITIALIZING) { 503 throw new IllegalStateException("No activity"); 504 } 505 506 if (!always && mCurState == newState) { 507 return; 508 } 509 510 mCurState = newState; 511 if (mActive != null) { 512 for (int i=0; i<mActive.size(); i++) { 513 Fragment f = mActive.get(i); 514 if (f != null) { 515 moveToState(f, newState, transit, transitStyle); 516 } 517 } 518 519 if (mNeedMenuInvalidate && mActivity != null) { 520 mActivity.invalidateOptionsMenu(); 521 mNeedMenuInvalidate = false; 522 } 523 } 524 } 525 526 void makeActive(Fragment f) { 527 if (f.mIndex >= 0) { 528 return; 529 } 530 531 if (mAvailIndices == null || mAvailIndices.size() <= 0) { 532 if (mActive == null) { 533 mActive = new ArrayList<Fragment>(); 534 } 535 f.setIndex(mActive.size()); 536 mActive.add(f); 537 538 } else { 539 f.setIndex(mAvailIndices.remove(mAvailIndices.size()-1)); 540 mActive.set(f.mIndex, f); 541 } 542 } 543 544 void makeInactive(Fragment f) { 545 if (f.mIndex < 0) { 546 return; 547 } 548 549 if (DEBUG) Log.v(TAG, "Freeing fragment index " + f.mIndex); 550 mActive.set(f.mIndex, null); 551 if (mAvailIndices == null) { 552 mAvailIndices = new ArrayList<Integer>(); 553 } 554 mAvailIndices.add(f.mIndex); 555 mActivity.invalidateFragmentIndex(f.mIndex); 556 f.clearIndex(); 557 } 558 559 public void addFragment(Fragment fragment, boolean moveToStateNow) { 560 if (mAdded == null) { 561 mAdded = new ArrayList<Fragment>(); 562 } 563 mAdded.add(fragment); 564 makeActive(fragment); 565 if (DEBUG) Log.v(TAG, "add: " + fragment); 566 fragment.mAdded = true; 567 if (fragment.mHasMenu) { 568 mNeedMenuInvalidate = true; 569 } 570 if (moveToStateNow) { 571 moveToState(fragment, mCurState, 0, 0); 572 } 573 } 574 575 public void removeFragment(Fragment fragment, int transition, int transitionStyle) { 576 if (DEBUG) Log.v(TAG, "remove: " + fragment + " nesting=" + fragment.mBackStackNesting); 577 mAdded.remove(fragment); 578 final boolean inactive = fragment.mBackStackNesting <= 0; 579 if (fragment.mHasMenu) { 580 mNeedMenuInvalidate = true; 581 } 582 fragment.mAdded = false; 583 moveToState(fragment, inactive ? Fragment.INITIALIZING : Fragment.CREATED, 584 transition, transitionStyle); 585 if (inactive) { 586 makeInactive(fragment); 587 } 588 } 589 590 public void hideFragment(Fragment fragment, int transition, int transitionStyle) { 591 if (DEBUG) Log.v(TAG, "hide: " + fragment); 592 if (!fragment.mHidden) { 593 fragment.mHidden = true; 594 if (fragment.mView != null) { 595 Animator anim = loadAnimator(fragment, transition, true, 596 transitionStyle); 597 if (anim != null) { 598 anim.setTarget(fragment.mView); 599 anim.start(); 600 } 601 fragment.mView.setVisibility(View.GONE); 602 } 603 if (fragment.mAdded && fragment.mHasMenu) { 604 mNeedMenuInvalidate = true; 605 } 606 fragment.onHiddenChanged(true); 607 } 608 } 609 610 public void showFragment(Fragment fragment, int transition, int transitionStyle) { 611 if (DEBUG) Log.v(TAG, "show: " + fragment); 612 if (fragment.mHidden) { 613 fragment.mHidden = false; 614 if (fragment.mView != null) { 615 Animator anim = loadAnimator(fragment, transition, true, 616 transitionStyle); 617 if (anim != null) { 618 anim.setTarget(fragment.mView); 619 anim.start(); 620 } 621 fragment.mView.setVisibility(View.VISIBLE); 622 } 623 if (fragment.mAdded && fragment.mHasMenu) { 624 mNeedMenuInvalidate = true; 625 } 626 fragment.onHiddenChanged(false); 627 } 628 } 629 630 public Fragment findFragmentById(int id) { 631 if (mActive != null) { 632 // First look through added fragments. 633 for (int i=mAdded.size()-1; i>=0; i--) { 634 Fragment f = mAdded.get(i); 635 if (f != null && f.mFragmentId == id) { 636 return f; 637 } 638 } 639 // Now for any known fragment. 640 for (int i=mActive.size()-1; i>=0; i--) { 641 Fragment f = mActive.get(i); 642 if (f != null && f.mFragmentId == id) { 643 return f; 644 } 645 } 646 } 647 return null; 648 } 649 650 public Fragment findFragmentByTag(String tag) { 651 if (mActive != null && tag != null) { 652 // First look through added fragments. 653 for (int i=mAdded.size()-1; i>=0; i--) { 654 Fragment f = mAdded.get(i); 655 if (f != null && tag.equals(f.mTag)) { 656 return f; 657 } 658 } 659 // Now for any known fragment. 660 for (int i=mActive.size()-1; i>=0; i--) { 661 Fragment f = mActive.get(i); 662 if (f != null && tag.equals(f.mTag)) { 663 return f; 664 } 665 } 666 } 667 return null; 668 } 669 670 public Fragment findFragmentByWho(String who) { 671 if (mActive != null && who != null) { 672 for (int i=mActive.size()-1; i>=0; i--) { 673 Fragment f = mActive.get(i); 674 if (f != null && who.equals(f.mWho)) { 675 return f; 676 } 677 } 678 } 679 return null; 680 } 681 682 public void enqueueAction(Runnable action) { 683 if (mStateSaved) { 684 throw new IllegalStateException( 685 "Can not perform this action after onSaveInstanceState"); 686 } 687 synchronized (this) { 688 if (mPendingActions == null) { 689 mPendingActions = new ArrayList<Runnable>(); 690 } 691 mPendingActions.add(action); 692 if (mPendingActions.size() == 1) { 693 mActivity.mHandler.removeCallbacks(mExecCommit); 694 mActivity.mHandler.post(mExecCommit); 695 } 696 } 697 } 698 699 public int allocBackStackIndex(BackStackEntry bse) { 700 synchronized (this) { 701 if (mAvailBackStackIndices == null || mAvailBackStackIndices.size() <= 0) { 702 if (mBackStackIndices == null) { 703 mBackStackIndices = new ArrayList<BackStackEntry>(); 704 } 705 int index = mBackStackIndices.size(); 706 if (DEBUG) Log.v(TAG, "Setting back stack index " + index + " to " + bse); 707 mBackStackIndices.add(bse); 708 return index; 709 710 } else { 711 int index = mAvailBackStackIndices.remove(mAvailBackStackIndices.size()-1); 712 if (DEBUG) Log.v(TAG, "Adding back stack index " + index + " with " + bse); 713 mBackStackIndices.set(index, bse); 714 return index; 715 } 716 } 717 } 718 719 public void setBackStackIndex(int index, BackStackEntry bse) { 720 synchronized (this) { 721 if (mBackStackIndices == null) { 722 mBackStackIndices = new ArrayList<BackStackEntry>(); 723 } 724 int N = mBackStackIndices.size(); 725 if (index < N) { 726 if (DEBUG) Log.v(TAG, "Setting back stack index " + index + " to " + bse); 727 mBackStackIndices.set(index, bse); 728 } else { 729 while (N < index) { 730 mBackStackIndices.add(null); 731 if (mAvailBackStackIndices == null) { 732 mAvailBackStackIndices = new ArrayList<Integer>(); 733 } 734 if (DEBUG) Log.v(TAG, "Adding available back stack index " + N); 735 mAvailBackStackIndices.add(N); 736 N++; 737 } 738 if (DEBUG) Log.v(TAG, "Adding back stack index " + index + " with " + bse); 739 mBackStackIndices.add(bse); 740 } 741 } 742 } 743 744 public void freeBackStackIndex(int index) { 745 synchronized (this) { 746 mBackStackIndices.set(index, null); 747 if (mAvailBackStackIndices == null) { 748 mAvailBackStackIndices = new ArrayList<Integer>(); 749 } 750 if (DEBUG) Log.v(TAG, "Freeing back stack index " + index); 751 mAvailBackStackIndices.add(index); 752 } 753 } 754 755 /** 756 * Only call from main thread! 757 */ 758 public void execPendingActions() { 759 if (mExecutingActions) { 760 throw new IllegalStateException("Recursive entry to execPendingActions"); 761 } 762 763 while (true) { 764 int numActions; 765 766 synchronized (this) { 767 if (mPendingActions == null || mPendingActions.size() == 0) { 768 return; 769 } 770 771 numActions = mPendingActions.size(); 772 if (mTmpActions == null || mTmpActions.length < numActions) { 773 mTmpActions = new Runnable[numActions]; 774 } 775 mPendingActions.toArray(mTmpActions); 776 mPendingActions.clear(); 777 mActivity.mHandler.removeCallbacks(mExecCommit); 778 } 779 780 mExecutingActions = true; 781 for (int i=0; i<numActions; i++) { 782 mTmpActions[i].run(); 783 } 784 mExecutingActions = false; 785 } 786 } 787 788 public void addBackStackState(BackStackEntry state) { 789 if (mBackStack == null) { 790 mBackStack = new ArrayList<BackStackEntry>(); 791 } 792 mBackStack.add(state); 793 } 794 795 boolean popBackStackState(Handler handler, String name, int id, int flags) { 796 if (mBackStack == null) { 797 return false; 798 } 799 if (name == null && id < 0 && (flags&Activity.POP_BACK_STACK_INCLUSIVE) == 0) { 800 int last = mBackStack.size()-1; 801 if (last < 0) { 802 return false; 803 } 804 final BackStackEntry bss = mBackStack.remove(last); 805 enqueueAction(new Runnable() { 806 public void run() { 807 if (DEBUG) Log.v(TAG, "Popping back stack state: " + bss); 808 bss.popFromBackStack(true); 809 } 810 }); 811 } else { 812 int index = -1; 813 if (name != null || id >= 0) { 814 // If a name or ID is specified, look for that place in 815 // the stack. 816 index = mBackStack.size()-1; 817 while (index >= 0) { 818 BackStackEntry bss = mBackStack.get(index); 819 if (name != null && name.equals(bss.getName())) { 820 break; 821 } 822 if (id >= 0 && id == bss.mIndex) { 823 break; 824 } 825 index--; 826 } 827 if (index < 0) { 828 return false; 829 } 830 if ((flags&Activity.POP_BACK_STACK_INCLUSIVE) != 0) { 831 index--; 832 // Consume all following entries that match. 833 while (index >= 0) { 834 BackStackEntry bss = mBackStack.get(index); 835 if ((name != null && name.equals(bss.getName())) 836 || (id >= 0 && id == bss.mIndex)) { 837 index--; 838 continue; 839 } 840 break; 841 } 842 } 843 } 844 if (index == mBackStack.size()-1) { 845 return false; 846 } 847 final ArrayList<BackStackEntry> states 848 = new ArrayList<BackStackEntry>(); 849 for (int i=mBackStack.size()-1; i>index; i--) { 850 states.add(mBackStack.remove(i)); 851 } 852 enqueueAction(new Runnable() { 853 public void run() { 854 final int LAST = states.size()-1; 855 for (int i=0; i<=LAST; i++) { 856 if (DEBUG) Log.v(TAG, "Popping back stack state: " + states.get(i)); 857 states.get(i).popFromBackStack(i == LAST); 858 } 859 } 860 }); 861 } 862 return true; 863 } 864 865 ArrayList<Fragment> retainNonConfig() { 866 ArrayList<Fragment> fragments = null; 867 if (mActive != null) { 868 for (int i=0; i<mActive.size(); i++) { 869 Fragment f = mActive.get(i); 870 if (f != null && f.mRetainInstance) { 871 if (fragments == null) { 872 fragments = new ArrayList<Fragment>(); 873 } 874 fragments.add(f); 875 f.mRetaining = true; 876 } 877 } 878 } 879 return fragments; 880 } 881 882 void saveFragmentViewState(Fragment f) { 883 if (f.mView == null) { 884 return; 885 } 886 if (mStateArray == null) { 887 mStateArray = new SparseArray<Parcelable>(); 888 } 889 f.mView.saveHierarchyState(mStateArray); 890 if (mStateArray.size() > 0) { 891 f.mSavedViewState = mStateArray; 892 mStateArray = null; 893 } 894 } 895 896 Parcelable saveAllState() { 897 mStateSaved = true; 898 899 if (mActive == null || mActive.size() <= 0) { 900 return null; 901 } 902 903 // First collect all active fragments. 904 int N = mActive.size(); 905 FragmentState[] active = new FragmentState[N]; 906 boolean haveFragments = false; 907 for (int i=0; i<N; i++) { 908 Fragment f = mActive.get(i); 909 if (f != null) { 910 haveFragments = true; 911 912 FragmentState fs = new FragmentState(f); 913 active[i] = fs; 914 915 if (mStateBundle == null) { 916 mStateBundle = new Bundle(); 917 } 918 f.onSaveInstanceState(mStateBundle); 919 if (!mStateBundle.isEmpty()) { 920 fs.mSavedFragmentState = mStateBundle; 921 mStateBundle = null; 922 } 923 924 if (f.mView != null) { 925 saveFragmentViewState(f); 926 if (f.mSavedViewState != null) { 927 if (fs.mSavedFragmentState == null) { 928 fs.mSavedFragmentState = new Bundle(); 929 } 930 fs.mSavedFragmentState.putSparseParcelableArray( 931 FragmentManagerImpl.VIEW_STATE_TAG, f.mSavedViewState); 932 } 933 } 934 935 if (f.mTarget != null) { 936 if (fs.mSavedFragmentState == null) { 937 fs.mSavedFragmentState = new Bundle(); 938 } 939 putFragment(fs.mSavedFragmentState, 940 FragmentManagerImpl.TARGET_STATE_TAG, f.mTarget); 941 if (f.mTargetRequestCode != 0) { 942 fs.mSavedFragmentState.putInt( 943 FragmentManagerImpl.TARGET_REQUEST_CODE_STATE_TAG, 944 f.mTargetRequestCode); 945 } 946 } 947 948 if (DEBUG) Log.v(TAG, "Saved state of " + f + ": " 949 + fs.mSavedFragmentState); 950 } 951 } 952 953 if (!haveFragments) { 954 if (DEBUG) Log.v(TAG, "saveAllState: no fragments!"); 955 return null; 956 } 957 958 int[] added = null; 959 BackStackState[] backStack = null; 960 961 // Build list of currently added fragments. 962 N = mAdded.size(); 963 if (N > 0) { 964 added = new int[N]; 965 for (int i=0; i<N; i++) { 966 added[i] = mAdded.get(i).mIndex; 967 if (DEBUG) Log.v(TAG, "saveAllState: adding fragment #" + i 968 + ": " + mAdded.get(i)); 969 } 970 } 971 972 // Now save back stack. 973 if (mBackStack != null) { 974 N = mBackStack.size(); 975 if (N > 0) { 976 backStack = new BackStackState[N]; 977 for (int i=0; i<N; i++) { 978 backStack[i] = new BackStackState(this, mBackStack.get(i)); 979 if (DEBUG) Log.v(TAG, "saveAllState: adding back stack #" + i 980 + ": " + mBackStack.get(i)); 981 } 982 } 983 } 984 985 FragmentManagerState fms = new FragmentManagerState(); 986 fms.mActive = active; 987 fms.mAdded = added; 988 fms.mBackStack = backStack; 989 return fms; 990 } 991 992 void restoreAllState(Parcelable state, ArrayList<Fragment> nonConfig) { 993 // If there is no saved state at all, then there can not be 994 // any nonConfig fragments either, so that is that. 995 if (state == null) return; 996 FragmentManagerState fms = (FragmentManagerState)state; 997 if (fms.mActive == null) return; 998 999 // First re-attach any non-config instances we are retaining back 1000 // to their saved state, so we don't try to instantiate them again. 1001 if (nonConfig != null) { 1002 for (int i=0; i<nonConfig.size(); i++) { 1003 Fragment f = nonConfig.get(i); 1004 if (DEBUG) Log.v(TAG, "restoreAllState: re-attaching retained " + f); 1005 FragmentState fs = fms.mActive[f.mIndex]; 1006 fs.mInstance = f; 1007 f.mSavedViewState = null; 1008 f.mBackStackNesting = 0; 1009 f.mAdded = false; 1010 if (fs.mSavedFragmentState != null) { 1011 f.mSavedViewState = fs.mSavedFragmentState.getSparseParcelableArray( 1012 FragmentManagerImpl.VIEW_STATE_TAG); 1013 } 1014 } 1015 } 1016 1017 // Build the full list of active fragments, instantiating them from 1018 // their saved state. 1019 mActive = new ArrayList<Fragment>(fms.mActive.length); 1020 if (mAvailIndices != null) { 1021 mAvailIndices.clear(); 1022 } 1023 for (int i=0; i<fms.mActive.length; i++) { 1024 FragmentState fs = fms.mActive[i]; 1025 if (fs != null) { 1026 Fragment f = fs.instantiate(mActivity); 1027 if (DEBUG) Log.v(TAG, "restoreAllState: adding #" + i + ": " + f); 1028 mActive.add(f); 1029 } else { 1030 if (DEBUG) Log.v(TAG, "restoreAllState: adding #" + i + ": (null)"); 1031 mActive.add(null); 1032 if (mAvailIndices == null) { 1033 mAvailIndices = new ArrayList<Integer>(); 1034 } 1035 if (DEBUG) Log.v(TAG, "restoreAllState: adding avail #" + i); 1036 mAvailIndices.add(i); 1037 } 1038 } 1039 1040 // Update the target of all retained fragments. 1041 if (nonConfig != null) { 1042 for (int i=0; i<nonConfig.size(); i++) { 1043 Fragment f = nonConfig.get(i); 1044 if (f.mTarget != null) { 1045 if (f.mTarget.mIndex < mActive.size()) { 1046 f.mTarget = mActive.get(f.mTarget.mIndex); 1047 } else { 1048 Log.w(TAG, "Re-attaching retained fragment " + f 1049 + " target no longer exists: " + f.mTarget); 1050 f.mTarget = null; 1051 } 1052 } 1053 } 1054 } 1055 1056 // Build the list of currently added fragments. 1057 if (fms.mAdded != null) { 1058 mAdded = new ArrayList<Fragment>(fms.mAdded.length); 1059 for (int i=0; i<fms.mAdded.length; i++) { 1060 Fragment f = mActive.get(fms.mAdded[i]); 1061 if (f == null) { 1062 throw new IllegalStateException( 1063 "No instantiated fragment for index #" + fms.mAdded[i]); 1064 } 1065 f.mAdded = true; 1066 f.mImmediateActivity = mActivity; 1067 if (DEBUG) Log.v(TAG, "restoreAllState: making added #" + i + ": " + f); 1068 mAdded.add(f); 1069 } 1070 } else { 1071 mAdded = null; 1072 } 1073 1074 // Build the back stack. 1075 if (fms.mBackStack != null) { 1076 mBackStack = new ArrayList<BackStackEntry>(fms.mBackStack.length); 1077 for (int i=0; i<fms.mBackStack.length; i++) { 1078 BackStackEntry bse = fms.mBackStack[i].instantiate(this); 1079 if (DEBUG) Log.v(TAG, "restoreAllState: adding bse #" + i 1080 + " (index " + bse.mIndex + "): " + bse); 1081 mBackStack.add(bse); 1082 if (bse.mIndex >= 0) { 1083 setBackStackIndex(bse.mIndex, bse); 1084 } 1085 } 1086 } else { 1087 mBackStack = null; 1088 } 1089 } 1090 1091 public void attachActivity(Activity activity) { 1092 if (mActivity != null) throw new IllegalStateException(); 1093 mActivity = activity; 1094 } 1095 1096 public void dispatchCreate() { 1097 mStateSaved = false; 1098 moveToState(Fragment.CREATED, false); 1099 } 1100 1101 public void dispatchActivityCreated() { 1102 mStateSaved = false; 1103 moveToState(Fragment.ACTIVITY_CREATED, false); 1104 } 1105 1106 public void dispatchStart() { 1107 mStateSaved = false; 1108 moveToState(Fragment.STARTED, false); 1109 } 1110 1111 public void dispatchResume() { 1112 mStateSaved = false; 1113 moveToState(Fragment.RESUMED, false); 1114 } 1115 1116 public void dispatchPause() { 1117 moveToState(Fragment.STARTED, false); 1118 } 1119 1120 public void dispatchStop() { 1121 moveToState(Fragment.ACTIVITY_CREATED, false); 1122 } 1123 1124 public void dispatchDestroy() { 1125 moveToState(Fragment.INITIALIZING, false); 1126 mActivity = null; 1127 } 1128 1129 public boolean dispatchCreateOptionsMenu(Menu menu, MenuInflater inflater) { 1130 boolean show = false; 1131 if (mActive != null) { 1132 for (int i=0; i<mAdded.size(); i++) { 1133 Fragment f = mAdded.get(i); 1134 if (f != null && !f.mHidden && f.mHasMenu) { 1135 show = true; 1136 f.onCreateOptionsMenu(menu, inflater); 1137 } 1138 } 1139 } 1140 return show; 1141 } 1142 1143 public boolean dispatchPrepareOptionsMenu(Menu menu) { 1144 boolean show = false; 1145 if (mActive != null) { 1146 for (int i=0; i<mAdded.size(); i++) { 1147 Fragment f = mAdded.get(i); 1148 if (f != null && !f.mHidden && f.mHasMenu) { 1149 show = true; 1150 f.onPrepareOptionsMenu(menu); 1151 } 1152 } 1153 } 1154 return show; 1155 } 1156 1157 public boolean dispatchOptionsItemSelected(MenuItem item) { 1158 if (mActive != null) { 1159 for (int i=0; i<mAdded.size(); i++) { 1160 Fragment f = mAdded.get(i); 1161 if (f != null && !f.mHidden && f.mHasMenu) { 1162 if (f.onOptionsItemSelected(item)) { 1163 return true; 1164 } 1165 } 1166 } 1167 } 1168 return false; 1169 } 1170 1171 public boolean dispatchContextItemSelected(MenuItem item) { 1172 if (mActive != null) { 1173 for (int i=0; i<mAdded.size(); i++) { 1174 Fragment f = mAdded.get(i); 1175 if (f != null && !f.mHidden) { 1176 if (f.onContextItemSelected(item)) { 1177 return true; 1178 } 1179 } 1180 } 1181 } 1182 return false; 1183 } 1184 1185 public void dispatchOptionsMenuClosed(Menu menu) { 1186 if (mActive != null) { 1187 for (int i=0; i<mAdded.size(); i++) { 1188 Fragment f = mAdded.get(i); 1189 if (f != null && !f.mHidden && f.mHasMenu) { 1190 f.onOptionsMenuClosed(menu); 1191 } 1192 } 1193 } 1194 } 1195 1196 public static int reverseTransit(int transit) { 1197 int rev = 0; 1198 switch (transit) { 1199 case FragmentTransaction.TRANSIT_FRAGMENT_OPEN: 1200 rev = FragmentTransaction.TRANSIT_FRAGMENT_CLOSE; 1201 break; 1202 case FragmentTransaction.TRANSIT_FRAGMENT_CLOSE: 1203 rev = FragmentTransaction.TRANSIT_FRAGMENT_OPEN; 1204 break; 1205 } 1206 return rev; 1207 1208 } 1209 1210 public static int transitToStyleIndex(int transit, boolean enter) { 1211 int animAttr = -1; 1212 switch (transit) { 1213 case FragmentTransaction.TRANSIT_FRAGMENT_OPEN: 1214 animAttr = enter 1215 ? com.android.internal.R.styleable.FragmentAnimation_fragmentOpenEnterAnimation 1216 : com.android.internal.R.styleable.FragmentAnimation_fragmentOpenExitAnimation; 1217 break; 1218 case FragmentTransaction.TRANSIT_FRAGMENT_CLOSE: 1219 animAttr = enter 1220 ? com.android.internal.R.styleable.FragmentAnimation_fragmentCloseEnterAnimation 1221 : com.android.internal.R.styleable.FragmentAnimation_fragmentCloseExitAnimation; 1222 break; 1223 } 1224 return animAttr; 1225 } 1226} 1227