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