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