BackStackRecord.java revision cba2e2c881e8e16ea5025b564c94320174d65f01
1/* 2 * Copyright (C) 2011 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.support.v4.app; 18 19import android.os.Parcel; 20import android.os.Parcelable; 21import android.text.TextUtils; 22import android.util.Log; 23 24import java.io.FileDescriptor; 25import java.io.PrintWriter; 26import java.util.ArrayList; 27 28final class BackStackState implements Parcelable { 29 final int[] mOps; 30 final int mTransition; 31 final int mTransitionStyle; 32 final String mName; 33 final int mIndex; 34 final int mBreadCrumbTitleRes; 35 final CharSequence mBreadCrumbTitleText; 36 final int mBreadCrumbShortTitleRes; 37 final CharSequence mBreadCrumbShortTitleText; 38 39 public BackStackState(FragmentManagerImpl fm, BackStackRecord bse) { 40 int numRemoved = 0; 41 BackStackRecord.Op op = bse.mHead; 42 while (op != null) { 43 if (op.removed != null) numRemoved += op.removed.size(); 44 op = op.next; 45 } 46 mOps = new int[bse.mNumOp*5 + numRemoved]; 47 48 if (!bse.mAddToBackStack) { 49 throw new IllegalStateException("Not on back stack"); 50 } 51 52 op = bse.mHead; 53 int pos = 0; 54 while (op != null) { 55 mOps[pos++] = op.cmd; 56 mOps[pos++] = op.fragment.mIndex; 57 mOps[pos++] = op.enterAnim; 58 mOps[pos++] = op.exitAnim; 59 if (op.removed != null) { 60 final int N = op.removed.size(); 61 mOps[pos++] = N; 62 for (int i=0; i<N; i++) { 63 mOps[pos++] = op.removed.get(i).mIndex; 64 } 65 } else { 66 mOps[pos++] = 0; 67 } 68 op = op.next; 69 } 70 mTransition = bse.mTransition; 71 mTransitionStyle = bse.mTransitionStyle; 72 mName = bse.mName; 73 mIndex = bse.mIndex; 74 mBreadCrumbTitleRes = bse.mBreadCrumbTitleRes; 75 mBreadCrumbTitleText = bse.mBreadCrumbTitleText; 76 mBreadCrumbShortTitleRes = bse.mBreadCrumbShortTitleRes; 77 mBreadCrumbShortTitleText = bse.mBreadCrumbShortTitleText; 78 } 79 80 public BackStackState(Parcel in) { 81 mOps = in.createIntArray(); 82 mTransition = in.readInt(); 83 mTransitionStyle = in.readInt(); 84 mName = in.readString(); 85 mIndex = in.readInt(); 86 mBreadCrumbTitleRes = in.readInt(); 87 mBreadCrumbTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 88 mBreadCrumbShortTitleRes = in.readInt(); 89 mBreadCrumbShortTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 90 } 91 92 public BackStackRecord instantiate(FragmentManagerImpl fm) { 93 BackStackRecord bse = new BackStackRecord(fm); 94 int pos = 0; 95 while (pos < mOps.length) { 96 BackStackRecord.Op op = new BackStackRecord.Op(); 97 op.cmd = mOps[pos++]; 98 if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG, 99 "BSE " + bse + " set base fragment #" + mOps[pos]); 100 Fragment f = fm.mActive.get(mOps[pos++]); 101 op.fragment = f; 102 op.enterAnim = mOps[pos++]; 103 op.exitAnim = mOps[pos++]; 104 final int N = mOps[pos++]; 105 if (N > 0) { 106 op.removed = new ArrayList<Fragment>(N); 107 for (int i=0; i<N; i++) { 108 if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG, 109 "BSE " + bse + " set remove fragment #" + mOps[pos]); 110 Fragment r = fm.mActive.get(mOps[pos++]); 111 op.removed.add(r); 112 } 113 } 114 bse.addOp(op); 115 } 116 bse.mTransition = mTransition; 117 bse.mTransitionStyle = mTransitionStyle; 118 bse.mName = mName; 119 bse.mIndex = mIndex; 120 bse.mAddToBackStack = true; 121 bse.mBreadCrumbTitleRes = mBreadCrumbTitleRes; 122 bse.mBreadCrumbTitleText = mBreadCrumbTitleText; 123 bse.mBreadCrumbShortTitleRes = mBreadCrumbShortTitleRes; 124 bse.mBreadCrumbShortTitleText = mBreadCrumbShortTitleText; 125 bse.bumpBackStackNesting(1); 126 return bse; 127 } 128 129 public int describeContents() { 130 return 0; 131 } 132 133 public void writeToParcel(Parcel dest, int flags) { 134 dest.writeIntArray(mOps); 135 dest.writeInt(mTransition); 136 dest.writeInt(mTransitionStyle); 137 dest.writeString(mName); 138 dest.writeInt(mIndex); 139 dest.writeInt(mBreadCrumbTitleRes); 140 TextUtils.writeToParcel(mBreadCrumbTitleText, dest, 0); 141 dest.writeInt(mBreadCrumbShortTitleRes); 142 TextUtils.writeToParcel(mBreadCrumbShortTitleText, dest, 0); 143 } 144 145 public static final Parcelable.Creator<BackStackState> CREATOR 146 = new Parcelable.Creator<BackStackState>() { 147 public BackStackState createFromParcel(Parcel in) { 148 return new BackStackState(in); 149 } 150 151 public BackStackState[] newArray(int size) { 152 return new BackStackState[size]; 153 } 154 }; 155} 156 157/** 158 * @hide Entry of an operation on the fragment back stack. 159 */ 160final class BackStackRecord extends FragmentTransaction implements 161 FragmentManager.BackStackEntry, Runnable { 162 static final String TAG = "BackStackEntry"; 163 164 final FragmentManagerImpl mManager; 165 166 static final int OP_NULL = 0; 167 static final int OP_ADD = 1; 168 static final int OP_REPLACE = 2; 169 static final int OP_REMOVE = 3; 170 static final int OP_HIDE = 4; 171 static final int OP_SHOW = 5; 172 173 static final class Op { 174 Op next; 175 Op prev; 176 int cmd; 177 Fragment fragment; 178 int enterAnim; 179 int exitAnim; 180 ArrayList<Fragment> removed; 181 } 182 183 Op mHead; 184 Op mTail; 185 int mNumOp; 186 int mEnterAnim; 187 int mExitAnim; 188 int mTransition; 189 int mTransitionStyle; 190 boolean mAddToBackStack; 191 boolean mAllowAddToBackStack = true; 192 String mName; 193 boolean mCommitted; 194 int mIndex; 195 196 int mBreadCrumbTitleRes; 197 CharSequence mBreadCrumbTitleText; 198 int mBreadCrumbShortTitleRes; 199 CharSequence mBreadCrumbShortTitleText; 200 201 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 202 writer.print(prefix); writer.print("mName="); writer.print(mName); 203 writer.print(" mIndex="); writer.print(mIndex); 204 writer.print(" mCommitted="); writer.println(mCommitted); 205 if (mTransition != FragmentTransaction.TRANSIT_NONE) { 206 writer.print(prefix); writer.print("mTransition=#"); 207 writer.print(Integer.toHexString(mTransition)); 208 writer.print(" mTransitionStyle=#"); 209 writer.println(Integer.toHexString(mTransitionStyle)); 210 } 211 if (mEnterAnim != 0 || mExitAnim !=0) { 212 writer.print(prefix); writer.print("mEnterAnim=#"); 213 writer.print(Integer.toHexString(mEnterAnim)); 214 writer.print(" mExitAnim=#"); 215 writer.println(Integer.toHexString(mExitAnim)); 216 } 217 if (mBreadCrumbTitleRes != 0 || mBreadCrumbTitleText != null) { 218 writer.print(prefix); writer.print("mBreadCrumbTitleRes=#"); 219 writer.print(Integer.toHexString(mBreadCrumbTitleRes)); 220 writer.print(" mBreadCrumbTitleText="); 221 writer.println(mBreadCrumbTitleText); 222 } 223 if (mBreadCrumbShortTitleRes != 0 || mBreadCrumbShortTitleText != null) { 224 writer.print(prefix); writer.print("mBreadCrumbShortTitleRes=#"); 225 writer.print(Integer.toHexString(mBreadCrumbShortTitleRes)); 226 writer.print(" mBreadCrumbShortTitleText="); 227 writer.println(mBreadCrumbShortTitleText); 228 } 229 230 if (mHead != null) { 231 writer.print(prefix); writer.println("Operations:"); 232 String innerPrefix = prefix + " "; 233 Op op = mHead; 234 int num = 0; 235 while (op != null) { 236 writer.print(prefix); writer.print(" Op #"); writer.print(num); 237 writer.println(":"); 238 writer.print(innerPrefix); writer.print("cmd="); writer.print(op.cmd); 239 writer.print(" fragment="); writer.println(op.fragment); 240 if (op.enterAnim != 0 || op.exitAnim != 0) { 241 writer.print(prefix); writer.print("enterAnim="); writer.print(op.enterAnim); 242 writer.print(" exitAnim="); writer.println(op.exitAnim); 243 } 244 if (op.removed != null && op.removed.size() > 0) { 245 for (int i=0; i<op.removed.size(); i++) { 246 writer.print(innerPrefix); 247 if (op.removed.size() == 1) { 248 writer.print("Removed: "); 249 } else { 250 writer.println("Removed:"); 251 writer.print(innerPrefix); writer.print(" #"); writer.print(num); 252 writer.print(": "); 253 } 254 writer.println(op.removed.get(i)); 255 } 256 } 257 op = op.next; 258 } 259 } 260 } 261 262 public BackStackRecord(FragmentManagerImpl manager) { 263 mManager = manager; 264 } 265 266 public int getId() { 267 return mIndex; 268 } 269 270 public int getBreadCrumbTitleRes() { 271 return mBreadCrumbTitleRes; 272 } 273 274 public int getBreadCrumbShortTitleRes() { 275 return mBreadCrumbShortTitleRes; 276 } 277 278 public CharSequence getBreadCrumbTitle() { 279 if (mBreadCrumbTitleRes != 0) { 280 return mManager.mActivity.getText(mBreadCrumbTitleRes); 281 } 282 return mBreadCrumbTitleText; 283 } 284 285 public CharSequence getBreadCrumbShortTitle() { 286 if (mBreadCrumbShortTitleRes != 0) { 287 return mManager.mActivity.getText(mBreadCrumbShortTitleRes); 288 } 289 return mBreadCrumbShortTitleText; 290 } 291 292 void addOp(Op op) { 293 if (mHead == null) { 294 mHead = mTail = op; 295 } else { 296 op.prev = mTail; 297 mTail.next = op; 298 mTail = op; 299 } 300 op.enterAnim = mEnterAnim; 301 op.exitAnim = mExitAnim; 302 mNumOp++; 303 } 304 305 public FragmentTransaction add(Fragment fragment, String tag) { 306 doAddOp(0, fragment, tag, OP_ADD); 307 return this; 308 } 309 310 public FragmentTransaction add(int containerViewId, Fragment fragment) { 311 doAddOp(containerViewId, fragment, null, OP_ADD); 312 return this; 313 } 314 315 public FragmentTransaction add(int containerViewId, Fragment fragment, String tag) { 316 doAddOp(containerViewId, fragment, tag, OP_ADD); 317 return this; 318 } 319 320 private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) { 321 if (fragment.mImmediateActivity != null) { 322 throw new IllegalStateException("Fragment already added: " + fragment); 323 } 324 fragment.mImmediateActivity = mManager.mActivity; 325 fragment.mFragmentManager = mManager; 326 327 if (tag != null) { 328 if (fragment.mTag != null && !tag.equals(fragment.mTag)) { 329 throw new IllegalStateException("Can't change tag of fragment " 330 + fragment + ": was " + fragment.mTag 331 + " now " + tag); 332 } 333 fragment.mTag = tag; 334 } 335 336 if (containerViewId != 0) { 337 if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) { 338 throw new IllegalStateException("Can't change container ID of fragment " 339 + fragment + ": was " + fragment.mFragmentId 340 + " now " + containerViewId); 341 } 342 fragment.mContainerId = fragment.mFragmentId = containerViewId; 343 } 344 345 Op op = new Op(); 346 op.cmd = opcmd; 347 op.fragment = fragment; 348 addOp(op); 349 } 350 351 public FragmentTransaction replace(int containerViewId, Fragment fragment) { 352 return replace(containerViewId, fragment, null); 353 } 354 355 public FragmentTransaction replace(int containerViewId, Fragment fragment, String tag) { 356 if (containerViewId == 0) { 357 throw new IllegalArgumentException("Must use non-zero containerViewId"); 358 } 359 360 doAddOp(containerViewId, fragment, tag, OP_REPLACE); 361 return this; 362 } 363 364 public FragmentTransaction remove(Fragment fragment) { 365 if (fragment.mImmediateActivity == null) { 366 throw new IllegalStateException("Fragment not added: " + fragment); 367 } 368 fragment.mImmediateActivity = null; 369 370 Op op = new Op(); 371 op.cmd = OP_REMOVE; 372 op.fragment = fragment; 373 addOp(op); 374 375 return this; 376 } 377 378 public FragmentTransaction hide(Fragment fragment) { 379 if (fragment.mImmediateActivity == null) { 380 throw new IllegalStateException("Fragment not added: " + fragment); 381 } 382 383 Op op = new Op(); 384 op.cmd = OP_HIDE; 385 op.fragment = fragment; 386 addOp(op); 387 388 return this; 389 } 390 391 public FragmentTransaction show(Fragment fragment) { 392 if (fragment.mImmediateActivity == null) { 393 throw new IllegalStateException("Fragment not added: " + fragment); 394 } 395 396 Op op = new Op(); 397 op.cmd = OP_SHOW; 398 op.fragment = fragment; 399 addOp(op); 400 401 return this; 402 } 403 404 public FragmentTransaction setCustomAnimations(int enter, int exit) { 405 mEnterAnim = enter; 406 mExitAnim = exit; 407 return this; 408 } 409 410 public FragmentTransaction setTransition(int transition) { 411 mTransition = transition; 412 return this; 413 } 414 415 public FragmentTransaction setTransitionStyle(int styleRes) { 416 mTransitionStyle = styleRes; 417 return this; 418 } 419 420 public FragmentTransaction addToBackStack(String name) { 421 if (!mAllowAddToBackStack) { 422 throw new IllegalStateException( 423 "This FragmentTransaction is not allowed to be added to the back stack."); 424 } 425 mAddToBackStack = true; 426 mName = name; 427 return this; 428 } 429 430 public boolean isAddToBackStackAllowed() { 431 return mAllowAddToBackStack; 432 } 433 434 public FragmentTransaction disallowAddToBackStack() { 435 if (mAddToBackStack) { 436 throw new IllegalStateException( 437 "This transaction is already being added to the back stack"); 438 } 439 mAllowAddToBackStack = false; 440 return this; 441 } 442 443 public FragmentTransaction setBreadCrumbTitle(int res) { 444 mBreadCrumbTitleRes = res; 445 mBreadCrumbTitleText = null; 446 return this; 447 } 448 449 public FragmentTransaction setBreadCrumbTitle(CharSequence text) { 450 mBreadCrumbTitleRes = 0; 451 mBreadCrumbTitleText = text; 452 return this; 453 } 454 455 public FragmentTransaction setBreadCrumbShortTitle(int res) { 456 mBreadCrumbShortTitleRes = res; 457 mBreadCrumbShortTitleText = null; 458 return this; 459 } 460 461 public FragmentTransaction setBreadCrumbShortTitle(CharSequence text) { 462 mBreadCrumbShortTitleRes = 0; 463 mBreadCrumbShortTitleText = text; 464 return this; 465 } 466 467 void bumpBackStackNesting(int amt) { 468 if (!mAddToBackStack) { 469 return; 470 } 471 if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting in " + this 472 + " by " + amt); 473 Op op = mHead; 474 while (op != null) { 475 op.fragment.mBackStackNesting += amt; 476 if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting of " 477 + op.fragment + " to " + op.fragment.mBackStackNesting); 478 if (op.removed != null) { 479 for (int i=op.removed.size()-1; i>=0; i--) { 480 Fragment r = op.removed.get(i); 481 r.mBackStackNesting += amt; 482 if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting of " 483 + r + " to " + r.mBackStackNesting); 484 } 485 } 486 op = op.next; 487 } 488 } 489 490 public int commit() { 491 return commitInternal(false); 492 } 493 494 public int commitAllowingStateLoss() { 495 return commitInternal(true); 496 } 497 498 int commitInternal(boolean allowStateLoss) { 499 if (mCommitted) throw new IllegalStateException("commit already called"); 500 if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Commit: " + this); 501 mCommitted = true; 502 if (mAddToBackStack) { 503 mIndex = mManager.allocBackStackIndex(this); 504 } else { 505 mIndex = -1; 506 } 507 mManager.enqueueAction(this, allowStateLoss); 508 return mIndex; 509 } 510 511 public void run() { 512 if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Run: " + this); 513 514 if (mAddToBackStack) { 515 if (mIndex < 0) { 516 throw new IllegalStateException("addToBackStack() called after commit()"); 517 } 518 } 519 520 bumpBackStackNesting(1); 521 522 Op op = mHead; 523 while (op != null) { 524 switch (op.cmd) { 525 case OP_ADD: { 526 Fragment f = op.fragment; 527 f.mNextAnim = op.enterAnim; 528 mManager.addFragment(f, false); 529 } break; 530 case OP_REPLACE: { 531 Fragment f = op.fragment; 532 if (mManager.mAdded != null) { 533 for (int i=0; i<mManager.mAdded.size(); i++) { 534 Fragment old = mManager.mAdded.get(i); 535 if (FragmentManagerImpl.DEBUG) Log.v(TAG, 536 "OP_REPLACE: adding=" + f + " old=" + old); 537 if (old.mContainerId == f.mContainerId) { 538 if (op.removed == null) { 539 op.removed = new ArrayList<Fragment>(); 540 } 541 op.removed.add(old); 542 old.mNextAnim = op.exitAnim; 543 if (mAddToBackStack) { 544 old.mBackStackNesting += 1; 545 if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting of " 546 + old + " to " + old.mBackStackNesting); 547 } 548 mManager.removeFragment(old, mTransition, mTransitionStyle); 549 } 550 } 551 } 552 f.mNextAnim = op.enterAnim; 553 mManager.addFragment(f, false); 554 } break; 555 case OP_REMOVE: { 556 Fragment f = op.fragment; 557 f.mNextAnim = op.exitAnim; 558 mManager.removeFragment(f, mTransition, mTransitionStyle); 559 } break; 560 case OP_HIDE: { 561 Fragment f = op.fragment; 562 f.mNextAnim = op.exitAnim; 563 mManager.hideFragment(f, mTransition, mTransitionStyle); 564 } break; 565 case OP_SHOW: { 566 Fragment f = op.fragment; 567 f.mNextAnim = op.enterAnim; 568 mManager.showFragment(f, mTransition, mTransitionStyle); 569 } break; 570 default: { 571 throw new IllegalArgumentException("Unknown cmd: " + op.cmd); 572 } 573 } 574 575 op = op.next; 576 } 577 578 mManager.moveToState(mManager.mCurState, mTransition, 579 mTransitionStyle, true); 580 581 if (mAddToBackStack) { 582 mManager.addBackStackState(this); 583 } 584 } 585 586 public void popFromBackStack(boolean doStateMove) { 587 if (FragmentManagerImpl.DEBUG) Log.v(TAG, "popFromBackStack: " + this); 588 589 bumpBackStackNesting(-1); 590 591 Op op = mTail; 592 while (op != null) { 593 switch (op.cmd) { 594 case OP_ADD: { 595 Fragment f = op.fragment; 596 f.mImmediateActivity = null; 597 mManager.removeFragment(f, 598 FragmentManagerImpl.reverseTransit(mTransition), 599 mTransitionStyle); 600 } break; 601 case OP_REPLACE: { 602 Fragment f = op.fragment; 603 f.mImmediateActivity = null; 604 mManager.removeFragment(f, 605 FragmentManagerImpl.reverseTransit(mTransition), 606 mTransitionStyle); 607 if (op.removed != null) { 608 for (int i=0; i<op.removed.size(); i++) { 609 Fragment old = op.removed.get(i); 610 f.mImmediateActivity = mManager.mActivity; 611 mManager.addFragment(old, false); 612 } 613 } 614 } break; 615 case OP_REMOVE: { 616 Fragment f = op.fragment; 617 f.mImmediateActivity = mManager.mActivity; 618 mManager.addFragment(f, false); 619 } break; 620 case OP_HIDE: { 621 Fragment f = op.fragment; 622 mManager.showFragment(f, 623 FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle); 624 } break; 625 case OP_SHOW: { 626 Fragment f = op.fragment; 627 mManager.hideFragment(f, 628 FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle); 629 } break; 630 default: { 631 throw new IllegalArgumentException("Unknown cmd: " + op.cmd); 632 } 633 } 634 635 op = op.prev; 636 } 637 638 if (doStateMove) { 639 mManager.moveToState(mManager.mCurState, 640 FragmentManagerImpl.reverseTransit(mTransition), mTransitionStyle, true); 641 } 642 643 if (mIndex >= 0) { 644 mManager.freeBackStackIndex(mIndex); 645 mIndex = -1; 646 } 647 } 648 649 public String getName() { 650 return mName; 651 } 652 653 public int getTransition() { 654 return mTransition; 655 } 656 657 public int getTransitionStyle() { 658 return mTransitionStyle; 659 } 660 661 public boolean isEmpty() { 662 return mNumOp == 0; 663 } 664} 665