FragmentManager.java revision 990e6fc0326f5948736650c0cb71b6002d443c9c
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 static android.support.annotation.RestrictTo.Scope.GROUP_ID; 20 21import android.content.Context; 22import android.content.res.Configuration; 23import android.content.res.Resources.NotFoundException; 24import android.content.res.TypedArray; 25import android.os.Build; 26import android.os.Bundle; 27import android.os.Looper; 28import android.os.Parcel; 29import android.os.Parcelable; 30import android.support.annotation.CallSuper; 31import android.support.annotation.IdRes; 32import android.support.annotation.RestrictTo; 33import android.support.annotation.StringRes; 34import android.support.v4.os.BuildCompat; 35import android.support.v4.util.DebugUtils; 36import android.support.v4.util.LogWriter; 37import android.support.v4.view.LayoutInflaterFactory; 38import android.support.v4.view.ViewCompat; 39import android.util.AttributeSet; 40import android.util.Log; 41import android.util.SparseArray; 42import android.view.Menu; 43import android.view.MenuInflater; 44import android.view.MenuItem; 45import android.view.View; 46import android.view.ViewGroup; 47import android.view.animation.AccelerateInterpolator; 48import android.view.animation.AlphaAnimation; 49import android.view.animation.Animation; 50import android.view.animation.Animation.AnimationListener; 51import android.view.animation.AnimationSet; 52import android.view.animation.AnimationUtils; 53import android.view.animation.DecelerateInterpolator; 54import android.view.animation.Interpolator; 55import android.view.animation.ScaleAnimation; 56 57import java.io.FileDescriptor; 58import java.io.PrintWriter; 59import java.lang.reflect.Field; 60import java.util.ArrayList; 61import java.util.Arrays; 62import java.util.List; 63 64/** 65 * Static library support version of the framework's {@link android.app.FragmentManager}. 66 * Used to write apps that run on platforms prior to Android 3.0. When running 67 * on Android 3.0 or above, this implementation is still used; it does not try 68 * to switch to the framework's implementation. See the framework {@link FragmentManager} 69 * documentation for a class overview. 70 * 71 * <p>Your activity must derive from {@link FragmentActivity} to use this. From such an activity, 72 * you can acquire the {@link FragmentManager} by calling 73 * {@link FragmentActivity#getSupportFragmentManager}. 74 */ 75public abstract class FragmentManager { 76 /** 77 * Representation of an entry on the fragment back stack, as created 78 * with {@link FragmentTransaction#addToBackStack(String) 79 * FragmentTransaction.addToBackStack()}. Entries can later be 80 * retrieved with {@link FragmentManager#getBackStackEntryAt(int) 81 * FragmentManager.getBackStackEntryAt()}. 82 * 83 * <p>Note that you should never hold on to a BackStackEntry object; 84 * the identifier as returned by {@link #getId} is the only thing that 85 * will be persisted across activity instances. 86 */ 87 public interface BackStackEntry { 88 /** 89 * Return the unique identifier for the entry. This is the only 90 * representation of the entry that will persist across activity 91 * instances. 92 */ 93 public int getId(); 94 95 /** 96 * Get the name that was supplied to 97 * {@link FragmentTransaction#addToBackStack(String) 98 * FragmentTransaction.addToBackStack(String)} when creating this entry. 99 */ 100 public String getName(); 101 102 /** 103 * Return the full bread crumb title resource identifier for the entry, 104 * or 0 if it does not have one. 105 */ 106 @StringRes 107 public int getBreadCrumbTitleRes(); 108 109 /** 110 * Return the short bread crumb title resource identifier for the entry, 111 * or 0 if it does not have one. 112 */ 113 @StringRes 114 public int getBreadCrumbShortTitleRes(); 115 116 /** 117 * Return the full bread crumb title for the entry, or null if it 118 * does not have one. 119 */ 120 public CharSequence getBreadCrumbTitle(); 121 122 /** 123 * Return the short bread crumb title for the entry, or null if it 124 * does not have one. 125 */ 126 public CharSequence getBreadCrumbShortTitle(); 127 } 128 129 /** 130 * Interface to watch for changes to the back stack. 131 */ 132 public interface OnBackStackChangedListener { 133 /** 134 * Called whenever the contents of the back stack change. 135 */ 136 public void onBackStackChanged(); 137 } 138 139 /** 140 * Start a series of edit operations on the Fragments associated with 141 * this FragmentManager. 142 * 143 * <p>Note: A fragment transaction can only be created/committed prior 144 * to an activity saving its state. If you try to commit a transaction 145 * after {@link FragmentActivity#onSaveInstanceState FragmentActivity.onSaveInstanceState()} 146 * (and prior to a following {@link FragmentActivity#onStart FragmentActivity.onStart} 147 * or {@link FragmentActivity#onResume FragmentActivity.onResume()}, you will get an error. 148 * This is because the framework takes care of saving your current fragments 149 * in the state, and if changes are made after the state is saved then they 150 * will be lost.</p> 151 */ 152 public abstract FragmentTransaction beginTransaction(); 153 154 /** 155 * @hide -- remove once prebuilts are in. 156 * @deprecated 157 */ 158 @RestrictTo(GROUP_ID) 159 @Deprecated 160 public FragmentTransaction openTransaction() { 161 return beginTransaction(); 162 } 163 164 /** 165 * After a {@link FragmentTransaction} is committed with 166 * {@link FragmentTransaction#commit FragmentTransaction.commit()}, it 167 * is scheduled to be executed asynchronously on the process's main thread. 168 * If you want to immediately executing any such pending operations, you 169 * can call this function (only from the main thread) to do so. Note that 170 * all callbacks and other related behavior will be done from within this 171 * call, so be careful about where this is called from. 172 * 173 * <p>If you are committing a single transaction that does not modify the 174 * fragment back stack, strongly consider using 175 * {@link FragmentTransaction#commitNow()} instead. This can help avoid 176 * unwanted side effects when other code in your app has pending committed 177 * transactions that expect different timing.</p> 178 * <p> 179 * This also forces the start of any postponed Transactions where 180 * {@link Fragment#postponeEnterTransition()} has been called. 181 * 182 * @return Returns true if there were any pending transactions to be 183 * executed. 184 */ 185 public abstract boolean executePendingTransactions(); 186 187 /** 188 * Finds a fragment that was identified by the given id either when inflated 189 * from XML or as the container ID when added in a transaction. This first 190 * searches through fragments that are currently added to the manager's 191 * activity; if no such fragment is found, then all fragments currently 192 * on the back stack associated with this ID are searched. 193 * @return The fragment if found or null otherwise. 194 */ 195 public abstract Fragment findFragmentById(@IdRes int id); 196 197 /** 198 * Finds a fragment that was identified by the given tag either when inflated 199 * from XML or as supplied when added in a transaction. This first 200 * searches through fragments that are currently added to the manager's 201 * activity; if no such fragment is found, then all fragments currently 202 * on the back stack are searched. 203 * @return The fragment if found or null otherwise. 204 */ 205 public abstract Fragment findFragmentByTag(String tag); 206 207 /** 208 * Flag for {@link #popBackStack(String, int)} 209 * and {@link #popBackStack(int, int)}: If set, and the name or ID of 210 * a back stack entry has been supplied, then all matching entries will 211 * be consumed until one that doesn't match is found or the bottom of 212 * the stack is reached. Otherwise, all entries up to but not including that entry 213 * will be removed. 214 */ 215 public static final int POP_BACK_STACK_INCLUSIVE = 1<<0; 216 217 /** 218 * Pop the top state off the back stack. Returns true if there was one 219 * to pop, else false. This function is asynchronous -- it enqueues the 220 * request to pop, but the action will not be performed until the application 221 * returns to its event loop. 222 */ 223 public abstract void popBackStack(); 224 225 /** 226 * Like {@link #popBackStack()}, but performs the operation immediately 227 * inside of the call. This is like calling {@link #executePendingTransactions()} 228 * afterwards without forcing the start of postponed Transactions. 229 * @return Returns true if there was something popped, else false. 230 */ 231 public abstract boolean popBackStackImmediate(); 232 233 /** 234 * Pop the last fragment transition from the manager's fragment 235 * back stack. If there is nothing to pop, false is returned. 236 * This function is asynchronous -- it enqueues the 237 * request to pop, but the action will not be performed until the application 238 * returns to its event loop. 239 * 240 * @param name If non-null, this is the name of a previous back state 241 * to look for; if found, all states up to that state will be popped. The 242 * {@link #POP_BACK_STACK_INCLUSIVE} flag can be used to control whether 243 * the named state itself is popped. If null, only the top state is popped. 244 * @param flags Either 0 or {@link #POP_BACK_STACK_INCLUSIVE}. 245 */ 246 public abstract void popBackStack(String name, int flags); 247 248 /** 249 * Like {@link #popBackStack(String, int)}, but performs the operation immediately 250 * inside of the call. This is like calling {@link #executePendingTransactions()} 251 * afterwards without forcing the start of postponed Transactions. 252 * @return Returns true if there was something popped, else false. 253 */ 254 public abstract boolean popBackStackImmediate(String name, int flags); 255 256 /** 257 * Pop all back stack states up to the one with the given identifier. 258 * This function is asynchronous -- it enqueues the 259 * request to pop, but the action will not be performed until the application 260 * returns to its event loop. 261 * 262 * @param id Identifier of the stated to be popped. If no identifier exists, 263 * false is returned. 264 * The identifier is the number returned by 265 * {@link FragmentTransaction#commit() FragmentTransaction.commit()}. The 266 * {@link #POP_BACK_STACK_INCLUSIVE} flag can be used to control whether 267 * the named state itself is popped. 268 * @param flags Either 0 or {@link #POP_BACK_STACK_INCLUSIVE}. 269 */ 270 public abstract void popBackStack(int id, int flags); 271 272 /** 273 * Like {@link #popBackStack(int, int)}, but performs the operation immediately 274 * inside of the call. This is like calling {@link #executePendingTransactions()} 275 * afterwards without forcing the start of postponed Transactions. 276 * @return Returns true if there was something popped, else false. 277 */ 278 public abstract boolean popBackStackImmediate(int id, int flags); 279 280 /** 281 * Return the number of entries currently in the back stack. 282 */ 283 public abstract int getBackStackEntryCount(); 284 285 /** 286 * Return the BackStackEntry at index <var>index</var> in the back stack; 287 * entries start index 0 being the bottom of the stack. 288 */ 289 public abstract BackStackEntry getBackStackEntryAt(int index); 290 291 /** 292 * Add a new listener for changes to the fragment back stack. 293 */ 294 public abstract void addOnBackStackChangedListener(OnBackStackChangedListener listener); 295 296 /** 297 * Remove a listener that was previously added with 298 * {@link #addOnBackStackChangedListener(OnBackStackChangedListener)}. 299 */ 300 public abstract void removeOnBackStackChangedListener(OnBackStackChangedListener listener); 301 302 /** 303 * Put a reference to a fragment in a Bundle. This Bundle can be 304 * persisted as saved state, and when later restoring 305 * {@link #getFragment(Bundle, String)} will return the current 306 * instance of the same fragment. 307 * 308 * @param bundle The bundle in which to put the fragment reference. 309 * @param key The name of the entry in the bundle. 310 * @param fragment The Fragment whose reference is to be stored. 311 */ 312 public abstract void putFragment(Bundle bundle, String key, Fragment fragment); 313 314 /** 315 * Retrieve the current Fragment instance for a reference previously 316 * placed with {@link #putFragment(Bundle, String, Fragment)}. 317 * 318 * @param bundle The bundle from which to retrieve the fragment reference. 319 * @param key The name of the entry in the bundle. 320 * @return Returns the current Fragment instance that is associated with 321 * the given reference. 322 */ 323 public abstract Fragment getFragment(Bundle bundle, String key); 324 325 /** 326 * Get a list of all fragments that have been added to the fragment manager. 327 * 328 * @return The list of all fragments or null if none. 329 * @hide 330 */ 331 @RestrictTo(GROUP_ID) 332 public abstract List<Fragment> getFragments(); 333 334 /** 335 * Save the current instance state of the given Fragment. This can be 336 * used later when creating a new instance of the Fragment and adding 337 * it to the fragment manager, to have it create itself to match the 338 * current state returned here. Note that there are limits on how 339 * this can be used: 340 * 341 * <ul> 342 * <li>The Fragment must currently be attached to the FragmentManager. 343 * <li>A new Fragment created using this saved state must be the same class 344 * type as the Fragment it was created from. 345 * <li>The saved state can not contain dependencies on other fragments -- 346 * that is it can't use {@link #putFragment(Bundle, String, Fragment)} to 347 * store a fragment reference because that reference may not be valid when 348 * this saved state is later used. Likewise the Fragment's target and 349 * result code are not included in this state. 350 * </ul> 351 * 352 * @param f The Fragment whose state is to be saved. 353 * @return The generated state. This will be null if there was no 354 * interesting state created by the fragment. 355 */ 356 public abstract Fragment.SavedState saveFragmentInstanceState(Fragment f); 357 358 /** 359 * Returns true if the final {@link android.app.Activity#onDestroy() Activity.onDestroy()} 360 * call has been made on the FragmentManager's Activity, so this instance is now dead. 361 */ 362 public abstract boolean isDestroyed(); 363 364 /** 365 * Print the FragmentManager's state into the given stream. 366 * 367 * @param prefix Text to print at the front of each line. 368 * @param fd The raw file descriptor that the dump is being sent to. 369 * @param writer A PrintWriter to which the dump is to be set. 370 * @param args Additional arguments to the dump request. 371 */ 372 public abstract void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args); 373 374 /** 375 * Control whether the framework's internal fragment manager debugging 376 * logs are turned on. If enabled, you will see output in logcat as 377 * the framework performs fragment operations. 378 */ 379 public static void enableDebugLogging(boolean enabled) { 380 FragmentManagerImpl.DEBUG = enabled; 381 } 382} 383 384final class FragmentManagerState implements Parcelable { 385 FragmentState[] mActive; 386 int[] mAdded; 387 BackStackState[] mBackStack; 388 389 public FragmentManagerState() { 390 } 391 392 public FragmentManagerState(Parcel in) { 393 mActive = in.createTypedArray(FragmentState.CREATOR); 394 mAdded = in.createIntArray(); 395 mBackStack = in.createTypedArray(BackStackState.CREATOR); 396 } 397 398 @Override 399 public int describeContents() { 400 return 0; 401 } 402 403 @Override 404 public void writeToParcel(Parcel dest, int flags) { 405 dest.writeTypedArray(mActive, flags); 406 dest.writeIntArray(mAdded); 407 dest.writeTypedArray(mBackStack, flags); 408 } 409 410 public static final Parcelable.Creator<FragmentManagerState> CREATOR 411 = new Parcelable.Creator<FragmentManagerState>() { 412 @Override 413 public FragmentManagerState createFromParcel(Parcel in) { 414 return new FragmentManagerState(in); 415 } 416 417 @Override 418 public FragmentManagerState[] newArray(int size) { 419 return new FragmentManagerState[size]; 420 } 421 }; 422} 423 424/** 425 * Container for fragments associated with an activity. 426 */ 427final class FragmentManagerImpl extends FragmentManager implements LayoutInflaterFactory { 428 static boolean DEBUG = false; 429 static final String TAG = "FragmentManager"; 430 431 static final boolean HONEYCOMB = android.os.Build.VERSION.SDK_INT >= 11; 432 433 static final String TARGET_REQUEST_CODE_STATE_TAG = "android:target_req_state"; 434 static final String TARGET_STATE_TAG = "android:target_state"; 435 static final String VIEW_STATE_TAG = "android:view_state"; 436 static final String USER_VISIBLE_HINT_TAG = "android:user_visible_hint"; 437 438 static class AnimateOnHWLayerIfNeededListener implements AnimationListener { 439 private AnimationListener mOriginalListener; 440 private boolean mShouldRunOnHWLayer; 441 View mView; 442 443 public AnimateOnHWLayerIfNeededListener(final View v, Animation anim) { 444 if (v == null || anim == null) { 445 return; 446 } 447 mView = v; 448 } 449 450 public AnimateOnHWLayerIfNeededListener(final View v, Animation anim, 451 AnimationListener listener) { 452 if (v == null || anim == null) { 453 return; 454 } 455 mOriginalListener = listener; 456 mView = v; 457 mShouldRunOnHWLayer = true; 458 } 459 460 @Override 461 @CallSuper 462 public void onAnimationStart(Animation animation) { 463 if (mOriginalListener != null) { 464 mOriginalListener.onAnimationStart(animation); 465 } 466 } 467 468 @Override 469 @CallSuper 470 public void onAnimationEnd(Animation animation) { 471 if (mView != null && mShouldRunOnHWLayer) { 472 // If we're attached to a window, assume we're in the normal performTraversals 473 // drawing path for Animations running. It's not safe to change the layer type 474 // during drawing, so post it to the View to run later. If we're not attached 475 // or we're running on N and above, post it to the view. If we're not on N and 476 // not attached, do it right now since existing platform versions don't run the 477 // hwui renderer for detached views off the UI thread making changing layer type 478 // safe, but posting may not be. 479 // Prior to N posting to a detached view from a non-Looper thread could cause 480 // leaks, since the thread-local run queue on a non-Looper thread would never 481 // be flushed. 482 if (ViewCompat.isAttachedToWindow(mView) || BuildCompat.isAtLeastN()) { 483 mView.post(new Runnable() { 484 @Override 485 public void run() { 486 ViewCompat.setLayerType(mView, ViewCompat.LAYER_TYPE_NONE, null); 487 } 488 }); 489 } else { 490 ViewCompat.setLayerType(mView, ViewCompat.LAYER_TYPE_NONE, null); 491 } 492 } 493 if (mOriginalListener != null) { 494 mOriginalListener.onAnimationEnd(animation); 495 } 496 } 497 498 @Override 499 public void onAnimationRepeat(Animation animation) { 500 if (mOriginalListener != null) { 501 mOriginalListener.onAnimationRepeat(animation); 502 } 503 } 504 } 505 506 ArrayList<OpGenerator> mPendingActions; 507 Runnable[] mTmpActions; 508 boolean mExecutingActions; 509 510 ArrayList<Fragment> mActive; 511 ArrayList<Fragment> mAdded; 512 ArrayList<Integer> mAvailIndices; 513 ArrayList<BackStackRecord> mBackStack; 514 ArrayList<Fragment> mCreatedMenus; 515 516 // Must be accessed while locked. 517 ArrayList<BackStackRecord> mBackStackIndices; 518 ArrayList<Integer> mAvailBackStackIndices; 519 520 ArrayList<OnBackStackChangedListener> mBackStackChangeListeners; 521 522 int mCurState = Fragment.INITIALIZING; 523 FragmentHostCallback mHost; 524 FragmentController mController; 525 FragmentContainer mContainer; 526 Fragment mParent; 527 528 static Field sAnimationListenerField = null; 529 530 boolean mNeedMenuInvalidate; 531 boolean mStateSaved; 532 boolean mDestroyed; 533 String mNoTransactionsBecause; 534 boolean mHavePendingDeferredStart; 535 536 // Temporary vars for optimizing execution of BackStackRecords: 537 ArrayList<BackStackRecord> mTmpRecords; 538 ArrayList<Boolean> mTmpIsPop; 539 ArrayList<Fragment> mTmpAddedFragments; 540 541 // Temporary vars for state save and restore. 542 Bundle mStateBundle = null; 543 SparseArray<Parcelable> mStateArray = null; 544 545 // Postponed transactions. 546 ArrayList<StartEnterTransitionListener> mPostponedTransactions; 547 548 Runnable mExecCommit = new Runnable() { 549 @Override 550 public void run() { 551 execPendingActions(); 552 } 553 }; 554 555 static boolean modifiesAlpha(Animation anim) { 556 if (anim instanceof AlphaAnimation) { 557 return true; 558 } else if (anim instanceof AnimationSet) { 559 List<Animation> anims = ((AnimationSet) anim).getAnimations(); 560 for (int i = 0; i < anims.size(); i++) { 561 if (anims.get(i) instanceof AlphaAnimation) { 562 return true; 563 } 564 } 565 } 566 return false; 567 } 568 569 static boolean shouldRunOnHWLayer(View v, Animation anim) { 570 return Build.VERSION.SDK_INT >= 19 571 && ViewCompat.getLayerType(v) == ViewCompat.LAYER_TYPE_NONE 572 && ViewCompat.hasOverlappingRendering(v) 573 && modifiesAlpha(anim); 574 } 575 576 private void throwException(RuntimeException ex) { 577 Log.e(TAG, ex.getMessage()); 578 Log.e(TAG, "Activity state:"); 579 LogWriter logw = new LogWriter(TAG); 580 PrintWriter pw = new PrintWriter(logw); 581 if (mHost != null) { 582 try { 583 mHost.onDump(" ", null, pw, new String[] { }); 584 } catch (Exception e) { 585 Log.e(TAG, "Failed dumping state", e); 586 } 587 } else { 588 try { 589 dump(" ", null, pw, new String[] { }); 590 } catch (Exception e) { 591 Log.e(TAG, "Failed dumping state", e); 592 } 593 } 594 throw ex; 595 } 596 597 @Override 598 public FragmentTransaction beginTransaction() { 599 return new BackStackRecord(this); 600 } 601 602 @Override 603 public boolean executePendingTransactions() { 604 boolean updates = execPendingActions(); 605 forcePostponedTransactions(); 606 return updates; 607 } 608 609 @Override 610 public void popBackStack() { 611 enqueueAction(new PopBackStackState(null, -1, 0), false); 612 } 613 614 @Override 615 public boolean popBackStackImmediate() { 616 checkStateLoss(); 617 return popBackStackImmediate(null, -1, 0); 618 } 619 620 @Override 621 public void popBackStack(final String name, final int flags) { 622 enqueueAction(new PopBackStackState(name, -1, flags), false); 623 } 624 625 @Override 626 public boolean popBackStackImmediate(String name, int flags) { 627 checkStateLoss(); 628 return popBackStackImmediate(name, -1, flags); 629 } 630 631 @Override 632 public void popBackStack(final int id, final int flags) { 633 if (id < 0) { 634 throw new IllegalArgumentException("Bad id: " + id); 635 } 636 enqueueAction(new PopBackStackState(null, id, flags), false); 637 } 638 639 @Override 640 public boolean popBackStackImmediate(int id, int flags) { 641 checkStateLoss(); 642 execPendingActions(); 643 if (id < 0) { 644 throw new IllegalArgumentException("Bad id: " + id); 645 } 646 return popBackStackImmediate(null, id, flags); 647 } 648 649 /** 650 * Used by all public popBackStackImmediate methods, this executes pending transactions and 651 * returns true if the pop action did anything, regardless of what other pending 652 * transactions did. 653 * 654 * @return true if the pop operation did anything or false otherwise. 655 */ 656 private boolean popBackStackImmediate(String name, int id, int flags) { 657 execPendingActions(); 658 ensureExecReady(true); 659 660 boolean executePop = popBackStackState(mTmpRecords, mTmpIsPop, name, id, flags); 661 if (executePop) { 662 mExecutingActions = true; 663 try { 664 optimizeAndExecuteOps(mTmpRecords, mTmpIsPop); 665 } finally { 666 cleanupExec(); 667 } 668 } 669 670 doPendingDeferredStart(); 671 return executePop; 672 } 673 674 @Override 675 public int getBackStackEntryCount() { 676 return mBackStack != null ? mBackStack.size() : 0; 677 } 678 679 @Override 680 public BackStackEntry getBackStackEntryAt(int index) { 681 return mBackStack.get(index); 682 } 683 684 @Override 685 public void addOnBackStackChangedListener(OnBackStackChangedListener listener) { 686 if (mBackStackChangeListeners == null) { 687 mBackStackChangeListeners = new ArrayList<OnBackStackChangedListener>(); 688 } 689 mBackStackChangeListeners.add(listener); 690 } 691 692 @Override 693 public void removeOnBackStackChangedListener(OnBackStackChangedListener listener) { 694 if (mBackStackChangeListeners != null) { 695 mBackStackChangeListeners.remove(listener); 696 } 697 } 698 699 @Override 700 public void putFragment(Bundle bundle, String key, Fragment fragment) { 701 if (fragment.mIndex < 0) { 702 throwException(new IllegalStateException("Fragment " + fragment 703 + " is not currently in the FragmentManager")); 704 } 705 bundle.putInt(key, fragment.mIndex); 706 } 707 708 @Override 709 public Fragment getFragment(Bundle bundle, String key) { 710 int index = bundle.getInt(key, -1); 711 if (index == -1) { 712 return null; 713 } 714 if (index >= mActive.size()) { 715 throwException(new IllegalStateException("Fragment no longer exists for key " 716 + key + ": index " + index)); 717 } 718 Fragment f = mActive.get(index); 719 if (f == null) { 720 throwException(new IllegalStateException("Fragment no longer exists for key " 721 + key + ": index " + index)); 722 } 723 return f; 724 } 725 726 @Override 727 public List<Fragment> getFragments() { 728 return mActive; 729 } 730 731 @Override 732 public Fragment.SavedState saveFragmentInstanceState(Fragment fragment) { 733 if (fragment.mIndex < 0) { 734 throwException( new IllegalStateException("Fragment " + fragment 735 + " is not currently in the FragmentManager")); 736 } 737 if (fragment.mState > Fragment.INITIALIZING) { 738 Bundle result = saveFragmentBasicState(fragment); 739 return result != null ? new Fragment.SavedState(result) : null; 740 } 741 return null; 742 } 743 744 @Override 745 public boolean isDestroyed() { 746 return mDestroyed; 747 } 748 749 @Override 750 public String toString() { 751 StringBuilder sb = new StringBuilder(128); 752 sb.append("FragmentManager{"); 753 sb.append(Integer.toHexString(System.identityHashCode(this))); 754 sb.append(" in "); 755 if (mParent != null) { 756 DebugUtils.buildShortClassTag(mParent, sb); 757 } else { 758 DebugUtils.buildShortClassTag(mHost, sb); 759 } 760 sb.append("}}"); 761 return sb.toString(); 762 } 763 764 @Override 765 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 766 String innerPrefix = prefix + " "; 767 768 int N; 769 if (mActive != null) { 770 N = mActive.size(); 771 if (N > 0) { 772 writer.print(prefix); writer.print("Active Fragments in "); 773 writer.print(Integer.toHexString(System.identityHashCode(this))); 774 writer.println(":"); 775 for (int i=0; i<N; i++) { 776 Fragment f = mActive.get(i); 777 writer.print(prefix); writer.print(" #"); writer.print(i); 778 writer.print(": "); writer.println(f); 779 if (f != null) { 780 f.dump(innerPrefix, fd, writer, args); 781 } 782 } 783 } 784 } 785 786 if (mAdded != null) { 787 N = mAdded.size(); 788 if (N > 0) { 789 writer.print(prefix); writer.println("Added Fragments:"); 790 for (int i=0; i<N; i++) { 791 Fragment f = mAdded.get(i); 792 writer.print(prefix); writer.print(" #"); writer.print(i); 793 writer.print(": "); writer.println(f.toString()); 794 } 795 } 796 } 797 798 if (mCreatedMenus != null) { 799 N = mCreatedMenus.size(); 800 if (N > 0) { 801 writer.print(prefix); writer.println("Fragments Created Menus:"); 802 for (int i=0; i<N; i++) { 803 Fragment f = mCreatedMenus.get(i); 804 writer.print(prefix); writer.print(" #"); writer.print(i); 805 writer.print(": "); writer.println(f.toString()); 806 } 807 } 808 } 809 810 if (mBackStack != null) { 811 N = mBackStack.size(); 812 if (N > 0) { 813 writer.print(prefix); writer.println("Back Stack:"); 814 for (int i=0; i<N; i++) { 815 BackStackRecord bs = mBackStack.get(i); 816 writer.print(prefix); writer.print(" #"); writer.print(i); 817 writer.print(": "); writer.println(bs.toString()); 818 bs.dump(innerPrefix, fd, writer, args); 819 } 820 } 821 } 822 823 synchronized (this) { 824 if (mBackStackIndices != null) { 825 N = mBackStackIndices.size(); 826 if (N > 0) { 827 writer.print(prefix); writer.println("Back Stack Indices:"); 828 for (int i=0; i<N; i++) { 829 BackStackRecord bs = mBackStackIndices.get(i); 830 writer.print(prefix); writer.print(" #"); writer.print(i); 831 writer.print(": "); writer.println(bs); 832 } 833 } 834 } 835 836 if (mAvailBackStackIndices != null && mAvailBackStackIndices.size() > 0) { 837 writer.print(prefix); writer.print("mAvailBackStackIndices: "); 838 writer.println(Arrays.toString(mAvailBackStackIndices.toArray())); 839 } 840 } 841 842 if (mPendingActions != null) { 843 N = mPendingActions.size(); 844 if (N > 0) { 845 writer.print(prefix); writer.println("Pending Actions:"); 846 for (int i=0; i<N; i++) { 847 OpGenerator r = mPendingActions.get(i); 848 writer.print(prefix); writer.print(" #"); writer.print(i); 849 writer.print(": "); writer.println(r); 850 } 851 } 852 } 853 854 writer.print(prefix); writer.println("FragmentManager misc state:"); 855 writer.print(prefix); writer.print(" mHost="); writer.println(mHost); 856 writer.print(prefix); writer.print(" mContainer="); writer.println(mContainer); 857 if (mParent != null) { 858 writer.print(prefix); writer.print(" mParent="); writer.println(mParent); 859 } 860 writer.print(prefix); writer.print(" mCurState="); writer.print(mCurState); 861 writer.print(" mStateSaved="); writer.print(mStateSaved); 862 writer.print(" mDestroyed="); writer.println(mDestroyed); 863 if (mNeedMenuInvalidate) { 864 writer.print(prefix); writer.print(" mNeedMenuInvalidate="); 865 writer.println(mNeedMenuInvalidate); 866 } 867 if (mNoTransactionsBecause != null) { 868 writer.print(prefix); writer.print(" mNoTransactionsBecause="); 869 writer.println(mNoTransactionsBecause); 870 } 871 if (mAvailIndices != null && mAvailIndices.size() > 0) { 872 writer.print(prefix); writer.print(" mAvailIndices: "); 873 writer.println(Arrays.toString(mAvailIndices.toArray())); 874 } 875 } 876 877 static final Interpolator DECELERATE_QUINT = new DecelerateInterpolator(2.5f); 878 static final Interpolator DECELERATE_CUBIC = new DecelerateInterpolator(1.5f); 879 static final Interpolator ACCELERATE_QUINT = new AccelerateInterpolator(2.5f); 880 static final Interpolator ACCELERATE_CUBIC = new AccelerateInterpolator(1.5f); 881 882 static final int ANIM_DUR = 220; 883 884 static Animation makeOpenCloseAnimation(Context context, float startScale, 885 float endScale, float startAlpha, float endAlpha) { 886 AnimationSet set = new AnimationSet(false); 887 ScaleAnimation scale = new ScaleAnimation(startScale, endScale, startScale, endScale, 888 Animation.RELATIVE_TO_SELF, .5f, Animation.RELATIVE_TO_SELF, .5f); 889 scale.setInterpolator(DECELERATE_QUINT); 890 scale.setDuration(ANIM_DUR); 891 set.addAnimation(scale); 892 AlphaAnimation alpha = new AlphaAnimation(startAlpha, endAlpha); 893 alpha.setInterpolator(DECELERATE_CUBIC); 894 alpha.setDuration(ANIM_DUR); 895 set.addAnimation(alpha); 896 return set; 897 } 898 899 static Animation makeFadeAnimation(Context context, float start, float end) { 900 AlphaAnimation anim = new AlphaAnimation(start, end); 901 anim.setInterpolator(DECELERATE_CUBIC); 902 anim.setDuration(ANIM_DUR); 903 return anim; 904 } 905 906 Animation loadAnimation(Fragment fragment, int transit, boolean enter, 907 int transitionStyle) { 908 Animation animObj = fragment.onCreateAnimation(transit, enter, fragment.getNextAnim()); 909 if (animObj != null) { 910 return animObj; 911 } 912 913 if (fragment.getNextAnim() != 0) { 914 Animation anim = AnimationUtils.loadAnimation(mHost.getContext(), 915 fragment.getNextAnim()); 916 if (anim != null) { 917 return anim; 918 } 919 } 920 921 if (transit == 0) { 922 return null; 923 } 924 925 int styleIndex = transitToStyleIndex(transit, enter); 926 if (styleIndex < 0) { 927 return null; 928 } 929 930 switch (styleIndex) { 931 case ANIM_STYLE_OPEN_ENTER: 932 return makeOpenCloseAnimation(mHost.getContext(), 1.125f, 1.0f, 0, 1); 933 case ANIM_STYLE_OPEN_EXIT: 934 return makeOpenCloseAnimation(mHost.getContext(), 1.0f, .975f, 1, 0); 935 case ANIM_STYLE_CLOSE_ENTER: 936 return makeOpenCloseAnimation(mHost.getContext(), .975f, 1.0f, 0, 1); 937 case ANIM_STYLE_CLOSE_EXIT: 938 return makeOpenCloseAnimation(mHost.getContext(), 1.0f, 1.075f, 1, 0); 939 case ANIM_STYLE_FADE_ENTER: 940 return makeFadeAnimation(mHost.getContext(), 0, 1); 941 case ANIM_STYLE_FADE_EXIT: 942 return makeFadeAnimation(mHost.getContext(), 1, 0); 943 } 944 945 if (transitionStyle == 0 && mHost.onHasWindowAnimations()) { 946 transitionStyle = mHost.onGetWindowAnimations(); 947 } 948 if (transitionStyle == 0) { 949 return null; 950 } 951 952 //TypedArray attrs = mActivity.obtainStyledAttributes(transitionStyle, 953 // com.android.internal.R.styleable.FragmentAnimation); 954 //int anim = attrs.getResourceId(styleIndex, 0); 955 //attrs.recycle(); 956 957 //if (anim == 0) { 958 // return null; 959 //} 960 961 //return AnimatorInflater.loadAnimator(mActivity, anim); 962 return null; 963 } 964 965 public void performPendingDeferredStart(Fragment f) { 966 if (f.mDeferStart) { 967 if (mExecutingActions) { 968 // Wait until we're done executing our pending transactions 969 mHavePendingDeferredStart = true; 970 return; 971 } 972 f.mDeferStart = false; 973 moveToState(f, mCurState, 0, 0, false); 974 } 975 } 976 977 /** 978 * Sets the to be animated view on hardware layer during the animation. Note 979 * that calling this will replace any existing animation listener on the animation 980 * with a new one, as animations do not support more than one listeners. Therefore, 981 * animations that already have listeners should do the layer change operations 982 * in their existing listeners, rather than calling this function. 983 */ 984 private void setHWLayerAnimListenerIfAlpha(final View v, Animation anim) { 985 if (v == null || anim == null) { 986 return; 987 } 988 if (shouldRunOnHWLayer(v, anim)) { 989 AnimationListener originalListener = null; 990 try { 991 if (sAnimationListenerField == null) { 992 sAnimationListenerField = Animation.class.getDeclaredField("mListener"); 993 sAnimationListenerField.setAccessible(true); 994 } 995 originalListener = (AnimationListener) sAnimationListenerField.get(anim); 996 } catch (NoSuchFieldException e) { 997 Log.e(TAG, "No field with the name mListener is found in Animation class", e); 998 } catch (IllegalAccessException e) { 999 Log.e(TAG, "Cannot access Animation's mListener field", e); 1000 } 1001 // If there's already a listener set on the animation, we need wrap the new listener 1002 // around the existing listener, so that they will both get animation listener 1003 // callbacks. 1004 ViewCompat.setLayerType(v, ViewCompat.LAYER_TYPE_HARDWARE, null); 1005 anim.setAnimationListener(new AnimateOnHWLayerIfNeededListener(v, anim, 1006 originalListener)); 1007 } 1008 } 1009 1010 boolean isStateAtLeast(int state) { 1011 return mCurState >= state; 1012 } 1013 1014 void moveToState(Fragment f, int newState, int transit, int transitionStyle, 1015 boolean keepActive) { 1016 // Fragments that are not currently added will sit in the onCreate() state. 1017 if ((!f.mAdded || f.mDetached) && newState > Fragment.CREATED) { 1018 newState = Fragment.CREATED; 1019 } 1020 if (f.mRemoving && newState > f.mState) { 1021 // While removing a fragment, we can't change it to a higher state. 1022 newState = f.mState; 1023 } 1024 // Defer start if requested; don't allow it to move to STARTED or higher 1025 // if it's not already started. 1026 if (f.mDeferStart && f.mState < Fragment.STARTED && newState > Fragment.STOPPED) { 1027 newState = Fragment.STOPPED; 1028 } 1029 if (f.mState < newState) { 1030 // For fragments that are created from a layout, when restoring from 1031 // state we don't want to allow them to be created until they are 1032 // being reloaded from the layout. 1033 if (f.mFromLayout && !f.mInLayout) { 1034 return; 1035 } 1036 if (f.getAnimatingAway() != null) { 1037 // The fragment is currently being animated... but! Now we 1038 // want to move our state back up. Give up on waiting for the 1039 // animation, move to whatever the final state should be once 1040 // the animation is done, and then we can proceed from there. 1041 f.setAnimatingAway(null); 1042 moveToState(f, f.getStateAfterAnimating(), 0, 0, true); 1043 } 1044 switch (f.mState) { 1045 case Fragment.INITIALIZING: 1046 if (DEBUG) Log.v(TAG, "moveto CREATED: " + f); 1047 if (f.mSavedFragmentState != null) { 1048 f.mSavedFragmentState.setClassLoader(mHost.getContext().getClassLoader()); 1049 f.mSavedViewState = f.mSavedFragmentState.getSparseParcelableArray( 1050 FragmentManagerImpl.VIEW_STATE_TAG); 1051 f.mTarget = getFragment(f.mSavedFragmentState, 1052 FragmentManagerImpl.TARGET_STATE_TAG); 1053 if (f.mTarget != null) { 1054 f.mTargetRequestCode = f.mSavedFragmentState.getInt( 1055 FragmentManagerImpl.TARGET_REQUEST_CODE_STATE_TAG, 0); 1056 } 1057 f.mUserVisibleHint = f.mSavedFragmentState.getBoolean( 1058 FragmentManagerImpl.USER_VISIBLE_HINT_TAG, true); 1059 if (!f.mUserVisibleHint) { 1060 f.mDeferStart = true; 1061 if (newState > Fragment.STOPPED) { 1062 newState = Fragment.STOPPED; 1063 } 1064 } 1065 } 1066 f.mHost = mHost; 1067 f.mParentFragment = mParent; 1068 f.mFragmentManager = mParent != null 1069 ? mParent.mChildFragmentManager : mHost.getFragmentManagerImpl(); 1070 f.mCalled = false; 1071 f.onAttach(mHost.getContext()); 1072 if (!f.mCalled) { 1073 throw new SuperNotCalledException("Fragment " + f 1074 + " did not call through to super.onAttach()"); 1075 } 1076 if (f.mParentFragment == null) { 1077 mHost.onAttachFragment(f); 1078 } else { 1079 f.mParentFragment.onAttachFragment(f); 1080 } 1081 1082 if (!f.mRetaining) { 1083 f.performCreate(f.mSavedFragmentState); 1084 } else { 1085 f.restoreChildFragmentState(f.mSavedFragmentState); 1086 f.mState = Fragment.CREATED; 1087 } 1088 f.mRetaining = false; 1089 if (f.mFromLayout) { 1090 // For fragments that are part of the content view 1091 // layout, we need to instantiate the view immediately 1092 // and the inflater will take care of adding it. 1093 f.mView = f.performCreateView(f.getLayoutInflater( 1094 f.mSavedFragmentState), null, f.mSavedFragmentState); 1095 if (f.mView != null) { 1096 f.mInnerView = f.mView; 1097 if (Build.VERSION.SDK_INT >= 11) { 1098 ViewCompat.setSaveFromParentEnabled(f.mView, false); 1099 } else { 1100 f.mView = NoSaveStateFrameLayout.wrap(f.mView); 1101 } 1102 if (f.mHidden) f.mView.setVisibility(View.GONE); 1103 f.onViewCreated(f.mView, f.mSavedFragmentState); 1104 } else { 1105 f.mInnerView = null; 1106 } 1107 } 1108 case Fragment.CREATED: 1109 if (newState > Fragment.CREATED) { 1110 if (DEBUG) Log.v(TAG, "moveto ACTIVITY_CREATED: " + f); 1111 if (!f.mFromLayout) { 1112 ViewGroup container = null; 1113 if (f.mContainerId != 0) { 1114 if (f.mContainerId == View.NO_ID) { 1115 throwException(new IllegalArgumentException( 1116 "Cannot create fragment " 1117 + f 1118 + " for a container view with no id")); 1119 } 1120 container = (ViewGroup) mContainer.onFindViewById(f.mContainerId); 1121 if (container == null && !f.mRestored) { 1122 String resName; 1123 try { 1124 resName = f.getResources().getResourceName(f.mContainerId); 1125 } catch (NotFoundException e) { 1126 resName = "unknown"; 1127 } 1128 throwException(new IllegalArgumentException( 1129 "No view found for id 0x" 1130 + Integer.toHexString(f.mContainerId) + " (" 1131 + resName 1132 + ") for fragment " + f)); 1133 } 1134 } 1135 f.mContainer = container; 1136 f.mView = f.performCreateView(f.getLayoutInflater( 1137 f.mSavedFragmentState), container, f.mSavedFragmentState); 1138 if (f.mView != null) { 1139 f.mInnerView = f.mView; 1140 if (Build.VERSION.SDK_INT >= 11) { 1141 ViewCompat.setSaveFromParentEnabled(f.mView, false); 1142 } else { 1143 f.mView = NoSaveStateFrameLayout.wrap(f.mView); 1144 } 1145 if (container != null) { 1146 container.addView(f.mView); 1147 f.mIsNewlyAdded = true; 1148 } 1149 if (f.mHidden) { 1150 f.mView.setVisibility(View.GONE); 1151 f.mIsNewlyAdded = false; // No animation 1152 } 1153 f.onViewCreated(f.mView, f.mSavedFragmentState); 1154 } else { 1155 f.mInnerView = null; 1156 } 1157 } 1158 1159 f.performActivityCreated(f.mSavedFragmentState); 1160 if (f.mView != null) { 1161 f.restoreViewState(f.mSavedFragmentState); 1162 } 1163 f.mSavedFragmentState = null; 1164 } 1165 case Fragment.ACTIVITY_CREATED: 1166 if (newState > Fragment.ACTIVITY_CREATED) { 1167 f.mState = Fragment.STOPPED; 1168 } 1169 case Fragment.STOPPED: 1170 if (newState > Fragment.STOPPED) { 1171 if (DEBUG) Log.v(TAG, "moveto STARTED: " + f); 1172 f.performStart(); 1173 } 1174 case Fragment.STARTED: 1175 if (newState > Fragment.STARTED) { 1176 if (DEBUG) Log.v(TAG, "moveto RESUMED: " + f); 1177 f.performResume(); 1178 f.mSavedFragmentState = null; 1179 f.mSavedViewState = null; 1180 } 1181 } 1182 } else if (f.mState > newState) { 1183 switch (f.mState) { 1184 case Fragment.RESUMED: 1185 if (newState < Fragment.RESUMED) { 1186 if (DEBUG) Log.v(TAG, "movefrom RESUMED: " + f); 1187 f.performPause(); 1188 } 1189 case Fragment.STARTED: 1190 if (newState < Fragment.STARTED) { 1191 if (DEBUG) Log.v(TAG, "movefrom STARTED: " + f); 1192 f.performStop(); 1193 } 1194 case Fragment.STOPPED: 1195 if (newState < Fragment.STOPPED) { 1196 if (DEBUG) Log.v(TAG, "movefrom STOPPED: " + f); 1197 f.performReallyStop(); 1198 } 1199 case Fragment.ACTIVITY_CREATED: 1200 if (newState < Fragment.ACTIVITY_CREATED) { 1201 if (DEBUG) Log.v(TAG, "movefrom ACTIVITY_CREATED: " + f); 1202 if (f.mView != null) { 1203 // Need to save the current view state if not 1204 // done already. 1205 if (mHost.onShouldSaveFragmentState(f) && f.mSavedViewState == null) { 1206 saveFragmentViewState(f); 1207 } 1208 } 1209 f.performDestroyView(); 1210 if (f.mView != null && f.mContainer != null) { 1211 Animation anim = null; 1212 if (mCurState > Fragment.INITIALIZING && !mDestroyed 1213 && f.mView.getVisibility() == View.VISIBLE) { 1214 anim = loadAnimation(f, transit, false, 1215 transitionStyle); 1216 } 1217 if (anim != null) { 1218 final Fragment fragment = f; 1219 f.setAnimatingAway(f.mView); 1220 f.setStateAfterAnimating(newState); 1221 final View viewToAnimate = f.mView; 1222 anim.setAnimationListener(new AnimateOnHWLayerIfNeededListener( 1223 viewToAnimate, anim) { 1224 @Override 1225 public void onAnimationEnd(Animation animation) { 1226 super.onAnimationEnd(animation); 1227 if (fragment.getAnimatingAway() != null) { 1228 fragment.setAnimatingAway(null); 1229 moveToState(fragment, fragment.getStateAfterAnimating(), 1230 0, 0, false); 1231 } 1232 } 1233 }); 1234 f.mView.startAnimation(anim); 1235 } 1236 f.mContainer.removeView(f.mView); 1237 } 1238 f.mContainer = null; 1239 f.mView = null; 1240 f.mInnerView = null; 1241 } 1242 case Fragment.CREATED: 1243 if (newState < Fragment.CREATED) { 1244 if (mDestroyed) { 1245 if (f.getAnimatingAway() != null) { 1246 // The fragment's containing activity is 1247 // being destroyed, but this fragment is 1248 // currently animating away. Stop the 1249 // animation right now -- it is not needed, 1250 // and we can't wait any more on destroying 1251 // the fragment. 1252 View v = f.getAnimatingAway(); 1253 f.setAnimatingAway(null); 1254 v.clearAnimation(); 1255 } 1256 } 1257 if (f.getAnimatingAway() != null) { 1258 // We are waiting for the fragment's view to finish 1259 // animating away. Just make a note of the state 1260 // the fragment now should move to once the animation 1261 // is done. 1262 f.setStateAfterAnimating(newState); 1263 newState = Fragment.CREATED; 1264 } else { 1265 if (DEBUG) Log.v(TAG, "movefrom CREATED: " + f); 1266 if (!f.mRetaining) { 1267 f.performDestroy(); 1268 } else { 1269 f.mState = Fragment.INITIALIZING; 1270 } 1271 1272 f.performDetach(); 1273 if (!keepActive) { 1274 if (!f.mRetaining) { 1275 makeInactive(f); 1276 } else { 1277 f.mHost = null; 1278 f.mParentFragment = null; 1279 f.mFragmentManager = null; 1280 } 1281 } 1282 } 1283 } 1284 } 1285 } 1286 1287 if (f.mState != newState) { 1288 Log.w(TAG, "moveToState: Fragment state for " + f + " not updated inline; " 1289 + "expected state " + newState + " found " + f.mState); 1290 f.mState = newState; 1291 } 1292 } 1293 1294 void moveToState(Fragment f) { 1295 moveToState(f, mCurState, 0, 0, false); 1296 } 1297 1298 /** 1299 * Fragments that have been shown or hidden don't have their visibility changed or 1300 * animations run during the {@link #showFragment(Fragment)} or {@link #hideFragment(Fragment)} 1301 * calls. After fragments are brought to their final state in 1302 * {@link #moveFragmentToExpectedState(Fragment)} the fragments that have been shown or 1303 * hidden must have their visibility changed and their animations started here. 1304 * 1305 * @param fragment The fragment with mHiddenChanged = true that should change its View's 1306 * visibility and start the show or hide animation. 1307 */ 1308 void completeShowHideFragment(final Fragment fragment) { 1309 if (fragment.mView != null) { 1310 Animation anim = loadAnimation(fragment, fragment.getNextTransition(), 1311 !fragment.mHidden, fragment.getNextTransitionStyle()); 1312 if (anim != null) { 1313 setHWLayerAnimListenerIfAlpha(fragment.mView, anim); 1314 fragment.mView.startAnimation(anim); 1315 setHWLayerAnimListenerIfAlpha(fragment.mView, anim); 1316 anim.start(); 1317 } 1318 final int visibility = fragment.mHidden ? View.GONE : View.VISIBLE; 1319 fragment.mView.setVisibility(visibility); 1320 } 1321 if (fragment.mAdded && fragment.mHasMenu && fragment.mMenuVisible) { 1322 mNeedMenuInvalidate = true; 1323 } 1324 fragment.mHiddenChanged = false; 1325 fragment.onHiddenChanged(fragment.mHidden); 1326 } 1327 1328 /** 1329 * Moves a fragment to its expected final state or the fragment manager's state, depending 1330 * on whether the fragment manager's state is raised properly. 1331 * 1332 * @param f The fragment to change. 1333 */ 1334 void moveFragmentToExpectedState(Fragment f) { 1335 if (f == null) { 1336 return; 1337 } 1338 int nextState = mCurState; 1339 if (f.mRemoving) { 1340 if (f.isInBackStack()) { 1341 nextState = Fragment.CREATED; 1342 } else { 1343 nextState = Fragment.INITIALIZING; 1344 } 1345 } 1346 moveToState(f, nextState, f.getNextTransition(), f.getNextTransitionStyle(), false); 1347 1348 if (f.mView != null) { 1349 // Move the view if it is out of order 1350 Fragment underFragment = findFragmentUnder(f); 1351 if (underFragment != null) { 1352 final View underView = underFragment.mView; 1353 // make sure this fragment is in the right order. 1354 final ViewGroup container = f.mContainer; 1355 int underIndex = container.indexOfChild(underView); 1356 int viewIndex = container.indexOfChild(f.mView); 1357 if (viewIndex < underIndex) { 1358 container.removeViewAt(viewIndex); 1359 container.addView(f.mView, underIndex); 1360 } 1361 } 1362 if (f.mIsNewlyAdded && f.mContainer != null) { 1363 // Make it visible and run the animations 1364 f.mView.setVisibility(View.VISIBLE); 1365 f.mIsNewlyAdded = false; 1366 // run animations: 1367 Animation anim = loadAnimation(f, f.getNextTransition(), true, 1368 f.getNextTransitionStyle()); 1369 if (anim != null) { 1370 setHWLayerAnimListenerIfAlpha(f.mView, anim); 1371 f.mView.startAnimation(anim); 1372 } 1373 } 1374 } 1375 if (f.mHiddenChanged) { 1376 completeShowHideFragment(f); 1377 } 1378 } 1379 1380 void moveToState(int newState) { 1381 if (mHost == null && newState != Fragment.INITIALIZING) { 1382 throw new IllegalStateException("No activity"); 1383 } 1384 1385 mCurState = newState; 1386 1387 if (mActive != null) { 1388 boolean loadersRunning = false; 1389 1390 // Must add them in the proper order. mActive fragments may be out of order 1391 if (mAdded != null) { 1392 final int numAdded = mAdded.size(); 1393 for (int i = 0; i < numAdded; i++) { 1394 Fragment f = mAdded.get(i); 1395 moveFragmentToExpectedState(f); 1396 if (f.mLoaderManager != null) { 1397 loadersRunning |= f.mLoaderManager.hasRunningLoaders(); 1398 } 1399 } 1400 } 1401 1402 // Now iterate through all active fragments. These will include those that are removed 1403 // and detached. 1404 final int numActive = mActive.size(); 1405 for (int i = 0; i < numActive; i++) { 1406 Fragment f = mActive.get(i); 1407 if (f != null && (f.mRemoving || f.mDetached) && !f.mIsNewlyAdded) { 1408 moveFragmentToExpectedState(f); 1409 if (f.mLoaderManager != null) { 1410 loadersRunning |= f.mLoaderManager.hasRunningLoaders(); 1411 } 1412 } 1413 } 1414 1415 if (!loadersRunning) { 1416 startPendingDeferredFragments(); 1417 } 1418 1419 if (mNeedMenuInvalidate && mHost != null && mCurState == Fragment.RESUMED) { 1420 mHost.onSupportInvalidateOptionsMenu(); 1421 mNeedMenuInvalidate = false; 1422 } 1423 } 1424 } 1425 1426 void startPendingDeferredFragments() { 1427 if (mActive == null) return; 1428 1429 for (int i=0; i<mActive.size(); i++) { 1430 Fragment f = mActive.get(i); 1431 if (f != null) { 1432 performPendingDeferredStart(f); 1433 } 1434 } 1435 } 1436 1437 void makeActive(Fragment f) { 1438 if (f.mIndex >= 0) { 1439 return; 1440 } 1441 1442 if (mAvailIndices == null || mAvailIndices.size() <= 0) { 1443 if (mActive == null) { 1444 mActive = new ArrayList<Fragment>(); 1445 } 1446 f.setIndex(mActive.size(), mParent); 1447 mActive.add(f); 1448 1449 } else { 1450 f.setIndex(mAvailIndices.remove(mAvailIndices.size()-1), mParent); 1451 mActive.set(f.mIndex, f); 1452 } 1453 if (DEBUG) Log.v(TAG, "Allocated fragment index " + f); 1454 } 1455 1456 void makeInactive(Fragment f) { 1457 if (f.mIndex < 0) { 1458 return; 1459 } 1460 1461 if (DEBUG) Log.v(TAG, "Freeing fragment index " + f); 1462 mActive.set(f.mIndex, null); 1463 if (mAvailIndices == null) { 1464 mAvailIndices = new ArrayList<Integer>(); 1465 } 1466 mAvailIndices.add(f.mIndex); 1467 mHost.inactivateFragment(f.mWho); 1468 f.initState(); 1469 } 1470 1471 public void addFragment(Fragment fragment, boolean moveToStateNow) { 1472 if (mAdded == null) { 1473 mAdded = new ArrayList<Fragment>(); 1474 } 1475 if (DEBUG) Log.v(TAG, "add: " + fragment); 1476 makeActive(fragment); 1477 if (!fragment.mDetached) { 1478 if (mAdded.contains(fragment)) { 1479 throw new IllegalStateException("Fragment already added: " + fragment); 1480 } 1481 mAdded.add(fragment); 1482 fragment.mAdded = true; 1483 fragment.mRemoving = false; 1484 if (fragment.mView == null) { 1485 fragment.mHiddenChanged = false; 1486 } 1487 if (fragment.mHasMenu && fragment.mMenuVisible) { 1488 mNeedMenuInvalidate = true; 1489 } 1490 if (moveToStateNow) { 1491 moveToState(fragment); 1492 } 1493 } 1494 } 1495 1496 public void removeFragment(Fragment fragment) { 1497 if (DEBUG) Log.v(TAG, "remove: " + fragment + " nesting=" + fragment.mBackStackNesting); 1498 final boolean inactive = !fragment.isInBackStack(); 1499 if (!fragment.mDetached || inactive) { 1500 if (mAdded != null) { 1501 mAdded.remove(fragment); 1502 } 1503 if (fragment.mHasMenu && fragment.mMenuVisible) { 1504 mNeedMenuInvalidate = true; 1505 } 1506 fragment.mAdded = false; 1507 fragment.mRemoving = true; 1508 } 1509 } 1510 1511 /** 1512 * Marks a fragment as hidden to be later animated in with 1513 * {@link #completeShowHideFragment(Fragment)}. 1514 * 1515 * @param fragment The fragment to be shown. 1516 */ 1517 public void hideFragment(Fragment fragment) { 1518 if (DEBUG) Log.v(TAG, "hide: " + fragment); 1519 if (!fragment.mHidden) { 1520 fragment.mHidden = true; 1521 // Toggle hidden changed so that if a fragment goes through show/hide/show 1522 // it doesn't go through the animation. 1523 fragment.mHiddenChanged = !fragment.mHiddenChanged; 1524 } 1525 } 1526 1527 /** 1528 * Marks a fragment as shown to be later animated in with 1529 * {@link #completeShowHideFragment(Fragment)}. 1530 * 1531 * @param fragment The fragment to be shown. 1532 */ 1533 public void showFragment(Fragment fragment) { 1534 if (DEBUG) Log.v(TAG, "show: " + fragment); 1535 if (fragment.mHidden) { 1536 fragment.mHidden = false; 1537 // Toggle hidden changed so that if a fragment goes through show/hide/show 1538 // it doesn't go through the animation. 1539 fragment.mHiddenChanged = !fragment.mHiddenChanged; 1540 } 1541 } 1542 1543 public void detachFragment(Fragment fragment) { 1544 if (DEBUG) Log.v(TAG, "detach: " + fragment); 1545 if (!fragment.mDetached) { 1546 fragment.mDetached = true; 1547 if (fragment.mAdded) { 1548 // We are not already in back stack, so need to remove the fragment. 1549 if (mAdded != null) { 1550 if (DEBUG) Log.v(TAG, "remove from detach: " + fragment); 1551 mAdded.remove(fragment); 1552 } 1553 if (fragment.mHasMenu && fragment.mMenuVisible) { 1554 mNeedMenuInvalidate = true; 1555 } 1556 fragment.mAdded = false; 1557 } 1558 } 1559 } 1560 1561 public void attachFragment(Fragment fragment) { 1562 if (DEBUG) Log.v(TAG, "attach: " + fragment); 1563 if (fragment.mDetached) { 1564 fragment.mDetached = false; 1565 if (!fragment.mAdded) { 1566 if (mAdded == null) { 1567 mAdded = new ArrayList<Fragment>(); 1568 } 1569 if (mAdded.contains(fragment)) { 1570 throw new IllegalStateException("Fragment already added: " + fragment); 1571 } 1572 if (DEBUG) Log.v(TAG, "add from attach: " + fragment); 1573 mAdded.add(fragment); 1574 fragment.mAdded = true; 1575 if (fragment.mHasMenu && fragment.mMenuVisible) { 1576 mNeedMenuInvalidate = true; 1577 } 1578 } 1579 } 1580 } 1581 1582 @Override 1583 public Fragment findFragmentById(int id) { 1584 if (mAdded != null) { 1585 // First look through added fragments. 1586 for (int i=mAdded.size()-1; i>=0; i--) { 1587 Fragment f = mAdded.get(i); 1588 if (f != null && f.mFragmentId == id) { 1589 return f; 1590 } 1591 } 1592 } 1593 if (mActive != null) { 1594 // Now for any known fragment. 1595 for (int i=mActive.size()-1; i>=0; i--) { 1596 Fragment f = mActive.get(i); 1597 if (f != null && f.mFragmentId == id) { 1598 return f; 1599 } 1600 } 1601 } 1602 return null; 1603 } 1604 1605 @Override 1606 public Fragment findFragmentByTag(String tag) { 1607 if (mAdded != null && tag != null) { 1608 // First look through added fragments. 1609 for (int i=mAdded.size()-1; i>=0; i--) { 1610 Fragment f = mAdded.get(i); 1611 if (f != null && tag.equals(f.mTag)) { 1612 return f; 1613 } 1614 } 1615 } 1616 if (mActive != null && tag != null) { 1617 // Now for any known fragment. 1618 for (int i=mActive.size()-1; i>=0; i--) { 1619 Fragment f = mActive.get(i); 1620 if (f != null && tag.equals(f.mTag)) { 1621 return f; 1622 } 1623 } 1624 } 1625 return null; 1626 } 1627 1628 public Fragment findFragmentByWho(String who) { 1629 if (mActive != null && who != null) { 1630 for (int i=mActive.size()-1; i>=0; i--) { 1631 Fragment f = mActive.get(i); 1632 if (f != null && (f=f.findFragmentByWho(who)) != null) { 1633 return f; 1634 } 1635 } 1636 } 1637 return null; 1638 } 1639 1640 private void checkStateLoss() { 1641 if (mStateSaved) { 1642 throw new IllegalStateException( 1643 "Can not perform this action after onSaveInstanceState"); 1644 } 1645 if (mNoTransactionsBecause != null) { 1646 throw new IllegalStateException( 1647 "Can not perform this action inside of " + mNoTransactionsBecause); 1648 } 1649 } 1650 1651 /** 1652 * Adds an action to the queue of pending actions. 1653 * 1654 * @param action the action to add 1655 * @param allowStateLoss whether to allow loss of state information 1656 * @throws IllegalStateException if the activity has been destroyed 1657 */ 1658 public void enqueueAction(OpGenerator action, boolean allowStateLoss) { 1659 if (!allowStateLoss) { 1660 checkStateLoss(); 1661 } 1662 synchronized (this) { 1663 if (mDestroyed || mHost == null) { 1664 throw new IllegalStateException("Activity has been destroyed"); 1665 } 1666 if (mPendingActions == null) { 1667 mPendingActions = new ArrayList<>(); 1668 } 1669 mPendingActions.add(action); 1670 scheduleCommit(); 1671 } 1672 } 1673 1674 /** 1675 * Schedules the execution when one hasn't been scheduled already. This should happen 1676 * the first time {@link #enqueueAction(OpGenerator, boolean)} is called or when 1677 * a postponed transaction has been started with 1678 * {@link Fragment#startPostponedEnterTransition()} 1679 */ 1680 private void scheduleCommit() { 1681 synchronized (this) { 1682 boolean postponeReady = 1683 mPostponedTransactions != null && !mPostponedTransactions.isEmpty(); 1684 boolean pendingReady = mPendingActions != null && mPendingActions.size() == 1; 1685 if (postponeReady || pendingReady) { 1686 mHost.getHandler().removeCallbacks(mExecCommit); 1687 mHost.getHandler().post(mExecCommit); 1688 } 1689 } 1690 } 1691 1692 public int allocBackStackIndex(BackStackRecord bse) { 1693 synchronized (this) { 1694 if (mAvailBackStackIndices == null || mAvailBackStackIndices.size() <= 0) { 1695 if (mBackStackIndices == null) { 1696 mBackStackIndices = new ArrayList<BackStackRecord>(); 1697 } 1698 int index = mBackStackIndices.size(); 1699 if (DEBUG) Log.v(TAG, "Setting back stack index " + index + " to " + bse); 1700 mBackStackIndices.add(bse); 1701 return index; 1702 1703 } else { 1704 int index = mAvailBackStackIndices.remove(mAvailBackStackIndices.size()-1); 1705 if (DEBUG) Log.v(TAG, "Adding back stack index " + index + " with " + bse); 1706 mBackStackIndices.set(index, bse); 1707 return index; 1708 } 1709 } 1710 } 1711 1712 public void setBackStackIndex(int index, BackStackRecord bse) { 1713 synchronized (this) { 1714 if (mBackStackIndices == null) { 1715 mBackStackIndices = new ArrayList<BackStackRecord>(); 1716 } 1717 int N = mBackStackIndices.size(); 1718 if (index < N) { 1719 if (DEBUG) Log.v(TAG, "Setting back stack index " + index + " to " + bse); 1720 mBackStackIndices.set(index, bse); 1721 } else { 1722 while (N < index) { 1723 mBackStackIndices.add(null); 1724 if (mAvailBackStackIndices == null) { 1725 mAvailBackStackIndices = new ArrayList<Integer>(); 1726 } 1727 if (DEBUG) Log.v(TAG, "Adding available back stack index " + N); 1728 mAvailBackStackIndices.add(N); 1729 N++; 1730 } 1731 if (DEBUG) Log.v(TAG, "Adding back stack index " + index + " with " + bse); 1732 mBackStackIndices.add(bse); 1733 } 1734 } 1735 } 1736 1737 public void freeBackStackIndex(int index) { 1738 synchronized (this) { 1739 mBackStackIndices.set(index, null); 1740 if (mAvailBackStackIndices == null) { 1741 mAvailBackStackIndices = new ArrayList<Integer>(); 1742 } 1743 if (DEBUG) Log.v(TAG, "Freeing back stack index " + index); 1744 mAvailBackStackIndices.add(index); 1745 } 1746 } 1747 1748 /** 1749 * Broken out from exec*, this prepares for gathering and executing operations. 1750 * 1751 * @param allowStateLoss true if state loss should be ignored or false if it should be 1752 * checked. 1753 */ 1754 private void ensureExecReady(boolean allowStateLoss) { 1755 if (mExecutingActions) { 1756 throw new IllegalStateException("FragmentManager is already executing transactions"); 1757 } 1758 1759 if (Looper.myLooper() != mHost.getHandler().getLooper()) { 1760 throw new IllegalStateException("Must be called from main thread of fragment host"); 1761 } 1762 1763 if (!allowStateLoss) { 1764 checkStateLoss(); 1765 } 1766 1767 if (mTmpRecords == null) { 1768 mTmpRecords = new ArrayList<>(); 1769 mTmpIsPop = new ArrayList<>(); 1770 } 1771 executePostponedTransaction(null, null); 1772 } 1773 1774 public void execSingleAction(OpGenerator action, boolean allowStateLoss) { 1775 ensureExecReady(allowStateLoss); 1776 if (action.generateOps(mTmpRecords, mTmpIsPop)) { 1777 mExecutingActions = true; 1778 try { 1779 optimizeAndExecuteOps(mTmpRecords, mTmpIsPop); 1780 } finally { 1781 cleanupExec(); 1782 } 1783 } 1784 1785 doPendingDeferredStart(); 1786 } 1787 1788 /** 1789 * Broken out of exec*, this cleans up the mExecutingActions and the temporary structures 1790 * used in executing operations. 1791 */ 1792 private void cleanupExec() { 1793 mExecutingActions = false; 1794 mTmpIsPop.clear(); 1795 mTmpRecords.clear(); 1796 } 1797 1798 /** 1799 * Only call from main thread! 1800 */ 1801 public boolean execPendingActions() { 1802 ensureExecReady(true); 1803 1804 boolean didSomething = false; 1805 while (generateOpsForPendingActions(mTmpRecords, mTmpIsPop)) { 1806 mExecutingActions = true; 1807 try { 1808 optimizeAndExecuteOps(mTmpRecords, mTmpIsPop); 1809 } finally { 1810 cleanupExec(); 1811 } 1812 didSomething = true; 1813 } 1814 1815 doPendingDeferredStart(); 1816 1817 return didSomething; 1818 } 1819 1820 /** 1821 * Complete the execution of transactions that have previously been postponed, but are 1822 * now ready. 1823 */ 1824 private void executePostponedTransaction(ArrayList<BackStackRecord> records, 1825 ArrayList<Boolean> isRecordPop) { 1826 int numPostponed = mPostponedTransactions == null ? 0 : mPostponedTransactions.size(); 1827 for (int i = 0; i < numPostponed; i++) { 1828 StartEnterTransitionListener listener = mPostponedTransactions.get(i); 1829 if (records != null && !listener.mIsBack) { 1830 int index = records.indexOf(listener.mRecord); 1831 if (index != -1 && isRecordPop.get(index)) { 1832 listener.cancelTransaction(); 1833 continue; 1834 } 1835 } 1836 if (listener.isReady() || (records != null 1837 && listener.mRecord.interactsWith(records, 0, records.size()))) { 1838 mPostponedTransactions.remove(i); 1839 i--; 1840 numPostponed--; 1841 int index; 1842 if (records != null && !listener.mIsBack 1843 && (index = records.indexOf(listener.mRecord)) != -1 1844 && isRecordPop.get(index)) { 1845 // This is popping a postponed transaction 1846 listener.cancelTransaction(); 1847 } else { 1848 listener.completeTransaction(); 1849 } 1850 } 1851 } 1852 } 1853 1854 /** 1855 * Optimizes BackStackRecord operations. This method merges operations of proximate records 1856 * that allow optimization. See {@link FragmentTransaction#setAllowOptimization(boolean)}. 1857 * <p> 1858 * For example, a transaction that adds to the back stack and then another that pops that 1859 * back stack record will be optimized. 1860 * <p> 1861 * Likewise, two transactions committed that are executed at the same time will be optimized 1862 * as well as two pop operations executed together. 1863 * 1864 * @param records The records pending execution 1865 * @param isRecordPop The direction that these records are being run. 1866 */ 1867 private void optimizeAndExecuteOps(ArrayList<BackStackRecord> records, 1868 ArrayList<Boolean> isRecordPop) { 1869 if (records == null || records.isEmpty()) { 1870 return; 1871 } 1872 1873 if (isRecordPop == null || records.size() != isRecordPop.size()) { 1874 throw new IllegalStateException("Internal error with the back stack records"); 1875 } 1876 1877 // Force start of any postponed transactions that interact with scheduled transactions: 1878 executePostponedTransaction(records, isRecordPop); 1879 1880 final int numRecords = records.size(); 1881 int startIndex = 0; 1882 for (int recordNum = 0; recordNum < numRecords; recordNum++) { 1883 final boolean canOptimize = records.get(recordNum).mAllowOptimization; 1884 if (!canOptimize) { 1885 // execute all previous transactions 1886 if (startIndex != recordNum) { 1887 executeOpsTogether(records, isRecordPop, startIndex, recordNum); 1888 } 1889 // execute all unoptimized together 1890 int optimizeEnd; 1891 for (optimizeEnd = recordNum + 1; optimizeEnd < numRecords; optimizeEnd++) { 1892 if (records.get(optimizeEnd).mAllowOptimization) { 1893 break; 1894 } 1895 } 1896 executeOpsTogether(records, isRecordPop, recordNum, optimizeEnd); 1897 startIndex = optimizeEnd; 1898 recordNum = optimizeEnd - 1; 1899 } 1900 } 1901 if (startIndex != numRecords) { 1902 executeOpsTogether(records, isRecordPop, startIndex, numRecords); 1903 } 1904 } 1905 1906 /** 1907 * Optimizes a subset of a list of BackStackRecords, all of which either allow optimization or 1908 * do not allow optimization. 1909 * @param records A list of BackStackRecords that are to be optimized 1910 * @param isRecordPop The direction that these records are being run. 1911 * @param startIndex The index of the first record in <code>records</code> to be optimized 1912 * @param endIndex One more than the final record index in <code>records</code> to optimize. 1913 */ 1914 private void executeOpsTogether(ArrayList<BackStackRecord> records, 1915 ArrayList<Boolean> isRecordPop, int startIndex, int endIndex) { 1916 final boolean allowOptimization = records.get(startIndex).mAllowOptimization; 1917 boolean addToBackStack = false; 1918 if (mTmpAddedFragments == null) { 1919 mTmpAddedFragments = new ArrayList<>(); 1920 } else { 1921 mTmpAddedFragments.clear(); 1922 } 1923 if (mAdded != null) { 1924 mTmpAddedFragments.addAll(mAdded); 1925 } 1926 for (int recordNum = startIndex; recordNum < endIndex; recordNum++) { 1927 final BackStackRecord record = records.get(recordNum); 1928 final boolean isPop = isRecordPop.get(recordNum); 1929 if (!isPop) { 1930 record.expandReplaceOps(mTmpAddedFragments); 1931 } 1932 final int bumpAmount = isPop ? -1 : 1; 1933 record.bumpBackStackNesting(bumpAmount); 1934 addToBackStack = addToBackStack || record.mAddToBackStack; 1935 } 1936 mTmpAddedFragments.clear(); 1937 1938 if (!allowOptimization) { 1939 FragmentTransition.startTransitions(this, records, isRecordPop, startIndex, endIndex, 1940 false); 1941 } 1942 executeOps(records, isRecordPop, startIndex, endIndex); 1943 1944 int postponeIndex = endIndex; 1945 if (allowOptimization) { 1946 moveFragmentsToInvisible(); 1947 postponeIndex = postponePostponableTransactions(records, isRecordPop, 1948 startIndex, endIndex); 1949 } 1950 1951 if (postponeIndex != startIndex && allowOptimization) { 1952 // need to run something now 1953 FragmentTransition.startTransitions(this, records, isRecordPop, startIndex, 1954 postponeIndex, true); 1955 moveToState(mCurState); 1956 } 1957 1958 for (int recordNum = startIndex; recordNum < endIndex; recordNum++) { 1959 final BackStackRecord record = records.get(recordNum); 1960 final boolean isPop = isRecordPop.get(recordNum); 1961 if (isPop && record.mIndex >= 0) { 1962 freeBackStackIndex(record.mIndex); 1963 record.mIndex = -1; 1964 } 1965 } 1966 if (addToBackStack) { 1967 reportBackStackChanged(); 1968 } 1969 } 1970 1971 /** 1972 * Examine all transactions and determine which ones are marked as postponed. Those will 1973 * have their operations rolled back and moved to the end of the record list (up to endIndex). 1974 * It will also add the postponed transaction to the queue. 1975 * 1976 * @param records A list of BackStackRecords that should be checked. 1977 * @param isRecordPop The direction that these records are being run. 1978 * @param startIndex The index of the first record in <code>records</code> to be checked 1979 * @param endIndex One more than the final record index in <code>records</code> to be checked. 1980 * @return The index of the first postponed transaction or endIndex if no transaction was 1981 * postponed. 1982 */ 1983 private int postponePostponableTransactions(ArrayList<BackStackRecord> records, 1984 ArrayList<Boolean> isRecordPop, int startIndex, int endIndex) { 1985 int postponeIndex = endIndex; 1986 for (int i = endIndex - 1; i >= startIndex; i--) { 1987 final BackStackRecord record = records.get(i); 1988 final boolean isPop = isRecordPop.get(i); 1989 boolean isPostponed = record.isPostponed() 1990 && !record.interactsWith(records, i + 1, endIndex); 1991 if (isPostponed) { 1992 if (mPostponedTransactions == null) { 1993 mPostponedTransactions = new ArrayList<>(); 1994 } 1995 StartEnterTransitionListener listener = 1996 new StartEnterTransitionListener(record, isPop); 1997 mPostponedTransactions.add(listener); 1998 record.setOnStartPostponedListener(listener); 1999 2000 // roll back the transaction 2001 if (isPop) { 2002 record.executeOps(); 2003 } else { 2004 record.executePopOps(); 2005 } 2006 2007 // move to the end 2008 postponeIndex--; 2009 if (i != postponeIndex) { 2010 records.remove(i); 2011 records.add(postponeIndex, record); 2012 } 2013 2014 // different views may be visible now 2015 moveFragmentsToInvisible(); 2016 } 2017 } 2018 return postponeIndex; 2019 } 2020 2021 /** 2022 * When a postponed transaction is ready to be started, this completes the transaction, 2023 * removing, hiding, or showing views as well as starting the animations and transitions. 2024 * <p> 2025 * {@code runtransitions} is set to false when the transaction postponement was interrupted 2026 * abnormally -- normally by a new transaction being started that affects the postponed 2027 * transaction. 2028 * 2029 * @param record The transaction to run 2030 * @param isPop true if record is popping or false if it is adding 2031 * @param runTransitions true if the fragment transition should be run or false otherwise. 2032 * @param moveToState true if the state should be changed after executing the operations. 2033 * This is false when the transaction is canceled when a postponed 2034 * transaction is popped. 2035 */ 2036 private void completeExecute(BackStackRecord record, boolean isPop, boolean runTransitions, 2037 boolean moveToState) { 2038 ArrayList<BackStackRecord> records = new ArrayList<>(1); 2039 ArrayList<Boolean> isRecordPop = new ArrayList<>(1); 2040 records.add(record); 2041 isRecordPop.add(isPop); 2042 executeOps(records, isRecordPop, 0, 1); 2043 if (runTransitions) { 2044 FragmentTransition.startTransitions(this, records, isRecordPop, 0, 1, true); 2045 } 2046 if (moveToState) { 2047 moveToState(mCurState); 2048 } else if (mActive != null) { 2049 final int numActive = mActive.size(); 2050 for (int i = 0; i < numActive; i++) { 2051 // Allow added fragments to be removed during the pop since we aren't going 2052 // to move them to the final state with moveToState(mCurState). 2053 Fragment fragment = mActive.get(i); 2054 if (fragment.mView != null && fragment.mIsNewlyAdded 2055 && record.interactsWith(fragment.mContainerId)) { 2056 fragment.mIsNewlyAdded = false; 2057 } 2058 } 2059 } 2060 } 2061 2062 /** 2063 * Find a fragment within the fragment's container whose View should be below the passed 2064 * fragment. {@code null} is returned when the fragment has no View or if there should be 2065 * no fragment with a View below the given fragment. 2066 * 2067 * As an example, if mAdded has two Fragments with Views sharing the same container: 2068 * FragmentA 2069 * FragmentB 2070 * 2071 * Then, when processing FragmentB, FragmentA will be returned. If, however, FragmentA 2072 * had no View, null would be returned. 2073 * 2074 * @param f The fragment that may be on top of another fragment. 2075 * @return The fragment with a View under f, if one exists or null if f has no View or 2076 * there are no fragments with Views in the same container. 2077 */ 2078 private Fragment findFragmentUnder(Fragment f) { 2079 final ViewGroup container = f.mContainer; 2080 final View view = f.mView; 2081 2082 if (container == null || view == null) { 2083 return null; 2084 } 2085 2086 final int fragmentIndex = mAdded.indexOf(f); 2087 for (int i = fragmentIndex - 1; i >= 0; i--) { 2088 Fragment underFragment = mAdded.get(i); 2089 if (underFragment.mContainer == container && underFragment.mView != null) { 2090 // Found the fragment under this one 2091 return underFragment; 2092 } 2093 } 2094 return null; 2095 } 2096 2097 /** 2098 * Run the operations in the BackStackRecords, either to push or pop. 2099 * 2100 * @param records The list of records whose operations should be run. 2101 * @param isRecordPop The direction that these records are being run. 2102 * @param startIndex The index of the first entry in records to run. 2103 * @param endIndex One past the index of the final entry in records to run. 2104 */ 2105 private static void executeOps(ArrayList<BackStackRecord> records, 2106 ArrayList<Boolean> isRecordPop, int startIndex, int endIndex) { 2107 for (int i = startIndex; i < endIndex; i++) { 2108 final BackStackRecord record = records.get(i); 2109 final boolean isPop = isRecordPop.get(i); 2110 if (isPop) { 2111 record.executePopOps(); 2112 } else { 2113 record.executeOps(); 2114 } 2115 } 2116 } 2117 2118 /** 2119 * Ensure that fragments that are added are moved to at least the CREATED state. 2120 * Any newly-added Views are made INVISIBLE so that the Transaction can be postponed 2121 * with {@link Fragment#postponeEnterTransition()}. 2122 */ 2123 private void moveFragmentsToInvisible() { 2124 if (mCurState < Fragment.CREATED) { 2125 return; 2126 } 2127 // We want to leave the fragment in the started state 2128 final int state = Math.min(mCurState, Fragment.STARTED); 2129 final int numAdded = mAdded == null ? 0 : mAdded.size(); 2130 for (int i = 0; i < numAdded; i++) { 2131 Fragment fragment = mAdded.get(i); 2132 if (fragment.mState < state) { 2133 moveToState(fragment, state, fragment.getNextAnim(), fragment.getNextTransition(), 2134 false); 2135 if (fragment.mView != null && !fragment.mHidden && fragment.mIsNewlyAdded) { 2136 fragment.mView.setVisibility(View.INVISIBLE); 2137 } 2138 } 2139 } 2140 } 2141 2142 /** 2143 * Starts all postponed transactions regardless of whether they are ready or not. 2144 */ 2145 private void forcePostponedTransactions() { 2146 if (mPostponedTransactions != null) { 2147 while (!mPostponedTransactions.isEmpty()) { 2148 mPostponedTransactions.remove(0).completeTransaction(); 2149 } 2150 } 2151 } 2152 2153 /** 2154 * Ends the animations of fragments so that they immediately reach the end state. 2155 * This is used prior to saving the state so that the correct state is saved. 2156 */ 2157 private void endAnimatingAwayFragments() { 2158 final int numFragments = mActive == null ? 0 : mActive.size(); 2159 for (int i = 0; i < numFragments; i++) { 2160 Fragment fragment = mActive.get(i); 2161 if (fragment != null && fragment.getAnimatingAway() != null) { 2162 // Give up waiting for the animation and just end it. 2163 final int stateAfterAnimating = fragment.getStateAfterAnimating(); 2164 final View animatingAway = fragment.getAnimatingAway(); 2165 fragment.setAnimatingAway(null); 2166 animatingAway.clearAnimation(); 2167 moveToState(fragment, stateAfterAnimating, 0, 0, false); 2168 } 2169 } 2170 } 2171 2172 /** 2173 * Adds all records in the pending actions to records and whether they are add or pop 2174 * operations to isPop. After executing, the pending actions will be empty. 2175 * 2176 * @param records All pending actions will generate BackStackRecords added to this. 2177 * This contains the transactions, in order, to execute. 2178 * @param isPop All pending actions will generate booleans to add to this. This contains 2179 * an entry for each entry in records to indicate whether or not it is a 2180 * pop action. 2181 */ 2182 private boolean generateOpsForPendingActions(ArrayList<BackStackRecord> records, 2183 ArrayList<Boolean> isPop) { 2184 int numActions; 2185 synchronized (this) { 2186 if (mPendingActions == null || mPendingActions.size() == 0) { 2187 return false; 2188 } 2189 2190 numActions = mPendingActions.size(); 2191 for (int i = 0; i < numActions; i++) { 2192 mPendingActions.get(i).generateOps(records, isPop); 2193 } 2194 mPendingActions.clear(); 2195 mHost.getHandler().removeCallbacks(mExecCommit); 2196 } 2197 return numActions > 0; 2198 } 2199 2200 void doPendingDeferredStart() { 2201 if (mHavePendingDeferredStart) { 2202 boolean loadersRunning = false; 2203 for (int i = 0; i < mActive.size(); i++) { 2204 Fragment f = mActive.get(i); 2205 if (f != null && f.mLoaderManager != null) { 2206 loadersRunning |= f.mLoaderManager.hasRunningLoaders(); 2207 } 2208 } 2209 if (!loadersRunning) { 2210 mHavePendingDeferredStart = false; 2211 startPendingDeferredFragments(); 2212 } 2213 } 2214 } 2215 2216 void reportBackStackChanged() { 2217 if (mBackStackChangeListeners != null) { 2218 for (int i=0; i<mBackStackChangeListeners.size(); i++) { 2219 mBackStackChangeListeners.get(i).onBackStackChanged(); 2220 } 2221 } 2222 } 2223 2224 void addBackStackState(BackStackRecord state) { 2225 if (mBackStack == null) { 2226 mBackStack = new ArrayList<BackStackRecord>(); 2227 } 2228 mBackStack.add(state); 2229 reportBackStackChanged(); 2230 } 2231 2232 @SuppressWarnings("unused") 2233 boolean popBackStackState(ArrayList<BackStackRecord> records, ArrayList<Boolean> isRecordPop, 2234 String name, int id, int flags) { 2235 if (mBackStack == null) { 2236 return false; 2237 } 2238 if (name == null && id < 0 && (flags & POP_BACK_STACK_INCLUSIVE) == 0) { 2239 int last = mBackStack.size() - 1; 2240 if (last < 0) { 2241 return false; 2242 } 2243 records.add(mBackStack.remove(last)); 2244 isRecordPop.add(true); 2245 } else { 2246 int index = -1; 2247 if (name != null || id >= 0) { 2248 // If a name or ID is specified, look for that place in 2249 // the stack. 2250 index = mBackStack.size()-1; 2251 while (index >= 0) { 2252 BackStackRecord bss = mBackStack.get(index); 2253 if (name != null && name.equals(bss.getName())) { 2254 break; 2255 } 2256 if (id >= 0 && id == bss.mIndex) { 2257 break; 2258 } 2259 index--; 2260 } 2261 if (index < 0) { 2262 return false; 2263 } 2264 if ((flags&POP_BACK_STACK_INCLUSIVE) != 0) { 2265 index--; 2266 // Consume all following entries that match. 2267 while (index >= 0) { 2268 BackStackRecord bss = mBackStack.get(index); 2269 if ((name != null && name.equals(bss.getName())) 2270 || (id >= 0 && id == bss.mIndex)) { 2271 index--; 2272 continue; 2273 } 2274 break; 2275 } 2276 } 2277 } 2278 if (index == mBackStack.size()-1) { 2279 return false; 2280 } 2281 for (int i = mBackStack.size() - 1; i > index; i--) { 2282 records.add(mBackStack.remove(i)); 2283 isRecordPop.add(true); 2284 } 2285 } 2286 return true; 2287 } 2288 2289 FragmentManagerNonConfig retainNonConfig() { 2290 ArrayList<Fragment> fragments = null; 2291 ArrayList<FragmentManagerNonConfig> childFragments = null; 2292 if (mActive != null) { 2293 for (int i=0; i<mActive.size(); i++) { 2294 Fragment f = mActive.get(i); 2295 if (f != null) { 2296 if (f.mRetainInstance) { 2297 if (fragments == null) { 2298 fragments = new ArrayList<Fragment>(); 2299 } 2300 fragments.add(f); 2301 f.mRetaining = true; 2302 f.mTargetIndex = f.mTarget != null ? f.mTarget.mIndex : -1; 2303 if (DEBUG) Log.v(TAG, "retainNonConfig: keeping retained " + f); 2304 } 2305 boolean addedChild = false; 2306 if (f.mChildFragmentManager != null) { 2307 FragmentManagerNonConfig child = f.mChildFragmentManager.retainNonConfig(); 2308 if (child != null) { 2309 if (childFragments == null) { 2310 childFragments = new ArrayList<FragmentManagerNonConfig>(); 2311 for (int j = 0; j < i; j++) { 2312 childFragments.add(null); 2313 } 2314 } 2315 childFragments.add(child); 2316 addedChild = true; 2317 } 2318 } 2319 if (childFragments != null && !addedChild) { 2320 childFragments.add(null); 2321 } 2322 } 2323 } 2324 } 2325 if (fragments == null && childFragments == null) { 2326 return null; 2327 } 2328 return new FragmentManagerNonConfig(fragments, childFragments); 2329 } 2330 2331 void saveFragmentViewState(Fragment f) { 2332 if (f.mInnerView == null) { 2333 return; 2334 } 2335 if (mStateArray == null) { 2336 mStateArray = new SparseArray<Parcelable>(); 2337 } else { 2338 mStateArray.clear(); 2339 } 2340 f.mInnerView.saveHierarchyState(mStateArray); 2341 if (mStateArray.size() > 0) { 2342 f.mSavedViewState = mStateArray; 2343 mStateArray = null; 2344 } 2345 } 2346 2347 Bundle saveFragmentBasicState(Fragment f) { 2348 Bundle result = null; 2349 2350 if (mStateBundle == null) { 2351 mStateBundle = new Bundle(); 2352 } 2353 f.performSaveInstanceState(mStateBundle); 2354 if (!mStateBundle.isEmpty()) { 2355 result = mStateBundle; 2356 mStateBundle = null; 2357 } 2358 2359 if (f.mView != null) { 2360 saveFragmentViewState(f); 2361 } 2362 if (f.mSavedViewState != null) { 2363 if (result == null) { 2364 result = new Bundle(); 2365 } 2366 result.putSparseParcelableArray( 2367 FragmentManagerImpl.VIEW_STATE_TAG, f.mSavedViewState); 2368 } 2369 if (!f.mUserVisibleHint) { 2370 if (result == null) { 2371 result = new Bundle(); 2372 } 2373 // Only add this if it's not the default value 2374 result.putBoolean(FragmentManagerImpl.USER_VISIBLE_HINT_TAG, f.mUserVisibleHint); 2375 } 2376 2377 return result; 2378 } 2379 2380 Parcelable saveAllState() { 2381 // Make sure all pending operations have now been executed to get 2382 // our state update-to-date. 2383 forcePostponedTransactions(); 2384 endAnimatingAwayFragments(); 2385 execPendingActions(); 2386 2387 if (HONEYCOMB) { 2388 // As of Honeycomb, we save state after pausing. Prior to that 2389 // it is before pausing. With fragments this is an issue, since 2390 // there are many things you may do after pausing but before 2391 // stopping that change the fragment state. For those older 2392 // devices, we will not at this point say that we have saved 2393 // the state, so we will allow them to continue doing fragment 2394 // transactions. This retains the same semantics as Honeycomb, 2395 // though you do have the risk of losing the very most recent state 2396 // if the process is killed... we'll live with that. 2397 mStateSaved = true; 2398 } 2399 2400 if (mActive == null || mActive.size() <= 0) { 2401 return null; 2402 } 2403 2404 // First collect all active fragments. 2405 int N = mActive.size(); 2406 FragmentState[] active = new FragmentState[N]; 2407 boolean haveFragments = false; 2408 for (int i=0; i<N; i++) { 2409 Fragment f = mActive.get(i); 2410 if (f != null) { 2411 if (f.mIndex < 0) { 2412 throwException(new IllegalStateException( 2413 "Failure saving state: active " + f 2414 + " has cleared index: " + f.mIndex)); 2415 } 2416 2417 haveFragments = true; 2418 2419 FragmentState fs = new FragmentState(f); 2420 active[i] = fs; 2421 2422 if (f.mState > Fragment.INITIALIZING && fs.mSavedFragmentState == null) { 2423 fs.mSavedFragmentState = saveFragmentBasicState(f); 2424 2425 if (f.mTarget != null) { 2426 if (f.mTarget.mIndex < 0) { 2427 throwException(new IllegalStateException( 2428 "Failure saving state: " + f 2429 + " has target not in fragment manager: " + f.mTarget)); 2430 } 2431 if (fs.mSavedFragmentState == null) { 2432 fs.mSavedFragmentState = new Bundle(); 2433 } 2434 putFragment(fs.mSavedFragmentState, 2435 FragmentManagerImpl.TARGET_STATE_TAG, f.mTarget); 2436 if (f.mTargetRequestCode != 0) { 2437 fs.mSavedFragmentState.putInt( 2438 FragmentManagerImpl.TARGET_REQUEST_CODE_STATE_TAG, 2439 f.mTargetRequestCode); 2440 } 2441 } 2442 2443 } else { 2444 fs.mSavedFragmentState = f.mSavedFragmentState; 2445 } 2446 2447 if (DEBUG) Log.v(TAG, "Saved state of " + f + ": " 2448 + fs.mSavedFragmentState); 2449 } 2450 } 2451 2452 if (!haveFragments) { 2453 if (DEBUG) Log.v(TAG, "saveAllState: no fragments!"); 2454 return null; 2455 } 2456 2457 int[] added = null; 2458 BackStackState[] backStack = null; 2459 2460 // Build list of currently added fragments. 2461 if (mAdded != null) { 2462 N = mAdded.size(); 2463 if (N > 0) { 2464 added = new int[N]; 2465 for (int i=0; i<N; i++) { 2466 added[i] = mAdded.get(i).mIndex; 2467 if (added[i] < 0) { 2468 throwException(new IllegalStateException( 2469 "Failure saving state: active " + mAdded.get(i) 2470 + " has cleared index: " + added[i])); 2471 } 2472 if (DEBUG) Log.v(TAG, "saveAllState: adding fragment #" + i 2473 + ": " + mAdded.get(i)); 2474 } 2475 } 2476 } 2477 2478 // Now save back stack. 2479 if (mBackStack != null) { 2480 N = mBackStack.size(); 2481 if (N > 0) { 2482 backStack = new BackStackState[N]; 2483 for (int i=0; i<N; i++) { 2484 backStack[i] = new BackStackState(mBackStack.get(i)); 2485 if (DEBUG) Log.v(TAG, "saveAllState: adding back stack #" + i 2486 + ": " + mBackStack.get(i)); 2487 } 2488 } 2489 } 2490 2491 FragmentManagerState fms = new FragmentManagerState(); 2492 fms.mActive = active; 2493 fms.mAdded = added; 2494 fms.mBackStack = backStack; 2495 return fms; 2496 } 2497 2498 void restoreAllState(Parcelable state, FragmentManagerNonConfig nonConfig) { 2499 // If there is no saved state at all, then there can not be 2500 // any nonConfig fragments either, so that is that. 2501 if (state == null) return; 2502 FragmentManagerState fms = (FragmentManagerState)state; 2503 if (fms.mActive == null) return; 2504 2505 List<FragmentManagerNonConfig> childNonConfigs = null; 2506 2507 // First re-attach any non-config instances we are retaining back 2508 // to their saved state, so we don't try to instantiate them again. 2509 if (nonConfig != null) { 2510 List<Fragment> nonConfigFragments = nonConfig.getFragments(); 2511 childNonConfigs = nonConfig.getChildNonConfigs(); 2512 final int count = nonConfigFragments != null ? nonConfigFragments.size() : 0; 2513 for (int i = 0; i < count; i++) { 2514 Fragment f = nonConfigFragments.get(i); 2515 if (DEBUG) Log.v(TAG, "restoreAllState: re-attaching retained " + f); 2516 FragmentState fs = fms.mActive[f.mIndex]; 2517 fs.mInstance = f; 2518 f.mSavedViewState = null; 2519 f.mBackStackNesting = 0; 2520 f.mInLayout = false; 2521 f.mAdded = false; 2522 f.mTarget = null; 2523 if (fs.mSavedFragmentState != null) { 2524 fs.mSavedFragmentState.setClassLoader(mHost.getContext().getClassLoader()); 2525 f.mSavedViewState = fs.mSavedFragmentState.getSparseParcelableArray( 2526 FragmentManagerImpl.VIEW_STATE_TAG); 2527 f.mSavedFragmentState = fs.mSavedFragmentState; 2528 } 2529 } 2530 } 2531 2532 // Build the full list of active fragments, instantiating them from 2533 // their saved state. 2534 mActive = new ArrayList<>(fms.mActive.length); 2535 if (mAvailIndices != null) { 2536 mAvailIndices.clear(); 2537 } 2538 for (int i=0; i<fms.mActive.length; i++) { 2539 FragmentState fs = fms.mActive[i]; 2540 if (fs != null) { 2541 FragmentManagerNonConfig childNonConfig = null; 2542 if (childNonConfigs != null && i < childNonConfigs.size()) { 2543 childNonConfig = childNonConfigs.get(i); 2544 } 2545 Fragment f = fs.instantiate(mHost, mParent, childNonConfig); 2546 if (DEBUG) Log.v(TAG, "restoreAllState: active #" + i + ": " + f); 2547 mActive.add(f); 2548 // Now that the fragment is instantiated (or came from being 2549 // retained above), clear mInstance in case we end up re-restoring 2550 // from this FragmentState again. 2551 fs.mInstance = null; 2552 } else { 2553 mActive.add(null); 2554 if (mAvailIndices == null) { 2555 mAvailIndices = new ArrayList<Integer>(); 2556 } 2557 if (DEBUG) Log.v(TAG, "restoreAllState: avail #" + i); 2558 mAvailIndices.add(i); 2559 } 2560 } 2561 2562 // Update the target of all retained fragments. 2563 if (nonConfig != null) { 2564 List<Fragment> nonConfigFragments = nonConfig.getFragments(); 2565 final int count = nonConfigFragments != null ? nonConfigFragments.size() : 0; 2566 for (int i = 0; i < count; i++) { 2567 Fragment f = nonConfigFragments.get(i); 2568 if (f.mTargetIndex >= 0) { 2569 if (f.mTargetIndex < mActive.size()) { 2570 f.mTarget = mActive.get(f.mTargetIndex); 2571 } else { 2572 Log.w(TAG, "Re-attaching retained fragment " + f 2573 + " target no longer exists: " + f.mTargetIndex); 2574 f.mTarget = null; 2575 } 2576 } 2577 } 2578 } 2579 2580 // Build the list of currently added fragments. 2581 if (fms.mAdded != null) { 2582 mAdded = new ArrayList<Fragment>(fms.mAdded.length); 2583 for (int i=0; i<fms.mAdded.length; i++) { 2584 Fragment f = mActive.get(fms.mAdded[i]); 2585 if (f == null) { 2586 throwException(new IllegalStateException( 2587 "No instantiated fragment for index #" + fms.mAdded[i])); 2588 } 2589 f.mAdded = true; 2590 if (DEBUG) Log.v(TAG, "restoreAllState: added #" + i + ": " + f); 2591 if (mAdded.contains(f)) { 2592 throw new IllegalStateException("Already added!"); 2593 } 2594 mAdded.add(f); 2595 } 2596 } else { 2597 mAdded = null; 2598 } 2599 2600 // Build the back stack. 2601 if (fms.mBackStack != null) { 2602 mBackStack = new ArrayList<BackStackRecord>(fms.mBackStack.length); 2603 for (int i=0; i<fms.mBackStack.length; i++) { 2604 BackStackRecord bse = fms.mBackStack[i].instantiate(this); 2605 if (DEBUG) { 2606 Log.v(TAG, "restoreAllState: back stack #" + i 2607 + " (index " + bse.mIndex + "): " + bse); 2608 LogWriter logw = new LogWriter(TAG); 2609 PrintWriter pw = new PrintWriter(logw); 2610 bse.dump(" ", pw, false); 2611 } 2612 mBackStack.add(bse); 2613 if (bse.mIndex >= 0) { 2614 setBackStackIndex(bse.mIndex, bse); 2615 } 2616 } 2617 } else { 2618 mBackStack = null; 2619 } 2620 } 2621 2622 public void attachController(FragmentHostCallback host, 2623 FragmentContainer container, Fragment parent) { 2624 if (mHost != null) throw new IllegalStateException("Already attached"); 2625 mHost = host; 2626 mContainer = container; 2627 mParent = parent; 2628 } 2629 2630 public void noteStateNotSaved() { 2631 mStateSaved = false; 2632 } 2633 2634 public void dispatchCreate() { 2635 mStateSaved = false; 2636 moveToState(Fragment.CREATED); 2637 } 2638 2639 public void dispatchActivityCreated() { 2640 mStateSaved = false; 2641 moveToState(Fragment.ACTIVITY_CREATED); 2642 } 2643 2644 public void dispatchStart() { 2645 mStateSaved = false; 2646 moveToState(Fragment.STARTED); 2647 } 2648 2649 public void dispatchResume() { 2650 mStateSaved = false; 2651 moveToState(Fragment.RESUMED); 2652 } 2653 2654 public void dispatchPause() { 2655 moveToState(Fragment.STARTED); 2656 } 2657 2658 public void dispatchStop() { 2659 // See saveAllState() for the explanation of this. We do this for 2660 // all platform versions, to keep our behavior more consistent between 2661 // them. 2662 mStateSaved = true; 2663 2664 moveToState(Fragment.STOPPED); 2665 } 2666 2667 public void dispatchReallyStop() { 2668 moveToState(Fragment.ACTIVITY_CREATED); 2669 } 2670 2671 public void dispatchDestroyView() { 2672 moveToState(Fragment.CREATED); 2673 } 2674 2675 public void dispatchDestroy() { 2676 mDestroyed = true; 2677 execPendingActions(); 2678 moveToState(Fragment.INITIALIZING); 2679 mHost = null; 2680 mContainer = null; 2681 mParent = null; 2682 } 2683 2684 public void dispatchMultiWindowModeChanged(boolean isInMultiWindowMode) { 2685 if (mAdded == null) { 2686 return; 2687 } 2688 for (int i = mAdded.size() - 1; i >= 0; --i) { 2689 final android.support.v4.app.Fragment f = mAdded.get(i); 2690 if (f != null) { 2691 f.performMultiWindowModeChanged(isInMultiWindowMode); 2692 } 2693 } 2694 } 2695 2696 public void dispatchPictureInPictureModeChanged(boolean isInPictureInPictureMode) { 2697 if (mAdded == null) { 2698 return; 2699 } 2700 for (int i = mAdded.size() - 1; i >= 0; --i) { 2701 final android.support.v4.app.Fragment f = mAdded.get(i); 2702 if (f != null) { 2703 f.performPictureInPictureModeChanged(isInPictureInPictureMode); 2704 } 2705 } 2706 } 2707 2708 public void dispatchConfigurationChanged(Configuration newConfig) { 2709 if (mAdded != null) { 2710 for (int i=0; i<mAdded.size(); i++) { 2711 Fragment f = mAdded.get(i); 2712 if (f != null) { 2713 f.performConfigurationChanged(newConfig); 2714 } 2715 } 2716 } 2717 } 2718 2719 public void dispatchLowMemory() { 2720 if (mAdded != null) { 2721 for (int i=0; i<mAdded.size(); i++) { 2722 Fragment f = mAdded.get(i); 2723 if (f != null) { 2724 f.performLowMemory(); 2725 } 2726 } 2727 } 2728 } 2729 2730 public boolean dispatchCreateOptionsMenu(Menu menu, MenuInflater inflater) { 2731 boolean show = false; 2732 ArrayList<Fragment> newMenus = null; 2733 if (mAdded != null) { 2734 for (int i=0; i<mAdded.size(); i++) { 2735 Fragment f = mAdded.get(i); 2736 if (f != null) { 2737 if (f.performCreateOptionsMenu(menu, inflater)) { 2738 show = true; 2739 if (newMenus == null) { 2740 newMenus = new ArrayList<Fragment>(); 2741 } 2742 newMenus.add(f); 2743 } 2744 } 2745 } 2746 } 2747 2748 if (mCreatedMenus != null) { 2749 for (int i=0; i<mCreatedMenus.size(); i++) { 2750 Fragment f = mCreatedMenus.get(i); 2751 if (newMenus == null || !newMenus.contains(f)) { 2752 f.onDestroyOptionsMenu(); 2753 } 2754 } 2755 } 2756 2757 mCreatedMenus = newMenus; 2758 2759 return show; 2760 } 2761 2762 public boolean dispatchPrepareOptionsMenu(Menu menu) { 2763 boolean show = false; 2764 if (mAdded != null) { 2765 for (int i=0; i<mAdded.size(); i++) { 2766 Fragment f = mAdded.get(i); 2767 if (f != null) { 2768 if (f.performPrepareOptionsMenu(menu)) { 2769 show = true; 2770 } 2771 } 2772 } 2773 } 2774 return show; 2775 } 2776 2777 public boolean dispatchOptionsItemSelected(MenuItem item) { 2778 if (mAdded != null) { 2779 for (int i=0; i<mAdded.size(); i++) { 2780 Fragment f = mAdded.get(i); 2781 if (f != null) { 2782 if (f.performOptionsItemSelected(item)) { 2783 return true; 2784 } 2785 } 2786 } 2787 } 2788 return false; 2789 } 2790 2791 public boolean dispatchContextItemSelected(MenuItem item) { 2792 if (mAdded != null) { 2793 for (int i=0; i<mAdded.size(); i++) { 2794 Fragment f = mAdded.get(i); 2795 if (f != null) { 2796 if (f.performContextItemSelected(item)) { 2797 return true; 2798 } 2799 } 2800 } 2801 } 2802 return false; 2803 } 2804 2805 public void dispatchOptionsMenuClosed(Menu menu) { 2806 if (mAdded != null) { 2807 for (int i=0; i<mAdded.size(); i++) { 2808 Fragment f = mAdded.get(i); 2809 if (f != null) { 2810 f.performOptionsMenuClosed(menu); 2811 } 2812 } 2813 } 2814 } 2815 2816 public static int reverseTransit(int transit) { 2817 int rev = 0; 2818 switch (transit) { 2819 case FragmentTransaction.TRANSIT_FRAGMENT_OPEN: 2820 rev = FragmentTransaction.TRANSIT_FRAGMENT_CLOSE; 2821 break; 2822 case FragmentTransaction.TRANSIT_FRAGMENT_CLOSE: 2823 rev = FragmentTransaction.TRANSIT_FRAGMENT_OPEN; 2824 break; 2825 case FragmentTransaction.TRANSIT_FRAGMENT_FADE: 2826 rev = FragmentTransaction.TRANSIT_FRAGMENT_FADE; 2827 break; 2828 } 2829 return rev; 2830 2831 } 2832 2833 public static final int ANIM_STYLE_OPEN_ENTER = 1; 2834 public static final int ANIM_STYLE_OPEN_EXIT = 2; 2835 public static final int ANIM_STYLE_CLOSE_ENTER = 3; 2836 public static final int ANIM_STYLE_CLOSE_EXIT = 4; 2837 public static final int ANIM_STYLE_FADE_ENTER = 5; 2838 public static final int ANIM_STYLE_FADE_EXIT = 6; 2839 2840 public static int transitToStyleIndex(int transit, boolean enter) { 2841 int animAttr = -1; 2842 switch (transit) { 2843 case FragmentTransaction.TRANSIT_FRAGMENT_OPEN: 2844 animAttr = enter ? ANIM_STYLE_OPEN_ENTER : ANIM_STYLE_OPEN_EXIT; 2845 break; 2846 case FragmentTransaction.TRANSIT_FRAGMENT_CLOSE: 2847 animAttr = enter ? ANIM_STYLE_CLOSE_ENTER : ANIM_STYLE_CLOSE_EXIT; 2848 break; 2849 case FragmentTransaction.TRANSIT_FRAGMENT_FADE: 2850 animAttr = enter ? ANIM_STYLE_FADE_ENTER : ANIM_STYLE_FADE_EXIT; 2851 break; 2852 } 2853 return animAttr; 2854 } 2855 2856 @Override 2857 public View onCreateView(View parent, String name, Context context, AttributeSet attrs) { 2858 if (!"fragment".equals(name)) { 2859 return null; 2860 } 2861 2862 String fname = attrs.getAttributeValue(null, "class"); 2863 TypedArray a = context.obtainStyledAttributes(attrs, FragmentTag.Fragment); 2864 if (fname == null) { 2865 fname = a.getString(FragmentTag.Fragment_name); 2866 } 2867 int id = a.getResourceId(FragmentTag.Fragment_id, View.NO_ID); 2868 String tag = a.getString(FragmentTag.Fragment_tag); 2869 a.recycle(); 2870 2871 if (!Fragment.isSupportFragmentClass(mHost.getContext(), fname)) { 2872 // Invalid support lib fragment; let the device's framework handle it. 2873 // This will allow android.app.Fragments to do the right thing. 2874 return null; 2875 } 2876 2877 int containerId = parent != null ? parent.getId() : 0; 2878 if (containerId == View.NO_ID && id == View.NO_ID && tag == null) { 2879 throw new IllegalArgumentException(attrs.getPositionDescription() 2880 + ": Must specify unique android:id, android:tag, or have a parent with an id for " + fname); 2881 } 2882 2883 // If we restored from a previous state, we may already have 2884 // instantiated this fragment from the state and should use 2885 // that instance instead of making a new one. 2886 Fragment fragment = id != View.NO_ID ? findFragmentById(id) : null; 2887 if (fragment == null && tag != null) { 2888 fragment = findFragmentByTag(tag); 2889 } 2890 if (fragment == null && containerId != View.NO_ID) { 2891 fragment = findFragmentById(containerId); 2892 } 2893 2894 if (FragmentManagerImpl.DEBUG) Log.v(TAG, "onCreateView: id=0x" 2895 + Integer.toHexString(id) + " fname=" + fname 2896 + " existing=" + fragment); 2897 if (fragment == null) { 2898 fragment = Fragment.instantiate(context, fname); 2899 fragment.mFromLayout = true; 2900 fragment.mFragmentId = id != 0 ? id : containerId; 2901 fragment.mContainerId = containerId; 2902 fragment.mTag = tag; 2903 fragment.mInLayout = true; 2904 fragment.mFragmentManager = this; 2905 fragment.mHost = mHost; 2906 fragment.onInflate(mHost.getContext(), attrs, fragment.mSavedFragmentState); 2907 addFragment(fragment, true); 2908 2909 } else if (fragment.mInLayout) { 2910 // A fragment already exists and it is not one we restored from 2911 // previous state. 2912 throw new IllegalArgumentException(attrs.getPositionDescription() 2913 + ": Duplicate id 0x" + Integer.toHexString(id) 2914 + ", tag " + tag + ", or parent id 0x" + Integer.toHexString(containerId) 2915 + " with another fragment for " + fname); 2916 } else { 2917 // This fragment was retained from a previous instance; get it 2918 // going now. 2919 fragment.mInLayout = true; 2920 fragment.mHost = mHost; 2921 // If this fragment is newly instantiated (either right now, or 2922 // from last saved state), then give it the attributes to 2923 // initialize itself. 2924 if (!fragment.mRetaining) { 2925 fragment.onInflate(mHost.getContext(), attrs, fragment.mSavedFragmentState); 2926 } 2927 } 2928 2929 // If we haven't finished entering the CREATED state ourselves yet, 2930 // push the inflated child fragment along. 2931 if (mCurState < Fragment.CREATED && fragment.mFromLayout) { 2932 moveToState(fragment, Fragment.CREATED, 0, 0, false); 2933 } else { 2934 moveToState(fragment); 2935 } 2936 2937 if (fragment.mView == null) { 2938 throw new IllegalStateException("Fragment " + fname 2939 + " did not create a view."); 2940 } 2941 if (id != 0) { 2942 fragment.mView.setId(id); 2943 } 2944 if (fragment.mView.getTag() == null) { 2945 fragment.mView.setTag(tag); 2946 } 2947 return fragment.mView; 2948 } 2949 2950 LayoutInflaterFactory getLayoutInflaterFactory() { 2951 return this; 2952 } 2953 2954 static class FragmentTag { 2955 public static final int[] Fragment = { 2956 0x01010003, 0x010100d0, 0x010100d1 2957 }; 2958 public static final int Fragment_id = 1; 2959 public static final int Fragment_name = 0; 2960 public static final int Fragment_tag = 2; 2961 } 2962 2963 /** 2964 * An add or pop transaction to be scheduled for the UI thread. 2965 */ 2966 interface OpGenerator { 2967 /** 2968 * Generate transactions to add to {@code records} and whether or not the transaction is 2969 * an add or pop to {@code isRecordPop}. 2970 * 2971 * records and isRecordPop must be added equally so that each transaction in records 2972 * matches the boolean for whether or not it is a pop in isRecordPop. 2973 * 2974 * @param records A list to add transactions to. 2975 * @param isRecordPop A list to add whether or not the transactions added to records is 2976 * a pop transaction. 2977 * @return true if something was added or false otherwise. 2978 */ 2979 boolean generateOps(ArrayList<BackStackRecord> records, ArrayList<Boolean> isRecordPop); 2980 } 2981 2982 /** 2983 * A pop operation OpGenerator. This will be run on the UI thread and will generate the 2984 * transactions that will be popped if anything can be popped. 2985 */ 2986 private class PopBackStackState implements OpGenerator { 2987 final String mName; 2988 final int mId; 2989 final int mFlags; 2990 2991 PopBackStackState(String name, int id, int flags) { 2992 mName = name; 2993 mId = id; 2994 mFlags = flags; 2995 } 2996 2997 @Override 2998 public boolean generateOps(ArrayList<BackStackRecord> records, 2999 ArrayList<Boolean> isRecordPop) { 3000 return popBackStackState(records, isRecordPop, mName, mId, mFlags); 3001 } 3002 } 3003 3004 /** 3005 * A listener for a postponed transaction. This waits until 3006 * {@link Fragment#startPostponedEnterTransition()} is called or a transaction is started 3007 * that interacts with this one, based on interactions with the fragment container. 3008 */ 3009 static class StartEnterTransitionListener 3010 implements Fragment.OnStartEnterTransitionListener { 3011 private final boolean mIsBack; 3012 private final BackStackRecord mRecord; 3013 private int mNumPostponed; 3014 3015 StartEnterTransitionListener(BackStackRecord record, boolean isBack) { 3016 mIsBack = isBack; 3017 mRecord = record; 3018 } 3019 3020 /** 3021 * Called from {@link Fragment#startPostponedEnterTransition()}, this decreases the 3022 * number of Fragments that are postponed. This may cause the transaction to schedule 3023 * to finish running and run transitions and animations. 3024 */ 3025 @Override 3026 public void onStartEnterTransition() { 3027 mNumPostponed--; 3028 if (mNumPostponed != 0) { 3029 return; 3030 } 3031 mRecord.mManager.scheduleCommit(); 3032 } 3033 3034 /** 3035 * Called from {@link Fragment# 3036 * setOnStartEnterTransitionListener(Fragment.OnStartEnterTransitionListener)}, this 3037 * increases the number of fragments that are postponed as part of this transaction. 3038 */ 3039 @Override 3040 public void startListening() { 3041 mNumPostponed++; 3042 } 3043 3044 /** 3045 * @return true if there are no more postponed fragments as part of the transaction. 3046 */ 3047 public boolean isReady() { 3048 return mNumPostponed == 0; 3049 } 3050 3051 /** 3052 * Completes the transaction and start the animations and transitions. This may skip 3053 * the transitions if this is called before all fragments have called 3054 * {@link Fragment#startPostponedEnterTransition()}. 3055 */ 3056 public void completeTransaction() { 3057 final boolean canceled; 3058 canceled = mNumPostponed > 0; 3059 FragmentManagerImpl manager = mRecord.mManager; 3060 final int numAdded = manager.mAdded.size(); 3061 for (int i = 0; i < numAdded; i++) { 3062 final Fragment fragment = manager.mAdded.get(i); 3063 fragment.setOnStartEnterTransitionListener(null); 3064 if (canceled && fragment.isPostponed()) { 3065 fragment.startPostponedEnterTransition(); 3066 } 3067 } 3068 mRecord.mManager.completeExecute(mRecord, mIsBack, !canceled, true); 3069 } 3070 3071 /** 3072 * Cancels this transaction instead of completing it. That means that the state isn't 3073 * changed, so the pop results in no change to the state. 3074 */ 3075 public void cancelTransaction() { 3076 mRecord.mManager.completeExecute(mRecord, mIsBack, false, false); 3077 } 3078 } 3079} 3080