1 /* 2 * Copyright (C) 2016 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 package android.support.v4.app; 17 18 import static junit.framework.Assert.assertNull; 19 20 import static org.junit.Assert.assertEquals; 21 import static org.junit.Assert.assertTrue; 22 import static org.junit.Assert.fail; 23 import static org.mockito.Mockito.mock; 24 import static org.mockito.Mockito.reset; 25 import static org.mockito.Mockito.verify; 26 27 import android.app.Instrumentation; 28 import android.graphics.Rect; 29 import android.os.Build; 30 import android.os.Bundle; 31 import android.support.fragment.test.R; 32 import android.support.test.InstrumentationRegistry; 33 import android.support.test.filters.MediumTest; 34 import android.support.test.filters.SdkSuppress; 35 import android.support.test.rule.ActivityTestRule; 36 import android.support.v4.app.test.FragmentTestActivity; 37 import android.transition.TransitionSet; 38 import android.view.View; 39 40 import org.junit.After; 41 import org.junit.Before; 42 import org.junit.Rule; 43 import org.junit.Test; 44 import org.junit.runner.RunWith; 45 import org.junit.runners.Parameterized; 46 import org.mockito.ArgumentCaptor; 47 48 import java.util.ArrayList; 49 import java.util.List; 50 import java.util.Map; 51 52 @MediumTest 53 @RunWith(Parameterized.class) 54 @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP) 55 public class FragmentTransitionTest { 56 private final boolean mReorderingAllowed; 57 58 @Parameterized.Parameters data()59 public static Object[] data() { 60 return new Boolean[] { 61 false, true 62 }; 63 } 64 65 @Rule 66 public ActivityTestRule<FragmentTestActivity> mActivityRule = 67 new ActivityTestRule<FragmentTestActivity>(FragmentTestActivity.class); 68 69 private Instrumentation mInstrumentation; 70 private FragmentManager mFragmentManager; 71 private int mOnBackStackChangedTimes; 72 private FragmentManager.OnBackStackChangedListener mOnBackStackChangedListener; 73 FragmentTransitionTest(final boolean reordering)74 public FragmentTransitionTest(final boolean reordering) { 75 mReorderingAllowed = reordering; 76 } 77 78 @Before setup()79 public void setup() throws Throwable { 80 mInstrumentation = InstrumentationRegistry.getInstrumentation(); 81 mFragmentManager = mActivityRule.getActivity().getSupportFragmentManager(); 82 FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container); 83 mOnBackStackChangedTimes = 0; 84 mOnBackStackChangedListener = new FragmentManager.OnBackStackChangedListener() { 85 @Override 86 public void onBackStackChanged() { 87 mOnBackStackChangedTimes++; 88 } 89 }; 90 mFragmentManager.addOnBackStackChangedListener(mOnBackStackChangedListener); 91 } 92 93 @After teardown()94 public void teardown() throws Throwable { 95 mFragmentManager.removeOnBackStackChangedListener(mOnBackStackChangedListener); 96 mOnBackStackChangedListener = null; 97 } 98 99 // Test that normal view transitions (enter, exit, reenter, return) run with 100 // a single fragment. 101 @Test enterExitTransitions()102 public void enterExitTransitions() throws Throwable { 103 // enter transition 104 TransitionFragment fragment = setupInitialFragment(); 105 final View blue = findBlue(); 106 final View green = findBlue(); 107 108 // exit transition 109 mFragmentManager.beginTransaction() 110 .setReorderingAllowed(mReorderingAllowed) 111 .remove(fragment) 112 .addToBackStack(null) 113 .commit(); 114 115 fragment.waitForTransition(); 116 verifyAndClearTransition(fragment.exitTransition, null, green, blue); 117 verifyNoOtherTransitions(fragment); 118 assertEquals(2, mOnBackStackChangedTimes); 119 120 // reenter transition 121 FragmentTestUtil.popBackStackImmediate(mActivityRule); 122 fragment.waitForTransition(); 123 final View green2 = findGreen(); 124 final View blue2 = findBlue(); 125 verifyAndClearTransition(fragment.reenterTransition, null, green2, blue2); 126 verifyNoOtherTransitions(fragment); 127 assertEquals(3, mOnBackStackChangedTimes); 128 129 // return transition 130 FragmentTestUtil.popBackStackImmediate(mActivityRule); 131 fragment.waitForTransition(); 132 verifyAndClearTransition(fragment.returnTransition, null, green2, blue2); 133 verifyNoOtherTransitions(fragment); 134 assertEquals(4, mOnBackStackChangedTimes); 135 } 136 137 // Test that shared elements transition from one fragment to the next 138 // and back during pop. 139 @Test sharedElement()140 public void sharedElement() throws Throwable { 141 TransitionFragment fragment1 = setupInitialFragment(); 142 143 // Now do a transition to scene2 144 TransitionFragment fragment2 = new TransitionFragment(); 145 fragment2.setLayoutId(R.layout.scene2); 146 147 verifyTransition(fragment1, fragment2, "blueSquare"); 148 149 // Now pop the back stack 150 verifyPopTransition(1, fragment2, fragment1); 151 } 152 153 // Test that shared element transitions through multiple fragments work together 154 @Test intermediateFragment()155 public void intermediateFragment() throws Throwable { 156 TransitionFragment fragment1 = setupInitialFragment(); 157 158 final TransitionFragment fragment2 = new TransitionFragment(); 159 fragment2.setLayoutId(R.layout.scene3); 160 161 verifyTransition(fragment1, fragment2, "shared"); 162 163 final TransitionFragment fragment3 = new TransitionFragment(); 164 fragment3.setLayoutId(R.layout.scene2); 165 166 verifyTransition(fragment2, fragment3, "blueSquare"); 167 168 // Should transfer backwards when popping multiple: 169 verifyPopTransition(2, fragment3, fragment1, fragment2); 170 } 171 172 // Adding/removing the same fragment multiple times shouldn't mess anything up 173 @Test removeAdded()174 public void removeAdded() throws Throwable { 175 final TransitionFragment fragment1 = setupInitialFragment(); 176 177 final View startBlue = findBlue(); 178 final View startGreen = findGreen(); 179 180 final TransitionFragment fragment2 = new TransitionFragment(); 181 fragment2.setLayoutId(R.layout.scene2); 182 183 mInstrumentation.runOnMainSync(new Runnable() { 184 @Override 185 public void run() { 186 mFragmentManager.beginTransaction() 187 .setReorderingAllowed(mReorderingAllowed) 188 .replace(R.id.fragmentContainer, fragment2) 189 .replace(R.id.fragmentContainer, fragment1) 190 .replace(R.id.fragmentContainer, fragment2) 191 .addToBackStack(null) 192 .commit(); 193 } 194 }); 195 FragmentTestUtil.waitForExecution(mActivityRule); 196 assertEquals(2, mOnBackStackChangedTimes); 197 198 // should be a normal transition from fragment1 to fragment2 199 fragment2.waitForTransition(); 200 final View endBlue = findBlue(); 201 final View endGreen = findGreen(); 202 verifyAndClearTransition(fragment1.exitTransition, null, startBlue, startGreen); 203 verifyAndClearTransition(fragment2.enterTransition, null, endBlue, endGreen); 204 verifyNoOtherTransitions(fragment1); 205 verifyNoOtherTransitions(fragment2); 206 207 // Pop should also do the same thing 208 FragmentTestUtil.popBackStackImmediate(mActivityRule); 209 assertEquals(3, mOnBackStackChangedTimes); 210 211 fragment1.waitForTransition(); 212 final View popBlue = findBlue(); 213 final View popGreen = findGreen(); 214 verifyAndClearTransition(fragment1.reenterTransition, null, popBlue, popGreen); 215 verifyAndClearTransition(fragment2.returnTransition, null, endBlue, endGreen); 216 verifyNoOtherTransitions(fragment1); 217 verifyNoOtherTransitions(fragment2); 218 } 219 220 // Make sure that shared elements on two different fragment containers don't interact 221 @Test crossContainer()222 public void crossContainer() throws Throwable { 223 FragmentTestUtil.setContentView(mActivityRule, R.layout.double_container); 224 TransitionFragment fragment1 = new TransitionFragment(); 225 fragment1.setLayoutId(R.layout.scene1); 226 TransitionFragment fragment2 = new TransitionFragment(); 227 fragment2.setLayoutId(R.layout.scene1); 228 mFragmentManager.beginTransaction() 229 .setReorderingAllowed(mReorderingAllowed) 230 .add(R.id.fragmentContainer1, fragment1) 231 .add(R.id.fragmentContainer2, fragment2) 232 .addToBackStack(null) 233 .commit(); 234 FragmentTestUtil.waitForExecution(mActivityRule); 235 assertEquals(1, mOnBackStackChangedTimes); 236 237 fragment1.waitForTransition(); 238 final View greenSquare1 = findViewById(fragment1, R.id.greenSquare); 239 final View blueSquare1 = findViewById(fragment1, R.id.blueSquare); 240 verifyAndClearTransition(fragment1.enterTransition, null, greenSquare1, blueSquare1); 241 verifyNoOtherTransitions(fragment1); 242 fragment2.waitForTransition(); 243 final View greenSquare2 = findViewById(fragment2, R.id.greenSquare); 244 final View blueSquare2 = findViewById(fragment2, R.id.blueSquare); 245 verifyAndClearTransition(fragment2.enterTransition, null, greenSquare2, blueSquare2); 246 verifyNoOtherTransitions(fragment2); 247 248 // Make sure the correct transitions are run when the target names 249 // are different in both shared elements. We may fool the system. 250 verifyCrossTransition(false, fragment1, fragment2); 251 252 // Make sure the correct transitions are run when the source names 253 // are different in both shared elements. We may fool the system. 254 verifyCrossTransition(true, fragment1, fragment2); 255 } 256 257 // Make sure that onSharedElementStart and onSharedElementEnd are called 258 @Test callStartEndWithSharedElements()259 public void callStartEndWithSharedElements() throws Throwable { 260 TransitionFragment fragment1 = setupInitialFragment(); 261 262 // Now do a transition to scene2 263 TransitionFragment fragment2 = new TransitionFragment(); 264 fragment2.setLayoutId(R.layout.scene2); 265 266 SharedElementCallback enterCallback = mock(SharedElementCallback.class); 267 fragment2.setEnterSharedElementCallback(enterCallback); 268 269 final View startBlue = findBlue(); 270 271 verifyTransition(fragment1, fragment2, "blueSquare"); 272 273 ArgumentCaptor<List> names = ArgumentCaptor.forClass(List.class); 274 ArgumentCaptor<List> views = ArgumentCaptor.forClass(List.class); 275 ArgumentCaptor<List> snapshots = ArgumentCaptor.forClass(List.class); 276 verify(enterCallback).onSharedElementStart(names.capture(), views.capture(), 277 snapshots.capture()); 278 assertEquals(1, names.getValue().size()); 279 assertEquals(1, views.getValue().size()); 280 assertNull(snapshots.getValue()); 281 assertEquals("blueSquare", names.getValue().get(0)); 282 assertEquals(startBlue, views.getValue().get(0)); 283 284 final View endBlue = findBlue(); 285 286 verify(enterCallback).onSharedElementEnd(names.capture(), views.capture(), 287 snapshots.capture()); 288 assertEquals(1, names.getValue().size()); 289 assertEquals(1, views.getValue().size()); 290 assertNull(snapshots.getValue()); 291 assertEquals("blueSquare", names.getValue().get(0)); 292 assertEquals(endBlue, views.getValue().get(0)); 293 294 // Now pop the back stack 295 reset(enterCallback); 296 verifyPopTransition(1, fragment2, fragment1); 297 298 verify(enterCallback).onSharedElementStart(names.capture(), views.capture(), 299 snapshots.capture()); 300 assertEquals(1, names.getValue().size()); 301 assertEquals(1, views.getValue().size()); 302 assertNull(snapshots.getValue()); 303 assertEquals("blueSquare", names.getValue().get(0)); 304 assertEquals(endBlue, views.getValue().get(0)); 305 306 final View reenterBlue = findBlue(); 307 308 verify(enterCallback).onSharedElementEnd(names.capture(), views.capture(), 309 snapshots.capture()); 310 assertEquals(1, names.getValue().size()); 311 assertEquals(1, views.getValue().size()); 312 assertNull(snapshots.getValue()); 313 assertEquals("blueSquare", names.getValue().get(0)); 314 assertEquals(reenterBlue, views.getValue().get(0)); 315 } 316 317 // Make sure that onMapSharedElement works to change the shared element going out 318 @Test onMapSharedElementOut()319 public void onMapSharedElementOut() throws Throwable { 320 final TransitionFragment fragment1 = setupInitialFragment(); 321 322 // Now do a transition to scene2 323 TransitionFragment fragment2 = new TransitionFragment(); 324 fragment2.setLayoutId(R.layout.scene2); 325 326 final View startBlue = findBlue(); 327 final View startGreen = findGreen(); 328 329 final Rect startGreenBounds = getBoundsOnScreen(startGreen); 330 331 SharedElementCallback mapOut = new SharedElementCallback() { 332 @Override 333 public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) { 334 assertEquals(1, names.size()); 335 assertEquals("blueSquare", names.get(0)); 336 assertEquals(1, sharedElements.size()); 337 assertEquals(startBlue, sharedElements.get("blueSquare")); 338 sharedElements.put("blueSquare", startGreen); 339 } 340 }; 341 fragment1.setExitSharedElementCallback(mapOut); 342 343 mFragmentManager.beginTransaction() 344 .addSharedElement(startBlue, "blueSquare") 345 .replace(R.id.fragmentContainer, fragment2) 346 .setReorderingAllowed(mReorderingAllowed) 347 .addToBackStack(null) 348 .commit(); 349 FragmentTestUtil.waitForExecution(mActivityRule); 350 351 fragment1.waitForTransition(); 352 fragment2.waitForTransition(); 353 354 final View endBlue = findBlue(); 355 final Rect endBlueBounds = getBoundsOnScreen(endBlue); 356 357 verifyAndClearTransition(fragment2.sharedElementEnter, startGreenBounds, startGreen, 358 endBlue); 359 360 SharedElementCallback mapBack = new SharedElementCallback() { 361 @Override 362 public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) { 363 assertEquals(1, names.size()); 364 assertEquals("blueSquare", names.get(0)); 365 assertEquals(1, sharedElements.size()); 366 final View expectedBlue = findViewById(fragment1, R.id.blueSquare); 367 assertEquals(expectedBlue, sharedElements.get("blueSquare")); 368 final View greenSquare = findViewById(fragment1, R.id.greenSquare); 369 sharedElements.put("blueSquare", greenSquare); 370 } 371 }; 372 fragment1.setExitSharedElementCallback(mapBack); 373 374 FragmentTestUtil.popBackStackImmediate(mActivityRule); 375 376 fragment1.waitForTransition(); 377 fragment2.waitForTransition(); 378 379 final View reenterGreen = findGreen(); 380 verifyAndClearTransition(fragment2.sharedElementReturn, endBlueBounds, endBlue, 381 reenterGreen); 382 } 383 384 // Make sure that onMapSharedElement works to change the shared element target 385 @Test onMapSharedElementIn()386 public void onMapSharedElementIn() throws Throwable { 387 TransitionFragment fragment1 = setupInitialFragment(); 388 389 // Now do a transition to scene2 390 final TransitionFragment fragment2 = new TransitionFragment(); 391 fragment2.setLayoutId(R.layout.scene2); 392 393 final View startBlue = findBlue(); 394 final Rect startBlueBounds = getBoundsOnScreen(startBlue); 395 396 SharedElementCallback mapIn = new SharedElementCallback() { 397 @Override 398 public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) { 399 assertEquals(1, names.size()); 400 assertEquals("blueSquare", names.get(0)); 401 assertEquals(1, sharedElements.size()); 402 final View blueSquare = findViewById(fragment2, R.id.blueSquare); 403 assertEquals(blueSquare, sharedElements.get("blueSquare")); 404 final View greenSquare = findViewById(fragment2, R.id.greenSquare); 405 sharedElements.put("blueSquare", greenSquare); 406 } 407 }; 408 fragment2.setEnterSharedElementCallback(mapIn); 409 410 mFragmentManager.beginTransaction() 411 .addSharedElement(startBlue, "blueSquare") 412 .replace(R.id.fragmentContainer, fragment2) 413 .setReorderingAllowed(mReorderingAllowed) 414 .addToBackStack(null) 415 .commit(); 416 FragmentTestUtil.waitForExecution(mActivityRule); 417 418 fragment1.waitForTransition(); 419 fragment2.waitForTransition(); 420 421 final View endGreen = findGreen(); 422 final View endBlue = findBlue(); 423 final Rect endGreenBounds = getBoundsOnScreen(endGreen); 424 425 verifyAndClearTransition(fragment2.sharedElementEnter, startBlueBounds, startBlue, 426 endGreen); 427 428 SharedElementCallback mapBack = new SharedElementCallback() { 429 @Override 430 public void onMapSharedElements(List<String> names, Map<String, View> sharedElements) { 431 assertEquals(1, names.size()); 432 assertEquals("blueSquare", names.get(0)); 433 assertEquals(1, sharedElements.size()); 434 assertEquals(endBlue, sharedElements.get("blueSquare")); 435 sharedElements.put("blueSquare", endGreen); 436 } 437 }; 438 fragment2.setEnterSharedElementCallback(mapBack); 439 440 FragmentTestUtil.popBackStackImmediate(mActivityRule); 441 442 fragment1.waitForTransition(); 443 fragment2.waitForTransition(); 444 445 final View reenterBlue = findBlue(); 446 verifyAndClearTransition(fragment2.sharedElementReturn, endGreenBounds, endGreen, 447 reenterBlue); 448 } 449 450 // Ensure that shared element transitions that have targets properly target the views 451 @Test complexSharedElementTransition()452 public void complexSharedElementTransition() throws Throwable { 453 TransitionFragment fragment1 = setupInitialFragment(); 454 455 // Now do a transition to scene2 456 ComplexTransitionFragment fragment2 = new ComplexTransitionFragment(); 457 fragment2.setLayoutId(R.layout.scene2); 458 459 final View startBlue = findBlue(); 460 final View startGreen = findGreen(); 461 final Rect startBlueBounds = getBoundsOnScreen(startBlue); 462 463 mFragmentManager.beginTransaction() 464 .addSharedElement(startBlue, "blueSquare") 465 .addSharedElement(startGreen, "greenSquare") 466 .replace(R.id.fragmentContainer, fragment2) 467 .addToBackStack(null) 468 .setReorderingAllowed(true) 469 .commit(); 470 FragmentTestUtil.waitForExecution(mActivityRule); 471 assertEquals(2, mOnBackStackChangedTimes); 472 473 fragment1.waitForTransition(); 474 fragment2.waitForTransition(); 475 476 final View endBlue = findBlue(); 477 final View endGreen = findGreen(); 478 final Rect endBlueBounds = getBoundsOnScreen(endBlue); 479 480 verifyAndClearTransition(fragment2.sharedElementEnterTransition1, startBlueBounds, 481 startBlue, endBlue); 482 verifyAndClearTransition(fragment2.sharedElementEnterTransition2, startBlueBounds, 483 startGreen, endGreen); 484 485 // Now see if it works when popped 486 FragmentTestUtil.popBackStackImmediate(mActivityRule); 487 assertEquals(3, mOnBackStackChangedTimes); 488 489 fragment1.waitForTransition(); 490 fragment2.waitForTransition(); 491 492 final View reenterBlue = findBlue(); 493 final View reenterGreen = findGreen(); 494 495 verifyAndClearTransition(fragment2.sharedElementReturnTransition1, endBlueBounds, 496 endBlue, reenterBlue); 497 verifyAndClearTransition(fragment2.sharedElementReturnTransition2, endBlueBounds, 498 endGreen, reenterGreen); 499 } 500 501 // Ensure that after transitions have executed that they don't have any targets or other 502 // unfortunate modifications. 503 @Test transitionsEndUnchanged()504 public void transitionsEndUnchanged() throws Throwable { 505 TransitionFragment fragment1 = setupInitialFragment(); 506 507 // Now do a transition to scene2 508 TransitionFragment fragment2 = new TransitionFragment(); 509 fragment2.setLayoutId(R.layout.scene2); 510 511 verifyTransition(fragment1, fragment2, "blueSquare"); 512 assertEquals(0, fragment1.exitTransition.getTargets().size()); 513 assertEquals(0, fragment2.sharedElementEnter.getTargets().size()); 514 assertEquals(0, fragment2.enterTransition.getTargets().size()); 515 assertNull(fragment1.exitTransition.getEpicenterCallback()); 516 assertNull(fragment2.enterTransition.getEpicenterCallback()); 517 assertNull(fragment2.sharedElementEnter.getEpicenterCallback()); 518 519 // Now pop the back stack 520 verifyPopTransition(1, fragment2, fragment1); 521 522 assertEquals(0, fragment2.returnTransition.getTargets().size()); 523 assertEquals(0, fragment2.sharedElementReturn.getTargets().size()); 524 assertEquals(0, fragment1.reenterTransition.getTargets().size()); 525 assertNull(fragment2.returnTransition.getEpicenterCallback()); 526 assertNull(fragment2.sharedElementReturn.getEpicenterCallback()); 527 assertNull(fragment2.reenterTransition.getEpicenterCallback()); 528 } 529 530 // Ensure that transitions are done when a fragment is shown and hidden 531 @Test showHideTransition()532 public void showHideTransition() throws Throwable { 533 TransitionFragment fragment1 = setupInitialFragment(); 534 TransitionFragment fragment2 = new TransitionFragment(); 535 fragment2.setLayoutId(R.layout.scene2); 536 537 final View startBlue = findBlue(); 538 final View startGreen = findGreen(); 539 540 mFragmentManager.beginTransaction() 541 .setReorderingAllowed(mReorderingAllowed) 542 .add(R.id.fragmentContainer, fragment2) 543 .hide(fragment1) 544 .addToBackStack(null) 545 .commit(); 546 547 FragmentTestUtil.waitForExecution(mActivityRule); 548 fragment1.waitForTransition(); 549 fragment2.waitForTransition(); 550 551 final View endGreen = findViewById(fragment2, R.id.greenSquare); 552 final View endBlue = findViewById(fragment2, R.id.blueSquare); 553 554 assertEquals(View.GONE, fragment1.getView().getVisibility()); 555 assertEquals(View.VISIBLE, startGreen.getVisibility()); 556 assertEquals(View.VISIBLE, startBlue.getVisibility()); 557 558 verifyAndClearTransition(fragment1.exitTransition, null, startGreen, startBlue); 559 verifyNoOtherTransitions(fragment1); 560 561 verifyAndClearTransition(fragment2.enterTransition, null, endGreen, endBlue); 562 verifyNoOtherTransitions(fragment2); 563 564 FragmentTestUtil.popBackStackImmediate(mActivityRule); 565 566 FragmentTestUtil.waitForExecution(mActivityRule); 567 fragment1.waitForTransition(); 568 fragment2.waitForTransition(); 569 570 verifyAndClearTransition(fragment1.reenterTransition, null, startGreen, startBlue); 571 verifyNoOtherTransitions(fragment1); 572 573 assertEquals(View.VISIBLE, fragment1.getView().getVisibility()); 574 assertEquals(View.VISIBLE, startGreen.getVisibility()); 575 assertEquals(View.VISIBLE, startBlue.getVisibility()); 576 577 verifyAndClearTransition(fragment2.returnTransition, null, endGreen, endBlue); 578 verifyNoOtherTransitions(fragment2); 579 } 580 581 // Ensure that transitions are done when a fragment is attached and detached 582 @Test attachDetachTransition()583 public void attachDetachTransition() throws Throwable { 584 TransitionFragment fragment1 = setupInitialFragment(); 585 TransitionFragment fragment2 = new TransitionFragment(); 586 fragment2.setLayoutId(R.layout.scene2); 587 588 final View startBlue = findBlue(); 589 final View startGreen = findGreen(); 590 591 mFragmentManager.beginTransaction() 592 .setReorderingAllowed(mReorderingAllowed) 593 .add(R.id.fragmentContainer, fragment2) 594 .detach(fragment1) 595 .addToBackStack(null) 596 .commit(); 597 598 FragmentTestUtil.waitForExecution(mActivityRule); 599 600 final View endGreen = findViewById(fragment2, R.id.greenSquare); 601 final View endBlue = findViewById(fragment2, R.id.blueSquare); 602 603 verifyAndClearTransition(fragment1.exitTransition, null, startGreen, startBlue); 604 verifyNoOtherTransitions(fragment1); 605 606 verifyAndClearTransition(fragment2.enterTransition, null, endGreen, endBlue); 607 verifyNoOtherTransitions(fragment2); 608 609 FragmentTestUtil.popBackStackImmediate(mActivityRule); 610 611 FragmentTestUtil.waitForExecution(mActivityRule); 612 613 final View reenterBlue = findBlue(); 614 final View reenterGreen = findGreen(); 615 616 verifyAndClearTransition(fragment1.reenterTransition, null, reenterGreen, reenterBlue); 617 verifyNoOtherTransitions(fragment1); 618 619 verifyAndClearTransition(fragment2.returnTransition, null, endGreen, endBlue); 620 verifyNoOtherTransitions(fragment2); 621 } 622 623 // Ensure that shared element without matching transition name doesn't error out 624 @Test sharedElementMismatch()625 public void sharedElementMismatch() throws Throwable { 626 final TransitionFragment fragment1 = setupInitialFragment(); 627 628 // Now do a transition to scene2 629 TransitionFragment fragment2 = new TransitionFragment(); 630 fragment2.setLayoutId(R.layout.scene2); 631 632 final View startBlue = findBlue(); 633 final View startGreen = findGreen(); 634 final Rect startBlueBounds = getBoundsOnScreen(startBlue); 635 636 mFragmentManager.beginTransaction() 637 .addSharedElement(startBlue, "fooSquare") 638 .replace(R.id.fragmentContainer, fragment2) 639 .setReorderingAllowed(mReorderingAllowed) 640 .addToBackStack(null) 641 .commit(); 642 FragmentTestUtil.waitForExecution(mActivityRule); 643 644 fragment1.waitForTransition(); 645 fragment2.waitForTransition(); 646 647 final View endBlue = findBlue(); 648 final View endGreen = findGreen(); 649 650 if (mReorderingAllowed) { 651 verifyAndClearTransition(fragment1.exitTransition, null, startGreen, startBlue); 652 } else { 653 verifyAndClearTransition(fragment1.exitTransition, startBlueBounds, startGreen); 654 verifyAndClearTransition(fragment2.sharedElementEnter, startBlueBounds, startBlue); 655 } 656 verifyNoOtherTransitions(fragment1); 657 658 verifyAndClearTransition(fragment2.enterTransition, null, endGreen, endBlue); 659 verifyNoOtherTransitions(fragment2); 660 } 661 662 // Ensure that using the same source or target shared element results in an exception. 663 @Test sharedDuplicateTargetNames()664 public void sharedDuplicateTargetNames() throws Throwable { 665 setupInitialFragment(); 666 667 final View startBlue = findBlue(); 668 final View startGreen = findGreen(); 669 670 FragmentTransaction ft = mFragmentManager.beginTransaction(); 671 ft.addSharedElement(startBlue, "blueSquare"); 672 try { 673 ft.addSharedElement(startGreen, "blueSquare"); 674 fail("Expected IllegalArgumentException"); 675 } catch (IllegalArgumentException e) { 676 // expected 677 } 678 679 try { 680 ft.addSharedElement(startBlue, "greenSquare"); 681 fail("Expected IllegalArgumentException"); 682 } catch (IllegalArgumentException e) { 683 // expected 684 } 685 } 686 687 // Test that invisible fragment views don't participate in transitions 688 @Test invisibleNoTransitions()689 public void invisibleNoTransitions() throws Throwable { 690 if (!mReorderingAllowed) { 691 return; // only reordered transitions can avoid interaction 692 } 693 // enter transition 694 TransitionFragment fragment = new InvisibleFragment(); 695 fragment.setLayoutId(R.layout.scene1); 696 mFragmentManager.beginTransaction() 697 .setReorderingAllowed(mReorderingAllowed) 698 .add(R.id.fragmentContainer, fragment) 699 .addToBackStack(null) 700 .commit(); 701 FragmentTestUtil.waitForExecution(mActivityRule); 702 fragment.waitForNoTransition(); 703 verifyNoOtherTransitions(fragment); 704 705 // exit transition 706 mFragmentManager.beginTransaction() 707 .setReorderingAllowed(mReorderingAllowed) 708 .remove(fragment) 709 .addToBackStack(null) 710 .commit(); 711 712 fragment.waitForNoTransition(); 713 verifyNoOtherTransitions(fragment); 714 715 // reenter transition 716 FragmentTestUtil.popBackStackImmediate(mActivityRule); 717 fragment.waitForNoTransition(); 718 verifyNoOtherTransitions(fragment); 719 720 // return transition 721 FragmentTestUtil.popBackStackImmediate(mActivityRule); 722 fragment.waitForNoTransition(); 723 verifyNoOtherTransitions(fragment); 724 } 725 726 // No crash when transitioning a shared element and there is no shared element transition. 727 @Test noSharedElementTransition()728 public void noSharedElementTransition() throws Throwable { 729 TransitionFragment fragment1 = setupInitialFragment(); 730 731 final View startBlue = findBlue(); 732 final View startGreen = findGreen(); 733 final Rect startBlueBounds = getBoundsOnScreen(startBlue); 734 735 TransitionFragment fragment2 = new TransitionFragment(); 736 fragment2.setLayoutId(R.layout.scene2); 737 738 mFragmentManager.beginTransaction() 739 .setReorderingAllowed(mReorderingAllowed) 740 .addSharedElement(startBlue, "blueSquare") 741 .replace(R.id.fragmentContainer, fragment2) 742 .addToBackStack(null) 743 .commit(); 744 745 fragment1.waitForTransition(); 746 fragment2.waitForTransition(); 747 final View midGreen = findGreen(); 748 final View midBlue = findBlue(); 749 final Rect midBlueBounds = getBoundsOnScreen(midBlue); 750 verifyAndClearTransition(fragment1.exitTransition, startBlueBounds, startGreen); 751 verifyAndClearTransition(fragment2.sharedElementEnter, startBlueBounds, startBlue, midBlue); 752 verifyAndClearTransition(fragment2.enterTransition, midBlueBounds, midGreen); 753 verifyNoOtherTransitions(fragment1); 754 verifyNoOtherTransitions(fragment2); 755 756 final TransitionFragment fragment3 = new TransitionFragment(); 757 fragment3.setLayoutId(R.layout.scene3); 758 759 mActivityRule.runOnUiThread(new Runnable() { 760 @Override 761 public void run() { 762 FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager(); 763 fm.popBackStack(); 764 fm.beginTransaction() 765 .setReorderingAllowed(mReorderingAllowed) 766 .replace(R.id.fragmentContainer, fragment3) 767 .addToBackStack(null) 768 .commit(); 769 } 770 }); 771 772 // This shouldn't give an error. 773 FragmentTestUtil.executePendingTransactions(mActivityRule); 774 775 fragment2.waitForTransition(); 776 // It does not transition properly for ordered transactions, though. 777 if (mReorderingAllowed) { 778 verifyAndClearTransition(fragment2.returnTransition, null, midGreen, midBlue); 779 final View endGreen = findGreen(); 780 final View endBlue = findBlue(); 781 final View endRed = findRed(); 782 verifyAndClearTransition(fragment3.enterTransition, null, endGreen, endBlue, endRed); 783 verifyNoOtherTransitions(fragment2); 784 verifyNoOtherTransitions(fragment3); 785 } else { 786 // fragment3 doesn't get a transition since it conflicts with the pop transition 787 verifyNoOtherTransitions(fragment3); 788 // Everything else is just doing its best. Ordered transactions can't handle 789 // multiple transitions acting together except for popping multiple together. 790 } 791 } 792 setupInitialFragment()793 private TransitionFragment setupInitialFragment() throws Throwable { 794 TransitionFragment fragment1 = new TransitionFragment(); 795 fragment1.setLayoutId(R.layout.scene1); 796 mFragmentManager.beginTransaction() 797 .setReorderingAllowed(mReorderingAllowed) 798 .add(R.id.fragmentContainer, fragment1) 799 .addToBackStack(null) 800 .commit(); 801 FragmentTestUtil.waitForExecution(mActivityRule); 802 assertEquals(1, mOnBackStackChangedTimes); 803 fragment1.waitForTransition(); 804 final View blueSquare1 = findBlue(); 805 final View greenSquare1 = findGreen(); 806 verifyAndClearTransition(fragment1.enterTransition, null, blueSquare1, greenSquare1); 807 verifyNoOtherTransitions(fragment1); 808 return fragment1; 809 } 810 findViewById(Fragment fragment, int id)811 private View findViewById(Fragment fragment, int id) { 812 return fragment.getView().findViewById(id); 813 } 814 findGreen()815 private View findGreen() { 816 return mActivityRule.getActivity().findViewById(R.id.greenSquare); 817 } 818 findBlue()819 private View findBlue() { 820 return mActivityRule.getActivity().findViewById(R.id.blueSquare); 821 } 822 findRed()823 private View findRed() { 824 return mActivityRule.getActivity().findViewById(R.id.redSquare); 825 } 826 verifyAndClearTransition(TargetTracking transition, Rect epicenter, View... expected)827 private void verifyAndClearTransition(TargetTracking transition, Rect epicenter, 828 View... expected) { 829 if (epicenter == null) { 830 assertNull(transition.getCapturedEpicenter()); 831 } else { 832 assertEquals(epicenter, transition.getCapturedEpicenter()); 833 } 834 ArrayList<View> targets = transition.getTrackedTargets(); 835 StringBuilder sb = new StringBuilder(); 836 sb 837 .append("Expected: [") 838 .append(expected.length) 839 .append("] {"); 840 boolean isFirst = true; 841 for (View view : expected) { 842 if (isFirst) { 843 isFirst = false; 844 } else { 845 sb.append(", "); 846 } 847 sb.append(view); 848 } 849 sb 850 .append("}, but got: [") 851 .append(targets.size()) 852 .append("] {"); 853 isFirst = true; 854 for (View view : targets) { 855 if (isFirst) { 856 isFirst = false; 857 } else { 858 sb.append(", "); 859 } 860 sb.append(view); 861 } 862 sb.append("}"); 863 String errorMessage = sb.toString(); 864 865 assertEquals(errorMessage, expected.length, targets.size()); 866 for (View view : expected) { 867 assertTrue(errorMessage, targets.contains(view)); 868 } 869 transition.clearTargets(); 870 } 871 verifyNoOtherTransitions(TransitionFragment fragment)872 private void verifyNoOtherTransitions(TransitionFragment fragment) { 873 assertEquals(0, fragment.enterTransition.targets.size()); 874 assertEquals(0, fragment.exitTransition.targets.size()); 875 assertEquals(0, fragment.reenterTransition.targets.size()); 876 assertEquals(0, fragment.returnTransition.targets.size()); 877 assertEquals(0, fragment.sharedElementEnter.targets.size()); 878 assertEquals(0, fragment.sharedElementReturn.targets.size()); 879 } 880 verifyTransition(TransitionFragment from, TransitionFragment to, String sharedElementName)881 private void verifyTransition(TransitionFragment from, TransitionFragment to, 882 String sharedElementName) throws Throwable { 883 final int startOnBackStackChanged = mOnBackStackChangedTimes; 884 final View startBlue = findBlue(); 885 final View startGreen = findGreen(); 886 final View startRed = findRed(); 887 888 final Rect startBlueRect = getBoundsOnScreen(startBlue); 889 890 mFragmentManager.beginTransaction() 891 .setReorderingAllowed(mReorderingAllowed) 892 .addSharedElement(startBlue, sharedElementName) 893 .replace(R.id.fragmentContainer, to) 894 .addToBackStack(null) 895 .commit(); 896 897 FragmentTestUtil.waitForExecution(mActivityRule); 898 assertEquals(startOnBackStackChanged + 1, mOnBackStackChangedTimes); 899 900 to.waitForTransition(); 901 final View endGreen = findGreen(); 902 final View endBlue = findBlue(); 903 final View endRed = findRed(); 904 final Rect endBlueRect = getBoundsOnScreen(endBlue); 905 906 if (startRed != null) { 907 verifyAndClearTransition(from.exitTransition, startBlueRect, startGreen, startRed); 908 } else { 909 verifyAndClearTransition(from.exitTransition, startBlueRect, startGreen); 910 } 911 verifyNoOtherTransitions(from); 912 913 if (endRed != null) { 914 verifyAndClearTransition(to.enterTransition, endBlueRect, endGreen, endRed); 915 } else { 916 verifyAndClearTransition(to.enterTransition, endBlueRect, endGreen); 917 } 918 verifyAndClearTransition(to.sharedElementEnter, startBlueRect, startBlue, endBlue); 919 verifyNoOtherTransitions(to); 920 } 921 verifyCrossTransition(boolean swapSource, TransitionFragment from1, TransitionFragment from2)922 private void verifyCrossTransition(boolean swapSource, 923 TransitionFragment from1, TransitionFragment from2) throws Throwable { 924 final int startNumOnBackStackChanged = mOnBackStackChangedTimes; 925 final int changesPerOperation = mReorderingAllowed ? 1 : 2; 926 927 final TransitionFragment to1 = new TransitionFragment(); 928 to1.setLayoutId(R.layout.scene2); 929 final TransitionFragment to2 = new TransitionFragment(); 930 to2.setLayoutId(R.layout.scene2); 931 932 final View fromExit1 = findViewById(from1, R.id.greenSquare); 933 final View fromShared1 = findViewById(from1, R.id.blueSquare); 934 final Rect fromSharedRect1 = getBoundsOnScreen(fromShared1); 935 936 final int fromExitId2 = swapSource ? R.id.blueSquare : R.id.greenSquare; 937 final int fromSharedId2 = swapSource ? R.id.greenSquare : R.id.blueSquare; 938 final View fromExit2 = findViewById(from2, fromExitId2); 939 final View fromShared2 = findViewById(from2, fromSharedId2); 940 final Rect fromSharedRect2 = getBoundsOnScreen(fromShared2); 941 942 final String sharedElementName = swapSource ? "blueSquare" : "greenSquare"; 943 944 mActivityRule.runOnUiThread(new Runnable() { 945 @Override 946 public void run() { 947 mFragmentManager.beginTransaction() 948 .setReorderingAllowed(mReorderingAllowed) 949 .addSharedElement(fromShared1, "blueSquare") 950 .replace(R.id.fragmentContainer1, to1) 951 .addToBackStack(null) 952 .commit(); 953 mFragmentManager.beginTransaction() 954 .setReorderingAllowed(mReorderingAllowed) 955 .addSharedElement(fromShared2, sharedElementName) 956 .replace(R.id.fragmentContainer2, to2) 957 .addToBackStack(null) 958 .commit(); 959 } 960 }); 961 FragmentTestUtil.waitForExecution(mActivityRule); 962 assertEquals(startNumOnBackStackChanged + changesPerOperation, mOnBackStackChangedTimes); 963 964 from1.waitForTransition(); 965 from2.waitForTransition(); 966 to1.waitForTransition(); 967 to2.waitForTransition(); 968 969 final View toEnter1 = findViewById(to1, R.id.greenSquare); 970 final View toShared1 = findViewById(to1, R.id.blueSquare); 971 final Rect toSharedRect1 = getBoundsOnScreen(toShared1); 972 973 final View toEnter2 = findViewById(to2, fromSharedId2); 974 final View toShared2 = findViewById(to2, fromExitId2); 975 final Rect toSharedRect2 = getBoundsOnScreen(toShared2); 976 977 verifyAndClearTransition(from1.exitTransition, fromSharedRect1, fromExit1); 978 verifyAndClearTransition(from2.exitTransition, fromSharedRect2, fromExit2); 979 verifyNoOtherTransitions(from1); 980 verifyNoOtherTransitions(from2); 981 982 verifyAndClearTransition(to1.enterTransition, toSharedRect1, toEnter1); 983 verifyAndClearTransition(to2.enterTransition, toSharedRect2, toEnter2); 984 verifyAndClearTransition(to1.sharedElementEnter, fromSharedRect1, fromShared1, toShared1); 985 verifyAndClearTransition(to2.sharedElementEnter, fromSharedRect2, fromShared2, toShared2); 986 verifyNoOtherTransitions(to1); 987 verifyNoOtherTransitions(to2); 988 989 // Now pop it back 990 mActivityRule.runOnUiThread(new Runnable() { 991 @Override 992 public void run() { 993 mFragmentManager.popBackStack(); 994 mFragmentManager.popBackStack(); 995 } 996 }); 997 FragmentTestUtil.waitForExecution(mActivityRule); 998 assertEquals(startNumOnBackStackChanged + changesPerOperation + 1, 999 mOnBackStackChangedTimes); 1000 1001 from1.waitForTransition(); 1002 from2.waitForTransition(); 1003 to1.waitForTransition(); 1004 to2.waitForTransition(); 1005 1006 final View returnEnter1 = findViewById(from1, R.id.greenSquare); 1007 final View returnShared1 = findViewById(from1, R.id.blueSquare); 1008 1009 final View returnEnter2 = findViewById(from2, fromExitId2); 1010 final View returnShared2 = findViewById(from2, fromSharedId2); 1011 1012 verifyAndClearTransition(to1.returnTransition, toSharedRect1, toEnter1); 1013 verifyAndClearTransition(to2.returnTransition, toSharedRect2, toEnter2); 1014 verifyAndClearTransition(to1.sharedElementReturn, toSharedRect1, toShared1, returnShared1); 1015 verifyAndClearTransition(to2.sharedElementReturn, toSharedRect2, toShared2, returnShared2); 1016 verifyNoOtherTransitions(to1); 1017 verifyNoOtherTransitions(to2); 1018 1019 verifyAndClearTransition(from1.reenterTransition, fromSharedRect1, returnEnter1); 1020 verifyAndClearTransition(from2.reenterTransition, fromSharedRect2, returnEnter2); 1021 verifyNoOtherTransitions(from1); 1022 verifyNoOtherTransitions(from2); 1023 } 1024 verifyPopTransition(final int numPops, TransitionFragment from, TransitionFragment to, TransitionFragment... others)1025 private void verifyPopTransition(final int numPops, TransitionFragment from, 1026 TransitionFragment to, TransitionFragment... others) throws Throwable { 1027 final int startOnBackStackChanged = mOnBackStackChangedTimes; 1028 final View startBlue = findBlue(); 1029 final View startGreen = findGreen(); 1030 final View startRed = findRed(); 1031 final Rect startSharedRect = getBoundsOnScreen(startBlue); 1032 1033 mInstrumentation.runOnMainSync(new Runnable() { 1034 @Override 1035 public void run() { 1036 for (int i = 0; i < numPops; i++) { 1037 mFragmentManager.popBackStack(); 1038 } 1039 } 1040 }); 1041 FragmentTestUtil.waitForExecution(mActivityRule); 1042 assertEquals(startOnBackStackChanged + 1, mOnBackStackChangedTimes); 1043 1044 to.waitForTransition(); 1045 final View endGreen = findGreen(); 1046 final View endBlue = findBlue(); 1047 final View endRed = findRed(); 1048 final Rect endSharedRect = getBoundsOnScreen(endBlue); 1049 1050 if (startRed != null) { 1051 verifyAndClearTransition(from.returnTransition, startSharedRect, startGreen, startRed); 1052 } else { 1053 verifyAndClearTransition(from.returnTransition, startSharedRect, startGreen); 1054 } 1055 verifyAndClearTransition(from.sharedElementReturn, startSharedRect, startBlue, endBlue); 1056 verifyNoOtherTransitions(from); 1057 1058 if (endRed != null) { 1059 verifyAndClearTransition(to.reenterTransition, endSharedRect, endGreen, endRed); 1060 } else { 1061 verifyAndClearTransition(to.reenterTransition, endSharedRect, endGreen); 1062 } 1063 verifyNoOtherTransitions(to); 1064 1065 if (others != null) { 1066 for (TransitionFragment fragment : others) { 1067 verifyNoOtherTransitions(fragment); 1068 } 1069 } 1070 } 1071 getBoundsOnScreen(View view)1072 private static Rect getBoundsOnScreen(View view) { 1073 final int[] loc = new int[2]; 1074 view.getLocationOnScreen(loc); 1075 return new Rect(loc[0], loc[1], loc[0] + view.getWidth(), loc[1] + view.getHeight()); 1076 } 1077 1078 public static class ComplexTransitionFragment extends TransitionFragment { 1079 public final TrackingTransition sharedElementEnterTransition1 = new TrackingTransition(); 1080 public final TrackingTransition sharedElementEnterTransition2 = new TrackingTransition(); 1081 public final TrackingTransition sharedElementReturnTransition1 = new TrackingTransition(); 1082 public final TrackingTransition sharedElementReturnTransition2 = new TrackingTransition(); 1083 1084 public final TransitionSet sharedElementEnterTransition = new TransitionSet() 1085 .addTransition(sharedElementEnterTransition1) 1086 .addTransition(sharedElementEnterTransition2); 1087 public final TransitionSet sharedElementReturnTransition = new TransitionSet() 1088 .addTransition(sharedElementReturnTransition1) 1089 .addTransition(sharedElementReturnTransition2); 1090 ComplexTransitionFragment()1091 public ComplexTransitionFragment() { 1092 sharedElementEnterTransition1.addTarget(R.id.blueSquare); 1093 sharedElementEnterTransition2.addTarget(R.id.greenSquare); 1094 sharedElementReturnTransition1.addTarget(R.id.blueSquare); 1095 sharedElementReturnTransition2.addTarget(R.id.greenSquare); 1096 setSharedElementEnterTransition(sharedElementEnterTransition); 1097 setSharedElementReturnTransition(sharedElementReturnTransition); 1098 } 1099 } 1100 1101 public static class InvisibleFragment extends TransitionFragment { 1102 @Override onViewCreated(View view, Bundle savedInstanceState)1103 public void onViewCreated(View view, Bundle savedInstanceState) { 1104 view.setVisibility(View.INVISIBLE); 1105 super.onViewCreated(view, savedInstanceState); 1106 } 1107 } 1108 } 1109