1/* 2 * Copyright 2018 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 */ 16package androidx.fragment.app; 17 18import static org.junit.Assert.assertEquals; 19import static org.junit.Assert.assertFalse; 20import static org.junit.Assert.assertNotNull; 21import static org.junit.Assert.assertNull; 22import static org.junit.Assert.assertTrue; 23import static org.junit.Assert.fail; 24 25import android.app.Instrumentation; 26import android.os.Build; 27import android.os.Bundle; 28import android.os.Parcelable; 29import android.support.test.InstrumentationRegistry; 30import android.support.test.filters.LargeTest; 31import android.support.test.filters.SdkSuppress; 32import android.support.test.rule.ActivityTestRule; 33import android.support.test.runner.AndroidJUnit4; 34import android.util.Pair; 35import android.view.LayoutInflater; 36import android.view.View; 37import android.view.ViewGroup; 38 39import androidx.fragment.app.test.FragmentTestActivity; 40import androidx.fragment.test.R; 41 42import org.junit.Before; 43import org.junit.Rule; 44import org.junit.Test; 45import org.junit.runner.RunWith; 46 47@LargeTest 48@RunWith(AndroidJUnit4.class) 49@SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP) 50public class PostponedTransitionTest { 51 @Rule 52 public ActivityTestRule<FragmentTestActivity> mActivityRule = 53 new ActivityTestRule<FragmentTestActivity>(FragmentTestActivity.class); 54 55 private Instrumentation mInstrumentation; 56 private PostponedFragment1 mBeginningFragment; 57 58 @Before 59 public void setupContainer() throws Throwable { 60 mInstrumentation = InstrumentationRegistry.getInstrumentation(); 61 FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container); 62 final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager(); 63 mBeginningFragment = new PostponedFragment1(); 64 fm.beginTransaction() 65 .add(R.id.fragmentContainer, mBeginningFragment) 66 .setReorderingAllowed(true) 67 .commit(); 68 FragmentTestUtil.waitForExecution(mActivityRule); 69 70 mBeginningFragment.startPostponedEnterTransition(); 71 mBeginningFragment.waitForTransition(); 72 clearTargets(mBeginningFragment); 73 } 74 75 // Ensure that replacing with a fragment that has a postponed transition 76 // will properly postpone it, both adding and popping. 77 @Test 78 public void replaceTransition() throws Throwable { 79 final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager(); 80 final View startBlue = mActivityRule.getActivity().findViewById(R.id.blueSquare); 81 82 final PostponedFragment2 fragment = new PostponedFragment2(); 83 fm.beginTransaction() 84 .addSharedElement(startBlue, "blueSquare") 85 .replace(R.id.fragmentContainer, fragment) 86 .addToBackStack(null) 87 .setReorderingAllowed(true) 88 .commit(); 89 90 FragmentTestUtil.waitForExecution(mActivityRule); 91 92 // should be postponed now 93 assertPostponedTransition(mBeginningFragment, fragment, null); 94 95 // start the postponed transition 96 fragment.startPostponedEnterTransition(); 97 98 // make sure it ran 99 assertForwardTransition(mBeginningFragment, fragment); 100 101 FragmentTestUtil.popBackStackImmediate(mActivityRule); 102 103 // should be postponed going back, too 104 assertPostponedTransition(fragment, mBeginningFragment, null); 105 106 // start the postponed transition 107 mBeginningFragment.startPostponedEnterTransition(); 108 109 // make sure it ran 110 assertBackTransition(fragment, mBeginningFragment); 111 } 112 113 // Ensure that replacing a fragment doesn't cause problems with the back stack nesting level 114 @Test 115 public void backStackNestingLevel() throws Throwable { 116 final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager(); 117 View startBlue = mActivityRule.getActivity().findViewById(R.id.blueSquare); 118 119 final TransitionFragment fragment1 = new TransitionFragment2(); 120 fm.beginTransaction() 121 .addSharedElement(startBlue, "blueSquare") 122 .replace(R.id.fragmentContainer, fragment1) 123 .addToBackStack(null) 124 .setReorderingAllowed(true) 125 .commit(); 126 127 // make sure transition ran 128 assertForwardTransition(mBeginningFragment, fragment1); 129 130 FragmentTestUtil.popBackStackImmediate(mActivityRule); 131 132 // should be postponed going back 133 assertPostponedTransition(fragment1, mBeginningFragment, null); 134 135 // start the postponed transition 136 mBeginningFragment.startPostponedEnterTransition(); 137 138 // make sure it ran 139 assertBackTransition(fragment1, mBeginningFragment); 140 141 startBlue = mActivityRule.getActivity().findViewById(R.id.blueSquare); 142 143 final TransitionFragment fragment2 = new TransitionFragment2(); 144 fm.beginTransaction() 145 .addSharedElement(startBlue, "blueSquare") 146 .replace(R.id.fragmentContainer, fragment2) 147 .addToBackStack(null) 148 .setReorderingAllowed(true) 149 .commit(); 150 151 // make sure transition ran 152 assertForwardTransition(mBeginningFragment, fragment2); 153 154 FragmentTestUtil.popBackStackImmediate(mActivityRule); 155 156 // should be postponed going back 157 assertPostponedTransition(fragment2, mBeginningFragment, null); 158 159 // start the postponed transition 160 mBeginningFragment.startPostponedEnterTransition(); 161 162 // make sure it ran 163 assertBackTransition(fragment2, mBeginningFragment); 164 } 165 166 // Ensure that postponed transition is forced after another has been committed. 167 // This tests when the transactions are executed together 168 @Test 169 public void forcedTransition1() throws Throwable { 170 final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager(); 171 final View startBlue = mActivityRule.getActivity().findViewById(R.id.blueSquare); 172 173 final PostponedFragment2 fragment2 = new PostponedFragment2(); 174 final PostponedFragment1 fragment3 = new PostponedFragment1(); 175 176 final int[] commit = new int[1]; 177 // Need to run this on the UI thread so that the transaction doesn't start 178 // between the two 179 mInstrumentation.runOnMainSync(new Runnable() { 180 @Override 181 public void run() { 182 commit[0] = fm.beginTransaction() 183 .addSharedElement(startBlue, "blueSquare") 184 .replace(R.id.fragmentContainer, fragment2) 185 .addToBackStack(null) 186 .setReorderingAllowed(true) 187 .commit(); 188 189 fm.beginTransaction() 190 .addSharedElement(startBlue, "blueSquare") 191 .replace(R.id.fragmentContainer, fragment3) 192 .addToBackStack(null) 193 .setReorderingAllowed(true) 194 .commit(); 195 } 196 }); 197 FragmentTestUtil.waitForExecution(mActivityRule); 198 199 // transition to fragment2 should be started 200 assertForwardTransition(mBeginningFragment, fragment2); 201 202 // fragment3 should be postponed, but fragment2 should be executed with no transition. 203 assertPostponedTransition(fragment2, fragment3, mBeginningFragment); 204 205 // start the postponed transition 206 fragment3.startPostponedEnterTransition(); 207 208 // make sure it ran 209 assertForwardTransition(fragment2, fragment3); 210 211 FragmentTestUtil.popBackStackImmediate(mActivityRule, commit[0], 212 FragmentManager.POP_BACK_STACK_INCLUSIVE); 213 214 assertBackTransition(fragment3, fragment2); 215 216 assertPostponedTransition(fragment2, mBeginningFragment, fragment3); 217 218 // start the postponed transition 219 mBeginningFragment.startPostponedEnterTransition(); 220 221 // make sure it ran 222 assertBackTransition(fragment2, mBeginningFragment); 223 } 224 225 // Ensure that postponed transition is forced after another has been committed. 226 // This tests when the transactions are processed separately. 227 @Test 228 public void forcedTransition2() throws Throwable { 229 final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager(); 230 final View startBlue = mActivityRule.getActivity().findViewById(R.id.blueSquare); 231 232 final PostponedFragment2 fragment2 = new PostponedFragment2(); 233 234 fm.beginTransaction() 235 .addSharedElement(startBlue, "blueSquare") 236 .replace(R.id.fragmentContainer, fragment2) 237 .addToBackStack(null) 238 .setReorderingAllowed(true) 239 .commit(); 240 241 FragmentTestUtil.waitForExecution(mActivityRule); 242 243 assertPostponedTransition(mBeginningFragment, fragment2, null); 244 245 final PostponedFragment1 fragment3 = new PostponedFragment1(); 246 fm.beginTransaction() 247 .addSharedElement(startBlue, "blueSquare") 248 .replace(R.id.fragmentContainer, fragment3) 249 .addToBackStack(null) 250 .setReorderingAllowed(true) 251 .commit(); 252 253 // This should cancel the mBeginningFragment -> fragment2 transition 254 // and start fragment2 -> fragment3 transition postponed 255 FragmentTestUtil.waitForExecution(mActivityRule); 256 257 // fragment3 should be postponed, but fragment2 should be executed with no transition. 258 assertPostponedTransition(fragment2, fragment3, mBeginningFragment); 259 260 // start the postponed transition 261 fragment3.startPostponedEnterTransition(); 262 263 // make sure it ran 264 assertForwardTransition(fragment2, fragment3); 265 266 // Pop back to fragment2, but it should be postponed 267 FragmentTestUtil.popBackStackImmediate(mActivityRule); 268 269 assertPostponedTransition(fragment3, fragment2, null); 270 271 // Pop to mBeginningFragment -- should cancel the fragment2 transition and 272 // start the mBeginningFragment transaction postponed 273 274 FragmentTestUtil.popBackStackImmediate(mActivityRule); 275 276 assertPostponedTransition(fragment2, mBeginningFragment, fragment3); 277 278 // start the postponed transition 279 mBeginningFragment.startPostponedEnterTransition(); 280 281 // make sure it ran 282 assertBackTransition(fragment2, mBeginningFragment); 283 } 284 285 // Do a bunch of things to one fragment in a transaction and see if it can screw things up. 286 @Test 287 public void crazyTransition() throws Throwable { 288 final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager(); 289 final View startBlue = mActivityRule.getActivity().findViewById(R.id.blueSquare); 290 291 final PostponedFragment2 fragment2 = new PostponedFragment2(); 292 293 fm.beginTransaction() 294 .addSharedElement(startBlue, "blueSquare") 295 .hide(mBeginningFragment) 296 .replace(R.id.fragmentContainer, fragment2) 297 .hide(fragment2) 298 .detach(fragment2) 299 .attach(fragment2) 300 .show(fragment2) 301 .addToBackStack(null) 302 .setReorderingAllowed(true) 303 .commit(); 304 305 FragmentTestUtil.waitForExecution(mActivityRule); 306 307 assertPostponedTransition(mBeginningFragment, fragment2, null); 308 309 // start the postponed transition 310 fragment2.startPostponedEnterTransition(); 311 312 // make sure it ran 313 assertForwardTransition(mBeginningFragment, fragment2); 314 315 // Pop back to fragment2, but it should be postponed 316 FragmentTestUtil.popBackStackImmediate(mActivityRule); 317 318 assertPostponedTransition(fragment2, mBeginningFragment, null); 319 320 // start the postponed transition 321 mBeginningFragment.startPostponedEnterTransition(); 322 323 // make sure it ran 324 assertBackTransition(fragment2, mBeginningFragment); 325 } 326 327 // Execute transactions on different containers and ensure that they don't conflict 328 @Test 329 public void differentContainers() throws Throwable { 330 final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager(); 331 fm.beginTransaction() 332 .remove(mBeginningFragment) 333 .setReorderingAllowed(true) 334 .commit(); 335 FragmentTestUtil.waitForExecution(mActivityRule); 336 FragmentTestUtil.setContentView(mActivityRule, R.layout.double_container); 337 338 TransitionFragment fragment1 = new PostponedFragment1(); 339 TransitionFragment fragment2 = new PostponedFragment1(); 340 341 fm.beginTransaction() 342 .add(R.id.fragmentContainer1, fragment1) 343 .add(R.id.fragmentContainer2, fragment2) 344 .setReorderingAllowed(true) 345 .commit(); 346 FragmentTestUtil.waitForExecution(mActivityRule); 347 fragment1.startPostponedEnterTransition(); 348 fragment2.startPostponedEnterTransition(); 349 fragment1.waitForTransition(); 350 fragment2.waitForTransition(); 351 clearTargets(fragment1); 352 clearTargets(fragment2); 353 354 final View startBlue1 = fragment1.getView().findViewById(R.id.blueSquare); 355 final View startBlue2 = fragment2.getView().findViewById(R.id.blueSquare); 356 357 final TransitionFragment fragment3 = new PostponedFragment2(); 358 359 fm.beginTransaction() 360 .addSharedElement(startBlue1, "blueSquare") 361 .replace(R.id.fragmentContainer1, fragment3) 362 .addToBackStack(null) 363 .setReorderingAllowed(true) 364 .commit(); 365 366 FragmentTestUtil.waitForExecution(mActivityRule); 367 368 assertPostponedTransition(fragment1, fragment3, null); 369 370 final TransitionFragment fragment4 = new PostponedFragment2(); 371 372 fm.beginTransaction() 373 .addSharedElement(startBlue2, "blueSquare") 374 .replace(R.id.fragmentContainer2, fragment4) 375 .addToBackStack(null) 376 .setReorderingAllowed(true) 377 .commit(); 378 379 FragmentTestUtil.waitForExecution(mActivityRule); 380 381 assertPostponedTransition(fragment1, fragment3, null); 382 assertPostponedTransition(fragment2, fragment4, null); 383 384 // start the postponed transition 385 fragment3.startPostponedEnterTransition(); 386 387 // make sure only one ran 388 assertForwardTransition(fragment1, fragment3); 389 assertPostponedTransition(fragment2, fragment4, null); 390 391 // start the postponed transition 392 fragment4.startPostponedEnterTransition(); 393 394 // make sure it ran 395 assertForwardTransition(fragment2, fragment4); 396 397 // Pop back to fragment2 -- should be postponed 398 FragmentTestUtil.popBackStackImmediate(mActivityRule); 399 400 assertPostponedTransition(fragment4, fragment2, null); 401 402 // Pop back to fragment1 -- also should be postponed 403 FragmentTestUtil.popBackStackImmediate(mActivityRule); 404 405 assertPostponedTransition(fragment4, fragment2, null); 406 assertPostponedTransition(fragment3, fragment1, null); 407 408 // start the postponed transition 409 fragment2.startPostponedEnterTransition(); 410 411 // make sure it ran 412 assertBackTransition(fragment4, fragment2); 413 414 // but not the postponed one 415 assertPostponedTransition(fragment3, fragment1, null); 416 417 // start the postponed transition 418 fragment1.startPostponedEnterTransition(); 419 420 // make sure it ran 421 assertBackTransition(fragment3, fragment1); 422 } 423 424 // Execute transactions on different containers and ensure that they don't conflict. 425 // The postponement can be started out-of-order 426 @Test 427 public void outOfOrderContainers() throws Throwable { 428 final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager(); 429 fm.beginTransaction() 430 .remove(mBeginningFragment) 431 .setReorderingAllowed(true) 432 .commit(); 433 FragmentTestUtil.waitForExecution(mActivityRule); 434 FragmentTestUtil.setContentView(mActivityRule, R.layout.double_container); 435 436 TransitionFragment fragment1 = new PostponedFragment1(); 437 TransitionFragment fragment2 = new PostponedFragment1(); 438 439 fm.beginTransaction() 440 .add(R.id.fragmentContainer1, fragment1) 441 .add(R.id.fragmentContainer2, fragment2) 442 .setReorderingAllowed(true) 443 .commit(); 444 FragmentTestUtil.waitForExecution(mActivityRule); 445 fragment1.startPostponedEnterTransition(); 446 fragment2.startPostponedEnterTransition(); 447 fragment1.waitForTransition(); 448 fragment2.waitForTransition(); 449 clearTargets(fragment1); 450 clearTargets(fragment2); 451 452 final View startBlue1 = fragment1.getView().findViewById(R.id.blueSquare); 453 final View startBlue2 = fragment2.getView().findViewById(R.id.blueSquare); 454 455 final TransitionFragment fragment3 = new PostponedFragment2(); 456 457 fm.beginTransaction() 458 .addSharedElement(startBlue1, "blueSquare") 459 .replace(R.id.fragmentContainer1, fragment3) 460 .addToBackStack(null) 461 .setReorderingAllowed(true) 462 .commit(); 463 464 FragmentTestUtil.waitForExecution(mActivityRule); 465 466 assertPostponedTransition(fragment1, fragment3, null); 467 468 final TransitionFragment fragment4 = new PostponedFragment2(); 469 470 fm.beginTransaction() 471 .addSharedElement(startBlue2, "blueSquare") 472 .replace(R.id.fragmentContainer2, fragment4) 473 .addToBackStack(null) 474 .setReorderingAllowed(true) 475 .commit(); 476 477 FragmentTestUtil.waitForExecution(mActivityRule); 478 479 assertPostponedTransition(fragment1, fragment3, null); 480 assertPostponedTransition(fragment2, fragment4, null); 481 482 // start the postponed transition 483 fragment4.startPostponedEnterTransition(); 484 485 // make sure only one ran 486 assertForwardTransition(fragment2, fragment4); 487 assertPostponedTransition(fragment1, fragment3, null); 488 489 // start the postponed transition 490 fragment3.startPostponedEnterTransition(); 491 492 // make sure it ran 493 assertForwardTransition(fragment1, fragment3); 494 495 // Pop back to fragment2 -- should be postponed 496 FragmentTestUtil.popBackStackImmediate(mActivityRule); 497 498 assertPostponedTransition(fragment4, fragment2, null); 499 500 // Pop back to fragment1 -- also should be postponed 501 FragmentTestUtil.popBackStackImmediate(mActivityRule); 502 503 assertPostponedTransition(fragment4, fragment2, null); 504 assertPostponedTransition(fragment3, fragment1, null); 505 506 // start the postponed transition 507 fragment1.startPostponedEnterTransition(); 508 509 // make sure it ran 510 assertBackTransition(fragment3, fragment1); 511 512 // but not the postponed one 513 assertPostponedTransition(fragment4, fragment2, null); 514 515 // start the postponed transition 516 fragment2.startPostponedEnterTransition(); 517 518 // make sure it ran 519 assertBackTransition(fragment4, fragment2); 520 } 521 522 // Make sure that commitNow for a transaction on a different fragment container doesn't 523 // affect the postponed transaction 524 @Test 525 public void commitNowNoEffect() throws Throwable { 526 final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager(); 527 fm.beginTransaction() 528 .remove(mBeginningFragment) 529 .setReorderingAllowed(true) 530 .commit(); 531 FragmentTestUtil.waitForExecution(mActivityRule); 532 FragmentTestUtil.setContentView(mActivityRule, R.layout.double_container); 533 534 final TransitionFragment fragment1 = new PostponedFragment1(); 535 final TransitionFragment fragment2 = new PostponedFragment1(); 536 537 fm.beginTransaction() 538 .add(R.id.fragmentContainer1, fragment1) 539 .add(R.id.fragmentContainer2, fragment2) 540 .setReorderingAllowed(true) 541 .commit(); 542 FragmentTestUtil.waitForExecution(mActivityRule); 543 fragment1.startPostponedEnterTransition(); 544 fragment2.startPostponedEnterTransition(); 545 fragment1.waitForTransition(); 546 fragment2.waitForTransition(); 547 clearTargets(fragment1); 548 clearTargets(fragment2); 549 550 final View startBlue1 = fragment1.getView().findViewById(R.id.blueSquare); 551 final View startBlue2 = fragment2.getView().findViewById(R.id.blueSquare); 552 553 final TransitionFragment fragment3 = new PostponedFragment2(); 554 final StrictFragment strictFragment1 = new StrictFragment(); 555 556 fm.beginTransaction() 557 .addSharedElement(startBlue1, "blueSquare") 558 .replace(R.id.fragmentContainer1, fragment3) 559 .add(strictFragment1, "1") 560 .addToBackStack(null) 561 .setReorderingAllowed(true) 562 .commit(); 563 564 FragmentTestUtil.waitForExecution(mActivityRule); 565 566 assertPostponedTransition(fragment1, fragment3, null); 567 568 final TransitionFragment fragment4 = new PostponedFragment2(); 569 final StrictFragment strictFragment2 = new StrictFragment(); 570 571 mInstrumentation.runOnMainSync(new Runnable() { 572 @Override 573 public void run() { 574 fm.beginTransaction() 575 .addSharedElement(startBlue2, "blueSquare") 576 .replace(R.id.fragmentContainer2, fragment4) 577 .remove(strictFragment1) 578 .add(strictFragment2, "2") 579 .setReorderingAllowed(true) 580 .commitNow(); 581 } 582 }); 583 584 FragmentTestUtil.waitForExecution(mActivityRule); 585 586 assertPostponedTransition(fragment1, fragment3, null); 587 assertPostponedTransition(fragment2, fragment4, null); 588 589 // start the postponed transition 590 fragment4.startPostponedEnterTransition(); 591 592 // make sure only one ran 593 assertForwardTransition(fragment2, fragment4); 594 assertPostponedTransition(fragment1, fragment3, null); 595 596 // start the postponed transition 597 fragment3.startPostponedEnterTransition(); 598 599 // make sure it ran 600 assertForwardTransition(fragment1, fragment3); 601 } 602 603 // Make sure that commitNow for a transaction affecting a postponed fragment in the same 604 // container forces the postponed transition to start. 605 @Test 606 public void commitNowStartsPostponed() throws Throwable { 607 final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager(); 608 final View startBlue1 = mBeginningFragment.getView().findViewById(R.id.blueSquare); 609 610 final TransitionFragment fragment2 = new PostponedFragment2(); 611 final TransitionFragment fragment1 = new PostponedFragment1(); 612 613 fm.beginTransaction() 614 .addSharedElement(startBlue1, "blueSquare") 615 .replace(R.id.fragmentContainer, fragment2) 616 .addToBackStack(null) 617 .setReorderingAllowed(true) 618 .commit(); 619 FragmentTestUtil.waitForExecution(mActivityRule); 620 621 final View startBlue2 = fragment2.getView().findViewById(R.id.blueSquare); 622 623 mInstrumentation.runOnMainSync(new Runnable() { 624 @Override 625 public void run() { 626 fm.beginTransaction() 627 .addSharedElement(startBlue2, "blueSquare") 628 .replace(R.id.fragmentContainer, fragment1) 629 .setReorderingAllowed(true) 630 .commitNow(); 631 } 632 }); 633 634 assertPostponedTransition(fragment2, fragment1, mBeginningFragment); 635 636 // start the postponed transition 637 fragment1.startPostponedEnterTransition(); 638 639 assertForwardTransition(fragment2, fragment1); 640 } 641 642 // Make sure that when a transaction that removes a view is postponed that 643 // another transaction doesn't accidentally remove the view early. 644 @Test 645 public void noAccidentalRemoval() throws Throwable { 646 final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager(); 647 fm.beginTransaction() 648 .remove(mBeginningFragment) 649 .setReorderingAllowed(true) 650 .commit(); 651 FragmentTestUtil.waitForExecution(mActivityRule); 652 FragmentTestUtil.setContentView(mActivityRule, R.layout.double_container); 653 654 TransitionFragment fragment1 = new PostponedFragment1(); 655 656 fm.beginTransaction() 657 .add(R.id.fragmentContainer1, fragment1) 658 .setReorderingAllowed(true) 659 .commit(); 660 FragmentTestUtil.waitForExecution(mActivityRule); 661 fragment1.startPostponedEnterTransition(); 662 fragment1.waitForTransition(); 663 clearTargets(fragment1); 664 665 TransitionFragment fragment2 = new PostponedFragment2(); 666 // Create a postponed transaction that removes a view 667 fm.beginTransaction() 668 .replace(R.id.fragmentContainer1, fragment2) 669 .setReorderingAllowed(true) 670 .commit(); 671 FragmentTestUtil.waitForExecution(mActivityRule); 672 assertPostponedTransition(fragment1, fragment2, null); 673 674 TransitionFragment fragment3 = new PostponedFragment1(); 675 // Create a transaction that doesn't interfere with the previously postponed one 676 fm.beginTransaction() 677 .replace(R.id.fragmentContainer2, fragment3) 678 .setReorderingAllowed(true) 679 .commit(); 680 FragmentTestUtil.waitForExecution(mActivityRule); 681 682 assertPostponedTransition(fragment1, fragment2, null); 683 684 fragment3.startPostponedEnterTransition(); 685 fragment3.waitForTransition(); 686 clearTargets(fragment3); 687 688 assertPostponedTransition(fragment1, fragment2, null); 689 } 690 691 // Ensure that a postponed transaction that is popped runs immediately and that 692 // the transaction results in the original state with no transition. 693 @Test 694 public void popPostponedTransaction() throws Throwable { 695 final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager(); 696 final View startBlue = mBeginningFragment.getView().findViewById(R.id.blueSquare); 697 698 final TransitionFragment fragment = new PostponedFragment2(); 699 700 fm.beginTransaction() 701 .addSharedElement(startBlue, "blueSquare") 702 .replace(R.id.fragmentContainer, fragment) 703 .addToBackStack(null) 704 .setReorderingAllowed(true) 705 .commit(); 706 FragmentTestUtil.waitForExecution(mActivityRule); 707 708 assertPostponedTransition(mBeginningFragment, fragment, null); 709 710 FragmentTestUtil.popBackStackImmediate(mActivityRule); 711 712 fragment.waitForNoTransition(); 713 mBeginningFragment.waitForNoTransition(); 714 715 assureNoTransition(fragment); 716 assureNoTransition(mBeginningFragment); 717 718 assertFalse(fragment.isAdded()); 719 assertNull(fragment.getView()); 720 assertNotNull(mBeginningFragment.getView()); 721 assertEquals(View.VISIBLE, mBeginningFragment.getView().getVisibility()); 722 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { 723 assertEquals(1f, mBeginningFragment.getView().getAlpha(), 0f); 724 } 725 assertTrue(mBeginningFragment.getView().isAttachedToWindow()); 726 } 727 728 // Make sure that when saving the state during a postponed transaction that it saves 729 // the state as if it wasn't postponed. 730 @Test 731 public void saveWhilePostponed() throws Throwable { 732 final FragmentController fc1 = FragmentTestUtil.createController(mActivityRule); 733 FragmentTestUtil.resume(mActivityRule, fc1, null); 734 735 final FragmentManager fm1 = fc1.getSupportFragmentManager(); 736 737 PostponedFragment1 fragment1 = new PostponedFragment1(); 738 fm1.beginTransaction() 739 .add(R.id.fragmentContainer, fragment1, "1") 740 .addToBackStack(null) 741 .setReorderingAllowed(true) 742 .commit(); 743 FragmentTestUtil.waitForExecution(mActivityRule); 744 745 Pair<Parcelable, FragmentManagerNonConfig> state = 746 FragmentTestUtil.destroy(mActivityRule, fc1); 747 748 final FragmentController fc2 = FragmentTestUtil.createController(mActivityRule); 749 FragmentTestUtil.resume(mActivityRule, fc2, state); 750 751 final FragmentManager fm2 = fc2.getSupportFragmentManager(); 752 Fragment fragment2 = fm2.findFragmentByTag("1"); 753 assertNotNull(fragment2); 754 assertNotNull(fragment2.getView()); 755 assertEquals(View.VISIBLE, fragment2.getView().getVisibility()); 756 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { 757 assertEquals(1f, fragment2.getView().getAlpha(), 0f); 758 } 759 assertTrue(fragment2.isResumed()); 760 assertTrue(fragment2.isAdded()); 761 assertTrue(fragment2.getView().isAttachedToWindow()); 762 763 mInstrumentation.runOnMainSync(new Runnable() { 764 @Override 765 public void run() { 766 assertTrue(fm2.popBackStackImmediate()); 767 768 } 769 }); 770 771 assertFalse(fragment2.isResumed()); 772 assertFalse(fragment2.isAdded()); 773 assertNull(fragment2.getView()); 774 } 775 776 // Ensure that the postponed fragment transactions don't allow reentrancy in fragment manager 777 @Test 778 public void postponeDoesNotAllowReentrancy() throws Throwable { 779 final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager(); 780 final View startBlue = mActivityRule.getActivity().findViewById(R.id.blueSquare); 781 782 final CommitNowFragment fragment = new CommitNowFragment(); 783 fm.beginTransaction() 784 .addSharedElement(startBlue, "blueSquare") 785 .replace(R.id.fragmentContainer, fragment) 786 .setReorderingAllowed(true) 787 .addToBackStack(null) 788 .commit(); 789 790 FragmentTestUtil.waitForExecution(mActivityRule); 791 792 // should be postponed now 793 assertPostponedTransition(mBeginningFragment, fragment, null); 794 795 mActivityRule.runOnUiThread(new Runnable() { 796 @Override 797 public void run() { 798 // start the postponed transition 799 fragment.startPostponedEnterTransition(); 800 801 try { 802 // This should trigger an IllegalStateException 803 fm.executePendingTransactions(); 804 fail("commitNow() while executing a transaction should cause an " 805 + "IllegalStateException"); 806 } catch (IllegalStateException e) { 807 // expected 808 } 809 } 810 }); 811 } 812 813 private void assertPostponedTransition(TransitionFragment fromFragment, 814 TransitionFragment toFragment, TransitionFragment removedFragment) 815 throws InterruptedException { 816 if (removedFragment != null) { 817 assertNull(removedFragment.getView()); 818 assureNoTransition(removedFragment); 819 } 820 821 toFragment.waitForNoTransition(); 822 assertNotNull(fromFragment.getView()); 823 assertNotNull(toFragment.getView()); 824 assertTrue(fromFragment.getView().isAttachedToWindow()); 825 assertTrue(toFragment.getView().isAttachedToWindow()); 826 assertEquals(View.VISIBLE, fromFragment.getView().getVisibility()); 827 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { 828 assertEquals(View.VISIBLE, toFragment.getView().getVisibility()); 829 assertEquals(0f, toFragment.getView().getAlpha(), 0f); 830 } else { 831 assertEquals(View.INVISIBLE, toFragment.getView().getVisibility()); 832 } 833 assureNoTransition(fromFragment); 834 assureNoTransition(toFragment); 835 assertTrue(fromFragment.isResumed()); 836 assertFalse(toFragment.isResumed()); 837 } 838 839 private void clearTargets(TransitionFragment fragment) { 840 fragment.enterTransition.targets.clear(); 841 fragment.reenterTransition.targets.clear(); 842 fragment.exitTransition.targets.clear(); 843 fragment.returnTransition.targets.clear(); 844 fragment.sharedElementEnter.targets.clear(); 845 fragment.sharedElementReturn.targets.clear(); 846 } 847 848 private void assureNoTransition(TransitionFragment fragment) { 849 assertEquals(0, fragment.enterTransition.targets.size()); 850 assertEquals(0, fragment.reenterTransition.targets.size()); 851 assertEquals(0, fragment.enterTransition.targets.size()); 852 assertEquals(0, fragment.returnTransition.targets.size()); 853 assertEquals(0, fragment.sharedElementEnter.targets.size()); 854 assertEquals(0, fragment.sharedElementReturn.targets.size()); 855 } 856 857 private void assertForwardTransition(TransitionFragment start, TransitionFragment end) 858 throws InterruptedException { 859 start.waitForTransition(); 860 end.waitForTransition(); 861 assertEquals(0, start.enterTransition.targets.size()); 862 assertEquals(1, end.enterTransition.targets.size()); 863 864 assertEquals(0, start.reenterTransition.targets.size()); 865 assertEquals(0, end.reenterTransition.targets.size()); 866 867 assertEquals(0, start.returnTransition.targets.size()); 868 assertEquals(0, end.returnTransition.targets.size()); 869 870 assertEquals(1, start.exitTransition.targets.size()); 871 assertEquals(0, end.exitTransition.targets.size()); 872 873 assertEquals(0, start.sharedElementEnter.targets.size()); 874 assertEquals(2, end.sharedElementEnter.targets.size()); 875 876 assertEquals(0, start.sharedElementReturn.targets.size()); 877 assertEquals(0, end.sharedElementReturn.targets.size()); 878 879 final View blue = end.getView().findViewById(R.id.blueSquare); 880 assertTrue(end.sharedElementEnter.targets.contains(blue)); 881 assertEquals("blueSquare", end.sharedElementEnter.targets.get(0).getTransitionName()); 882 assertEquals("blueSquare", end.sharedElementEnter.targets.get(1).getTransitionName()); 883 884 assertNoTargets(start); 885 assertNoTargets(end); 886 887 clearTargets(start); 888 clearTargets(end); 889 } 890 891 private void assertBackTransition(TransitionFragment start, TransitionFragment end) 892 throws InterruptedException { 893 start.waitForTransition(); 894 end.waitForTransition(); 895 assertEquals(1, end.reenterTransition.targets.size()); 896 assertEquals(0, start.reenterTransition.targets.size()); 897 898 assertEquals(0, end.returnTransition.targets.size()); 899 assertEquals(1, start.returnTransition.targets.size()); 900 901 assertEquals(0, start.enterTransition.targets.size()); 902 assertEquals(0, end.enterTransition.targets.size()); 903 904 assertEquals(0, start.exitTransition.targets.size()); 905 assertEquals(0, end.exitTransition.targets.size()); 906 907 assertEquals(0, start.sharedElementEnter.targets.size()); 908 assertEquals(0, end.sharedElementEnter.targets.size()); 909 910 assertEquals(2, start.sharedElementReturn.targets.size()); 911 assertEquals(0, end.sharedElementReturn.targets.size()); 912 913 final View blue = end.getView().findViewById(R.id.blueSquare); 914 assertTrue(start.sharedElementReturn.targets.contains(blue)); 915 assertEquals("blueSquare", start.sharedElementReturn.targets.get(0).getTransitionName()); 916 assertEquals("blueSquare", start.sharedElementReturn.targets.get(1).getTransitionName()); 917 918 assertNoTargets(end); 919 assertNoTargets(start); 920 921 clearTargets(start); 922 clearTargets(end); 923 } 924 925 private static void assertNoTargets(TransitionFragment fragment) { 926 assertTrue(fragment.enterTransition.getTargets().isEmpty()); 927 assertTrue(fragment.reenterTransition.getTargets().isEmpty()); 928 assertTrue(fragment.exitTransition.getTargets().isEmpty()); 929 assertTrue(fragment.returnTransition.getTargets().isEmpty()); 930 assertTrue(fragment.sharedElementEnter.getTargets().isEmpty()); 931 assertTrue(fragment.sharedElementReturn.getTargets().isEmpty()); 932 } 933 934 public static class PostponedFragment1 extends TransitionFragment { 935 @Override 936 public View onCreateView(LayoutInflater inflater, ViewGroup container, 937 Bundle savedInstanceState) { 938 postponeEnterTransition(); 939 return inflater.inflate(R.layout.scene1, container, false); 940 } 941 } 942 943 public static class PostponedFragment2 extends TransitionFragment { 944 @Override 945 public View onCreateView(LayoutInflater inflater, ViewGroup container, 946 Bundle savedInstanceState) { 947 postponeEnterTransition(); 948 return inflater.inflate(R.layout.scene2, container, false); 949 } 950 } 951 952 public static class CommitNowFragment extends PostponedFragment1 { 953 @Override 954 public void onResume() { 955 super.onResume(); 956 // This should throw because this happens during the execution 957 getFragmentManager().beginTransaction() 958 .add(R.id.fragmentContainer, new PostponedFragment1()) 959 .commitNow(); 960 } 961 } 962 963 public static class TransitionFragment2 extends TransitionFragment { 964 @Override 965 public View onCreateView(LayoutInflater inflater, ViewGroup container, 966 Bundle savedInstanceState) { 967 super.onCreateView(inflater, container, savedInstanceState); 968 return inflater.inflate(R.layout.scene2, container, false); 969 } 970 } 971} 972