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.Build; 20import android.os.Parcel; 21import android.os.Parcelable; 22import android.support.v4.util.LogWriter; 23import android.support.v4.view.ViewCompat; 24import android.text.TextUtils; 25import android.util.Log; 26import android.view.View; 27 28import java.io.FileDescriptor; 29import java.io.PrintWriter; 30import java.lang.reflect.Modifier; 31import java.util.ArrayList; 32 33final class BackStackState implements Parcelable { 34 final int[] mOps; 35 final int mTransition; 36 final int mTransitionStyle; 37 final String mName; 38 final int mIndex; 39 final int mBreadCrumbTitleRes; 40 final CharSequence mBreadCrumbTitleText; 41 final int mBreadCrumbShortTitleRes; 42 final CharSequence mBreadCrumbShortTitleText; 43 final ArrayList<String> mSharedElementSourceNames; 44 final ArrayList<String> mSharedElementTargetNames; 45 final boolean mReorderingAllowed; 46 47 public BackStackState(BackStackRecord bse) { 48 final int numOps = bse.mOps.size(); 49 mOps = new int[numOps * 6]; 50 51 if (!bse.mAddToBackStack) { 52 throw new IllegalStateException("Not on back stack"); 53 } 54 55 int pos = 0; 56 for (int opNum = 0; opNum < numOps; opNum++) { 57 final BackStackRecord.Op op = bse.mOps.get(opNum); 58 mOps[pos++] = op.cmd; 59 mOps[pos++] = op.fragment != null ? op.fragment.mIndex : -1; 60 mOps[pos++] = op.enterAnim; 61 mOps[pos++] = op.exitAnim; 62 mOps[pos++] = op.popEnterAnim; 63 mOps[pos++] = op.popExitAnim; 64 } 65 mTransition = bse.mTransition; 66 mTransitionStyle = bse.mTransitionStyle; 67 mName = bse.mName; 68 mIndex = bse.mIndex; 69 mBreadCrumbTitleRes = bse.mBreadCrumbTitleRes; 70 mBreadCrumbTitleText = bse.mBreadCrumbTitleText; 71 mBreadCrumbShortTitleRes = bse.mBreadCrumbShortTitleRes; 72 mBreadCrumbShortTitleText = bse.mBreadCrumbShortTitleText; 73 mSharedElementSourceNames = bse.mSharedElementSourceNames; 74 mSharedElementTargetNames = bse.mSharedElementTargetNames; 75 mReorderingAllowed = bse.mReorderingAllowed; 76 } 77 78 public BackStackState(Parcel in) { 79 mOps = in.createIntArray(); 80 mTransition = in.readInt(); 81 mTransitionStyle = in.readInt(); 82 mName = in.readString(); 83 mIndex = in.readInt(); 84 mBreadCrumbTitleRes = in.readInt(); 85 mBreadCrumbTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 86 mBreadCrumbShortTitleRes = in.readInt(); 87 mBreadCrumbShortTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 88 mSharedElementSourceNames = in.createStringArrayList(); 89 mSharedElementTargetNames = in.createStringArrayList(); 90 mReorderingAllowed = in.readInt() != 0; 91 } 92 93 public BackStackRecord instantiate(FragmentManagerImpl fm) { 94 BackStackRecord bse = new BackStackRecord(fm); 95 int pos = 0; 96 int num = 0; 97 while (pos < mOps.length) { 98 BackStackRecord.Op op = new BackStackRecord.Op(); 99 op.cmd = mOps[pos++]; 100 if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG, 101 "Instantiate " + bse + " op #" + num + " base fragment #" + mOps[pos]); 102 int findex = mOps[pos++]; 103 if (findex >= 0) { 104 Fragment f = fm.mActive.get(findex); 105 op.fragment = f; 106 } else { 107 op.fragment = null; 108 } 109 op.enterAnim = mOps[pos++]; 110 op.exitAnim = mOps[pos++]; 111 op.popEnterAnim = mOps[pos++]; 112 op.popExitAnim = mOps[pos++]; 113 bse.mEnterAnim = op.enterAnim; 114 bse.mExitAnim = op.exitAnim; 115 bse.mPopEnterAnim = op.popEnterAnim; 116 bse.mPopExitAnim = op.popExitAnim; 117 bse.addOp(op); 118 num++; 119 } 120 bse.mTransition = mTransition; 121 bse.mTransitionStyle = mTransitionStyle; 122 bse.mName = mName; 123 bse.mIndex = mIndex; 124 bse.mAddToBackStack = true; 125 bse.mBreadCrumbTitleRes = mBreadCrumbTitleRes; 126 bse.mBreadCrumbTitleText = mBreadCrumbTitleText; 127 bse.mBreadCrumbShortTitleRes = mBreadCrumbShortTitleRes; 128 bse.mBreadCrumbShortTitleText = mBreadCrumbShortTitleText; 129 bse.mSharedElementSourceNames = mSharedElementSourceNames; 130 bse.mSharedElementTargetNames = mSharedElementTargetNames; 131 bse.mReorderingAllowed = mReorderingAllowed; 132 bse.bumpBackStackNesting(1); 133 return bse; 134 } 135 136 @Override 137 public int describeContents() { 138 return 0; 139 } 140 141 @Override 142 public void writeToParcel(Parcel dest, int flags) { 143 dest.writeIntArray(mOps); 144 dest.writeInt(mTransition); 145 dest.writeInt(mTransitionStyle); 146 dest.writeString(mName); 147 dest.writeInt(mIndex); 148 dest.writeInt(mBreadCrumbTitleRes); 149 TextUtils.writeToParcel(mBreadCrumbTitleText, dest, 0); 150 dest.writeInt(mBreadCrumbShortTitleRes); 151 TextUtils.writeToParcel(mBreadCrumbShortTitleText, dest, 0); 152 dest.writeStringList(mSharedElementSourceNames); 153 dest.writeStringList(mSharedElementTargetNames); 154 dest.writeInt(mReorderingAllowed ? 1 : 0); 155 } 156 157 public static final Parcelable.Creator<BackStackState> CREATOR 158 = new Parcelable.Creator<BackStackState>() { 159 @Override 160 public BackStackState createFromParcel(Parcel in) { 161 return new BackStackState(in); 162 } 163 164 @Override 165 public BackStackState[] newArray(int size) { 166 return new BackStackState[size]; 167 } 168 }; 169} 170 171/** 172 * Entry of an operation on the fragment back stack. 173 */ 174final class BackStackRecord extends FragmentTransaction implements 175 FragmentManager.BackStackEntry, FragmentManagerImpl.OpGenerator { 176 static final String TAG = FragmentManagerImpl.TAG; 177 static final boolean SUPPORTS_TRANSITIONS = Build.VERSION.SDK_INT >= 21; 178 179 final FragmentManagerImpl mManager; 180 181 static final int OP_NULL = 0; 182 static final int OP_ADD = 1; 183 static final int OP_REPLACE = 2; 184 static final int OP_REMOVE = 3; 185 static final int OP_HIDE = 4; 186 static final int OP_SHOW = 5; 187 static final int OP_DETACH = 6; 188 static final int OP_ATTACH = 7; 189 static final int OP_SET_PRIMARY_NAV = 8; 190 static final int OP_UNSET_PRIMARY_NAV = 9; 191 192 static final class Op { 193 int cmd; 194 Fragment fragment; 195 int enterAnim; 196 int exitAnim; 197 int popEnterAnim; 198 int popExitAnim; 199 200 Op() { 201 } 202 203 Op(int cmd, Fragment fragment) { 204 this.cmd = cmd; 205 this.fragment = fragment; 206 } 207 } 208 209 ArrayList<Op> mOps = new ArrayList<>(); 210 int mEnterAnim; 211 int mExitAnim; 212 int mPopEnterAnim; 213 int mPopExitAnim; 214 int mTransition; 215 int mTransitionStyle; 216 boolean mAddToBackStack; 217 boolean mAllowAddToBackStack = true; 218 String mName; 219 boolean mCommitted; 220 int mIndex = -1; 221 222 int mBreadCrumbTitleRes; 223 CharSequence mBreadCrumbTitleText; 224 int mBreadCrumbShortTitleRes; 225 CharSequence mBreadCrumbShortTitleText; 226 227 ArrayList<String> mSharedElementSourceNames; 228 ArrayList<String> mSharedElementTargetNames; 229 boolean mReorderingAllowed = false; 230 231 ArrayList<Runnable> mCommitRunnables; 232 233 @Override 234 public String toString() { 235 StringBuilder sb = new StringBuilder(128); 236 sb.append("BackStackEntry{"); 237 sb.append(Integer.toHexString(System.identityHashCode(this))); 238 if (mIndex >= 0) { 239 sb.append(" #"); 240 sb.append(mIndex); 241 } 242 if (mName != null) { 243 sb.append(" "); 244 sb.append(mName); 245 } 246 sb.append("}"); 247 return sb.toString(); 248 } 249 250 public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 251 dump(prefix, writer, true); 252 } 253 254 public void dump(String prefix, PrintWriter writer, boolean full) { 255 if (full) { 256 writer.print(prefix); writer.print("mName="); writer.print(mName); 257 writer.print(" mIndex="); writer.print(mIndex); 258 writer.print(" mCommitted="); writer.println(mCommitted); 259 if (mTransition != FragmentTransaction.TRANSIT_NONE) { 260 writer.print(prefix); writer.print("mTransition=#"); 261 writer.print(Integer.toHexString(mTransition)); 262 writer.print(" mTransitionStyle=#"); 263 writer.println(Integer.toHexString(mTransitionStyle)); 264 } 265 if (mEnterAnim != 0 || mExitAnim !=0) { 266 writer.print(prefix); writer.print("mEnterAnim=#"); 267 writer.print(Integer.toHexString(mEnterAnim)); 268 writer.print(" mExitAnim=#"); 269 writer.println(Integer.toHexString(mExitAnim)); 270 } 271 if (mPopEnterAnim != 0 || mPopExitAnim !=0) { 272 writer.print(prefix); writer.print("mPopEnterAnim=#"); 273 writer.print(Integer.toHexString(mPopEnterAnim)); 274 writer.print(" mPopExitAnim=#"); 275 writer.println(Integer.toHexString(mPopExitAnim)); 276 } 277 if (mBreadCrumbTitleRes != 0 || mBreadCrumbTitleText != null) { 278 writer.print(prefix); writer.print("mBreadCrumbTitleRes=#"); 279 writer.print(Integer.toHexString(mBreadCrumbTitleRes)); 280 writer.print(" mBreadCrumbTitleText="); 281 writer.println(mBreadCrumbTitleText); 282 } 283 if (mBreadCrumbShortTitleRes != 0 || mBreadCrumbShortTitleText != null) { 284 writer.print(prefix); writer.print("mBreadCrumbShortTitleRes=#"); 285 writer.print(Integer.toHexString(mBreadCrumbShortTitleRes)); 286 writer.print(" mBreadCrumbShortTitleText="); 287 writer.println(mBreadCrumbShortTitleText); 288 } 289 } 290 291 if (!mOps.isEmpty()) { 292 writer.print(prefix); writer.println("Operations:"); 293 String innerPrefix = prefix + " "; 294 final int numOps = mOps.size(); 295 for (int opNum = 0; opNum < numOps; opNum++) { 296 final Op op = mOps.get(opNum); 297 String cmdStr; 298 switch (op.cmd) { 299 case OP_NULL: cmdStr="NULL"; break; 300 case OP_ADD: cmdStr="ADD"; break; 301 case OP_REPLACE: cmdStr="REPLACE"; break; 302 case OP_REMOVE: cmdStr="REMOVE"; break; 303 case OP_HIDE: cmdStr="HIDE"; break; 304 case OP_SHOW: cmdStr="SHOW"; break; 305 case OP_DETACH: cmdStr="DETACH"; break; 306 case OP_ATTACH: cmdStr="ATTACH"; break; 307 case OP_SET_PRIMARY_NAV: cmdStr="SET_PRIMARY_NAV"; break; 308 case OP_UNSET_PRIMARY_NAV: cmdStr="UNSET_PRIMARY_NAV";break; 309 default: cmdStr="cmd=" + op.cmd; break; 310 } 311 writer.print(prefix); writer.print(" Op #"); writer.print(opNum); 312 writer.print(": "); writer.print(cmdStr); 313 writer.print(" "); writer.println(op.fragment); 314 if (full) { 315 if (op.enterAnim != 0 || op.exitAnim != 0) { 316 writer.print(prefix); writer.print("enterAnim=#"); 317 writer.print(Integer.toHexString(op.enterAnim)); 318 writer.print(" exitAnim=#"); 319 writer.println(Integer.toHexString(op.exitAnim)); 320 } 321 if (op.popEnterAnim != 0 || op.popExitAnim != 0) { 322 writer.print(prefix); writer.print("popEnterAnim=#"); 323 writer.print(Integer.toHexString(op.popEnterAnim)); 324 writer.print(" popExitAnim=#"); 325 writer.println(Integer.toHexString(op.popExitAnim)); 326 } 327 } 328 } 329 } 330 } 331 332 public BackStackRecord(FragmentManagerImpl manager) { 333 mManager = manager; 334 } 335 336 @Override 337 public int getId() { 338 return mIndex; 339 } 340 341 @Override 342 public int getBreadCrumbTitleRes() { 343 return mBreadCrumbTitleRes; 344 } 345 346 @Override 347 public int getBreadCrumbShortTitleRes() { 348 return mBreadCrumbShortTitleRes; 349 } 350 351 @Override 352 public CharSequence getBreadCrumbTitle() { 353 if (mBreadCrumbTitleRes != 0) { 354 return mManager.mHost.getContext().getText(mBreadCrumbTitleRes); 355 } 356 return mBreadCrumbTitleText; 357 } 358 359 @Override 360 public CharSequence getBreadCrumbShortTitle() { 361 if (mBreadCrumbShortTitleRes != 0) { 362 return mManager.mHost.getContext().getText(mBreadCrumbShortTitleRes); 363 } 364 return mBreadCrumbShortTitleText; 365 } 366 367 void addOp(Op op) { 368 mOps.add(op); 369 op.enterAnim = mEnterAnim; 370 op.exitAnim = mExitAnim; 371 op.popEnterAnim = mPopEnterAnim; 372 op.popExitAnim = mPopExitAnim; 373 } 374 375 @Override 376 public FragmentTransaction add(Fragment fragment, String tag) { 377 doAddOp(0, fragment, tag, OP_ADD); 378 return this; 379 } 380 381 @Override 382 public FragmentTransaction add(int containerViewId, Fragment fragment) { 383 doAddOp(containerViewId, fragment, null, OP_ADD); 384 return this; 385 } 386 387 @Override 388 public FragmentTransaction add(int containerViewId, Fragment fragment, String tag) { 389 doAddOp(containerViewId, fragment, tag, OP_ADD); 390 return this; 391 } 392 393 private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) { 394 final Class fragmentClass = fragment.getClass(); 395 final int modifiers = fragmentClass.getModifiers(); 396 if (fragmentClass.isAnonymousClass() || !Modifier.isPublic(modifiers) 397 || (fragmentClass.isMemberClass() && !Modifier.isStatic(modifiers))) { 398 throw new IllegalStateException("Fragment " + fragmentClass.getCanonicalName() 399 + " must be a public static class to be properly recreated from" 400 + " instance state."); 401 } 402 403 fragment.mFragmentManager = mManager; 404 405 if (tag != null) { 406 if (fragment.mTag != null && !tag.equals(fragment.mTag)) { 407 throw new IllegalStateException("Can't change tag of fragment " 408 + fragment + ": was " + fragment.mTag 409 + " now " + tag); 410 } 411 fragment.mTag = tag; 412 } 413 414 if (containerViewId != 0) { 415 if (containerViewId == View.NO_ID) { 416 throw new IllegalArgumentException("Can't add fragment " 417 + fragment + " with tag " + tag + " to container view with no id"); 418 } 419 if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) { 420 throw new IllegalStateException("Can't change container ID of fragment " 421 + fragment + ": was " + fragment.mFragmentId 422 + " now " + containerViewId); 423 } 424 fragment.mContainerId = fragment.mFragmentId = containerViewId; 425 } 426 427 addOp(new Op(opcmd, fragment)); 428 } 429 430 @Override 431 public FragmentTransaction replace(int containerViewId, Fragment fragment) { 432 return replace(containerViewId, fragment, null); 433 } 434 435 @Override 436 public FragmentTransaction replace(int containerViewId, Fragment fragment, String tag) { 437 if (containerViewId == 0) { 438 throw new IllegalArgumentException("Must use non-zero containerViewId"); 439 } 440 441 doAddOp(containerViewId, fragment, tag, OP_REPLACE); 442 return this; 443 } 444 445 @Override 446 public FragmentTransaction remove(Fragment fragment) { 447 addOp(new Op(OP_REMOVE, fragment)); 448 449 return this; 450 } 451 452 @Override 453 public FragmentTransaction hide(Fragment fragment) { 454 addOp(new Op(OP_HIDE, fragment)); 455 456 return this; 457 } 458 459 @Override 460 public FragmentTransaction show(Fragment fragment) { 461 addOp(new Op(OP_SHOW, fragment)); 462 463 return this; 464 } 465 466 @Override 467 public FragmentTransaction detach(Fragment fragment) { 468 addOp(new Op(OP_DETACH, fragment)); 469 470 return this; 471 } 472 473 @Override 474 public FragmentTransaction attach(Fragment fragment) { 475 addOp(new Op(OP_ATTACH, fragment)); 476 477 return this; 478 } 479 480 @Override 481 public FragmentTransaction setPrimaryNavigationFragment(Fragment fragment) { 482 addOp(new Op(OP_SET_PRIMARY_NAV, fragment)); 483 484 return this; 485 } 486 487 @Override 488 public FragmentTransaction setCustomAnimations(int enter, int exit) { 489 return setCustomAnimations(enter, exit, 0, 0); 490 } 491 492 @Override 493 public FragmentTransaction setCustomAnimations(int enter, int exit, 494 int popEnter, int popExit) { 495 mEnterAnim = enter; 496 mExitAnim = exit; 497 mPopEnterAnim = popEnter; 498 mPopExitAnim = popExit; 499 return this; 500 } 501 502 @Override 503 public FragmentTransaction setTransition(int transition) { 504 mTransition = transition; 505 return this; 506 } 507 508 @Override 509 public FragmentTransaction addSharedElement(View sharedElement, String name) { 510 if (SUPPORTS_TRANSITIONS) { 511 String transitionName = ViewCompat.getTransitionName(sharedElement); 512 if (transitionName == null) { 513 throw new IllegalArgumentException("Unique transitionNames are required for all" + 514 " sharedElements"); 515 } 516 if (mSharedElementSourceNames == null) { 517 mSharedElementSourceNames = new ArrayList<String>(); 518 mSharedElementTargetNames = new ArrayList<String>(); 519 } else if (mSharedElementTargetNames.contains(name)) { 520 throw new IllegalArgumentException("A shared element with the target name '" 521 + name + "' has already been added to the transaction."); 522 } else if (mSharedElementSourceNames.contains(transitionName)) { 523 throw new IllegalArgumentException("A shared element with the source name '" 524 + transitionName + " has already been added to the transaction."); 525 } 526 527 mSharedElementSourceNames.add(transitionName); 528 mSharedElementTargetNames.add(name); 529 } 530 return this; 531 } 532 533 @Override 534 public FragmentTransaction setTransitionStyle(int styleRes) { 535 mTransitionStyle = styleRes; 536 return this; 537 } 538 539 @Override 540 public FragmentTransaction addToBackStack(String name) { 541 if (!mAllowAddToBackStack) { 542 throw new IllegalStateException( 543 "This FragmentTransaction is not allowed to be added to the back stack."); 544 } 545 mAddToBackStack = true; 546 mName = name; 547 return this; 548 } 549 550 @Override 551 public boolean isAddToBackStackAllowed() { 552 return mAllowAddToBackStack; 553 } 554 555 @Override 556 public FragmentTransaction disallowAddToBackStack() { 557 if (mAddToBackStack) { 558 throw new IllegalStateException( 559 "This transaction is already being added to the back stack"); 560 } 561 mAllowAddToBackStack = false; 562 return this; 563 } 564 565 @Override 566 public FragmentTransaction setBreadCrumbTitle(int res) { 567 mBreadCrumbTitleRes = res; 568 mBreadCrumbTitleText = null; 569 return this; 570 } 571 572 @Override 573 public FragmentTransaction setBreadCrumbTitle(CharSequence text) { 574 mBreadCrumbTitleRes = 0; 575 mBreadCrumbTitleText = text; 576 return this; 577 } 578 579 @Override 580 public FragmentTransaction setBreadCrumbShortTitle(int res) { 581 mBreadCrumbShortTitleRes = res; 582 mBreadCrumbShortTitleText = null; 583 return this; 584 } 585 586 @Override 587 public FragmentTransaction setBreadCrumbShortTitle(CharSequence text) { 588 mBreadCrumbShortTitleRes = 0; 589 mBreadCrumbShortTitleText = text; 590 return this; 591 } 592 593 void bumpBackStackNesting(int amt) { 594 if (!mAddToBackStack) { 595 return; 596 } 597 if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting in " + this 598 + " by " + amt); 599 final int numOps = mOps.size(); 600 for (int opNum = 0; opNum < numOps; opNum++) { 601 final Op op = mOps.get(opNum); 602 if (op.fragment != null) { 603 op.fragment.mBackStackNesting += amt; 604 if (FragmentManagerImpl.DEBUG) Log.v(TAG, "Bump nesting of " 605 + op.fragment + " to " + op.fragment.mBackStackNesting); 606 } 607 } 608 } 609 610 @Override 611 public FragmentTransaction runOnCommit(Runnable runnable) { 612 if (runnable == null) { 613 throw new IllegalArgumentException("runnable cannot be null"); 614 } 615 disallowAddToBackStack(); 616 if (mCommitRunnables == null) { 617 mCommitRunnables = new ArrayList<>(); 618 } 619 mCommitRunnables.add(runnable); 620 return this; 621 } 622 623 public void runOnCommitRunnables() { 624 if (mCommitRunnables != null) { 625 for (int i = 0, N = mCommitRunnables.size(); i < N; i++) { 626 mCommitRunnables.get(i).run(); 627 } 628 mCommitRunnables = null; 629 } 630 } 631 632 @Override 633 public int commit() { 634 return commitInternal(false); 635 } 636 637 @Override 638 public int commitAllowingStateLoss() { 639 return commitInternal(true); 640 } 641 642 @Override 643 public void commitNow() { 644 disallowAddToBackStack(); 645 mManager.execSingleAction(this, false); 646 } 647 648 @Override 649 public void commitNowAllowingStateLoss() { 650 disallowAddToBackStack(); 651 mManager.execSingleAction(this, true); 652 } 653 654 @Override 655 public FragmentTransaction setReorderingAllowed(boolean reorderingAllowed) { 656 mReorderingAllowed = reorderingAllowed; 657 return this; 658 } 659 660 @Override 661 public FragmentTransaction setAllowOptimization(boolean allowOptimization) { 662 return setReorderingAllowed(allowOptimization); 663 } 664 665 int commitInternal(boolean allowStateLoss) { 666 if (mCommitted) throw new IllegalStateException("commit already called"); 667 if (FragmentManagerImpl.DEBUG) { 668 Log.v(TAG, "Commit: " + this); 669 LogWriter logw = new LogWriter(TAG); 670 PrintWriter pw = new PrintWriter(logw); 671 dump(" ", null, pw, null); 672 pw.close(); 673 } 674 mCommitted = true; 675 if (mAddToBackStack) { 676 mIndex = mManager.allocBackStackIndex(this); 677 } else { 678 mIndex = -1; 679 } 680 mManager.enqueueAction(this, allowStateLoss); 681 return mIndex; 682 } 683 684 /** 685 * Implementation of {@link FragmentManagerImpl.OpGenerator}. 686 * This operation is added to the list of pending actions during {@link #commit()}, and 687 * will be executed on the UI thread to run this FragmentTransaction. 688 * 689 * @param records Modified to add this BackStackRecord 690 * @param isRecordPop Modified to add a false (this isn't a pop) 691 * @return true always because the records and isRecordPop will always be changed 692 */ 693 @Override 694 public boolean generateOps(ArrayList<BackStackRecord> records, ArrayList<Boolean> isRecordPop) { 695 if (FragmentManagerImpl.DEBUG) { 696 Log.v(TAG, "Run: " + this); 697 } 698 699 records.add(this); 700 isRecordPop.add(false); 701 if (mAddToBackStack) { 702 mManager.addBackStackState(this); 703 } 704 return true; 705 } 706 707 boolean interactsWith(int containerId) { 708 final int numOps = mOps.size(); 709 for (int opNum = 0; opNum < numOps; opNum++) { 710 final Op op = mOps.get(opNum); 711 final int fragContainer = op.fragment != null ? op.fragment.mContainerId : 0; 712 if (fragContainer != 0 && fragContainer == containerId) { 713 return true; 714 } 715 } 716 return false; 717 } 718 719 boolean interactsWith(ArrayList<BackStackRecord> records, int startIndex, int endIndex) { 720 if (endIndex == startIndex) { 721 return false; 722 } 723 final int numOps = mOps.size(); 724 int lastContainer = -1; 725 for (int opNum = 0; opNum < numOps; opNum++) { 726 final Op op = mOps.get(opNum); 727 final int container = op.fragment != null ? op.fragment.mContainerId : 0; 728 if (container != 0 && container != lastContainer) { 729 lastContainer = container; 730 for (int i = startIndex; i < endIndex; i++) { 731 BackStackRecord record = records.get(i); 732 final int numThoseOps = record.mOps.size(); 733 for (int thoseOpIndex = 0; thoseOpIndex < numThoseOps; thoseOpIndex++) { 734 final Op thatOp = record.mOps.get(thoseOpIndex); 735 final int thatContainer = thatOp.fragment != null 736 ? thatOp.fragment.mContainerId : 0; 737 if (thatContainer == container) { 738 return true; 739 } 740 } 741 } 742 } 743 } 744 return false; 745 } 746 747 /** 748 * Executes the operations contained within this transaction. The Fragment states will only 749 * be modified if optimizations are not allowed. 750 */ 751 void executeOps() { 752 final int numOps = mOps.size(); 753 for (int opNum = 0; opNum < numOps; opNum++) { 754 final Op op = mOps.get(opNum); 755 final Fragment f = op.fragment; 756 if (f != null) { 757 f.setNextTransition(mTransition, mTransitionStyle); 758 } 759 switch (op.cmd) { 760 case OP_ADD: 761 f.setNextAnim(op.enterAnim); 762 mManager.addFragment(f, false); 763 break; 764 case OP_REMOVE: 765 f.setNextAnim(op.exitAnim); 766 mManager.removeFragment(f); 767 break; 768 case OP_HIDE: 769 f.setNextAnim(op.exitAnim); 770 mManager.hideFragment(f); 771 break; 772 case OP_SHOW: 773 f.setNextAnim(op.enterAnim); 774 mManager.showFragment(f); 775 break; 776 case OP_DETACH: 777 f.setNextAnim(op.exitAnim); 778 mManager.detachFragment(f); 779 break; 780 case OP_ATTACH: 781 f.setNextAnim(op.enterAnim); 782 mManager.attachFragment(f); 783 break; 784 case OP_SET_PRIMARY_NAV: 785 mManager.setPrimaryNavigationFragment(f); 786 break; 787 case OP_UNSET_PRIMARY_NAV: 788 mManager.setPrimaryNavigationFragment(null); 789 break; 790 default: 791 throw new IllegalArgumentException("Unknown cmd: " + op.cmd); 792 } 793 if (!mReorderingAllowed && op.cmd != OP_ADD && f != null) { 794 mManager.moveFragmentToExpectedState(f); 795 } 796 } 797 if (!mReorderingAllowed) { 798 // Added fragments are added at the end to comply with prior behavior. 799 mManager.moveToState(mManager.mCurState, true); 800 } 801 } 802 803 /** 804 * Reverses the execution of the operations within this transaction. The Fragment states will 805 * only be modified if reordering is not allowed. 806 * 807 * @param moveToState {@code true} if added fragments should be moved to their final state 808 * in ordered transactions 809 */ 810 void executePopOps(boolean moveToState) { 811 for (int opNum = mOps.size() - 1; opNum >= 0; opNum--) { 812 final Op op = mOps.get(opNum); 813 Fragment f = op.fragment; 814 if (f != null) { 815 f.setNextTransition(FragmentManagerImpl.reverseTransit(mTransition), 816 mTransitionStyle); 817 } 818 switch (op.cmd) { 819 case OP_ADD: 820 f.setNextAnim(op.popExitAnim); 821 mManager.removeFragment(f); 822 break; 823 case OP_REMOVE: 824 f.setNextAnim(op.popEnterAnim); 825 mManager.addFragment(f, false); 826 break; 827 case OP_HIDE: 828 f.setNextAnim(op.popEnterAnim); 829 mManager.showFragment(f); 830 break; 831 case OP_SHOW: 832 f.setNextAnim(op.popExitAnim); 833 mManager.hideFragment(f); 834 break; 835 case OP_DETACH: 836 f.setNextAnim(op.popEnterAnim); 837 mManager.attachFragment(f); 838 break; 839 case OP_ATTACH: 840 f.setNextAnim(op.popExitAnim); 841 mManager.detachFragment(f); 842 break; 843 case OP_SET_PRIMARY_NAV: 844 mManager.setPrimaryNavigationFragment(null); 845 break; 846 case OP_UNSET_PRIMARY_NAV: 847 mManager.setPrimaryNavigationFragment(f); 848 break; 849 default: 850 throw new IllegalArgumentException("Unknown cmd: " + op.cmd); 851 } 852 if (!mReorderingAllowed && op.cmd != OP_REMOVE && f != null) { 853 mManager.moveFragmentToExpectedState(f); 854 } 855 } 856 if (!mReorderingAllowed && moveToState) { 857 mManager.moveToState(mManager.mCurState, true); 858 } 859 } 860 861 /** 862 * Expands all meta-ops into their more primitive equivalents. This must be called prior to 863 * {@link #executeOps()} or any other call that operations on mOps for forward navigation. 864 * It should not be called for pop/reverse navigation operations. 865 * 866 * <p>Removes all OP_REPLACE ops and replaces them with the proper add and remove 867 * operations that are equivalent to the replace.</p> 868 * 869 * <p>Adds OP_UNSET_PRIMARY_NAV ops to match OP_SET_PRIMARY_NAV, OP_REMOVE and OP_DETACH 870 * ops so that we can restore the old primary nav fragment later. Since callers call this 871 * method in a loop before running ops from several transactions at once, the caller should 872 * pass the return value from this method as the oldPrimaryNav parameter for the next call. 873 * The first call in such a loop should pass the value of 874 * {@link FragmentManager#getPrimaryNavigationFragment()}.</p> 875 * 876 * @param added Initialized to the fragments that are in the mManager.mAdded, this 877 * will be modified to contain the fragments that will be in mAdded 878 * after the execution ({@link #executeOps()}. 879 * @param oldPrimaryNav The tracked primary navigation fragment as of the beginning of 880 * this set of ops 881 * @return the new oldPrimaryNav fragment after this record's ops would be run 882 */ 883 @SuppressWarnings("ReferenceEquality") 884 Fragment expandOps(ArrayList<Fragment> added, Fragment oldPrimaryNav) { 885 for (int opNum = 0; opNum < mOps.size(); opNum++) { 886 final Op op = mOps.get(opNum); 887 switch (op.cmd) { 888 case OP_ADD: 889 case OP_ATTACH: 890 added.add(op.fragment); 891 break; 892 case OP_REMOVE: 893 case OP_DETACH: { 894 added.remove(op.fragment); 895 if (op.fragment == oldPrimaryNav) { 896 mOps.add(opNum, new Op(OP_UNSET_PRIMARY_NAV, op.fragment)); 897 opNum++; 898 oldPrimaryNav = null; 899 } 900 } 901 break; 902 case OP_REPLACE: { 903 final Fragment f = op.fragment; 904 final int containerId = f.mContainerId; 905 boolean alreadyAdded = false; 906 for (int i = added.size() - 1; i >= 0; i--) { 907 final Fragment old = added.get(i); 908 if (old.mContainerId == containerId) { 909 if (old == f) { 910 alreadyAdded = true; 911 } else { 912 // This is duplicated from above since we only make 913 // a single pass for expanding ops. Unset any outgoing primary nav. 914 if (old == oldPrimaryNav) { 915 mOps.add(opNum, new Op(OP_UNSET_PRIMARY_NAV, old)); 916 opNum++; 917 oldPrimaryNav = null; 918 } 919 final Op removeOp = new Op(OP_REMOVE, old); 920 removeOp.enterAnim = op.enterAnim; 921 removeOp.popEnterAnim = op.popEnterAnim; 922 removeOp.exitAnim = op.exitAnim; 923 removeOp.popExitAnim = op.popExitAnim; 924 mOps.add(opNum, removeOp); 925 added.remove(old); 926 opNum++; 927 } 928 } 929 } 930 if (alreadyAdded) { 931 mOps.remove(opNum); 932 opNum--; 933 } else { 934 op.cmd = OP_ADD; 935 added.add(f); 936 } 937 } 938 break; 939 case OP_SET_PRIMARY_NAV: { 940 // It's ok if this is null, that means we will restore to no active 941 // primary navigation fragment on a pop. 942 mOps.add(opNum, new Op(OP_UNSET_PRIMARY_NAV, oldPrimaryNav)); 943 opNum++; 944 // Will be set by the OP_SET_PRIMARY_NAV we inserted before when run 945 oldPrimaryNav = op.fragment; 946 } 947 break; 948 } 949 } 950 return oldPrimaryNav; 951 } 952 953 /** 954 * Removes fragments that are added or removed during a pop operation. 955 * 956 * @param added Initialized to the fragments that are in the mManager.mAdded, this 957 * will be modified to contain the fragments that will be in mAdded 958 * after the execution ({@link #executeOps()}. 959 * @param oldPrimaryNav The tracked primary navigation fragment as of the beginning of 960 * this set of ops 961 * @return the new oldPrimaryNav fragment after this record's ops would be popped 962 */ 963 Fragment trackAddedFragmentsInPop(ArrayList<Fragment> added, Fragment oldPrimaryNav) { 964 for (int opNum = 0; opNum < mOps.size(); opNum++) { 965 final Op op = mOps.get(opNum); 966 switch (op.cmd) { 967 case OP_ADD: 968 case OP_ATTACH: 969 added.remove(op.fragment); 970 break; 971 case OP_REMOVE: 972 case OP_DETACH: 973 added.add(op.fragment); 974 break; 975 case OP_UNSET_PRIMARY_NAV: 976 oldPrimaryNav = op.fragment; 977 break; 978 case OP_SET_PRIMARY_NAV: 979 oldPrimaryNav = null; 980 break; 981 } 982 } 983 return oldPrimaryNav; 984 } 985 986 boolean isPostponed() { 987 for (int opNum = 0; opNum < mOps.size(); opNum++) { 988 final Op op = mOps.get(opNum); 989 if (isFragmentPostponed(op)) { 990 return true; 991 } 992 } 993 return false; 994 } 995 996 void setOnStartPostponedListener(Fragment.OnStartEnterTransitionListener listener) { 997 for (int opNum = 0; opNum < mOps.size(); opNum++) { 998 final Op op = mOps.get(opNum); 999 if (isFragmentPostponed(op)) { 1000 op.fragment.setOnStartEnterTransitionListener(listener); 1001 } 1002 } 1003 } 1004 1005 private static boolean isFragmentPostponed(Op op) { 1006 final Fragment fragment = op.fragment; 1007 return fragment != null && fragment.mAdded && fragment.mView != null && !fragment.mDetached 1008 && !fragment.mHidden && fragment.isPostponed(); 1009 } 1010 1011 @Override 1012 public String getName() { 1013 return mName; 1014 } 1015 1016 public int getTransition() { 1017 return mTransition; 1018 } 1019 1020 public int getTransitionStyle() { 1021 return mTransitionStyle; 1022 } 1023 1024 @Override 1025 public boolean isEmpty() { 1026 return mOps.isEmpty(); 1027 } 1028} 1029