Home | History | Annotate | Download | only in app
      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
     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 
     74     public FragmentTransitionTest(final boolean reordering) {
     75         mReorderingAllowed = reordering;
     76     }
     77 
     78     @Before
     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
     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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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 
    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 
    811     private View findViewById(Fragment fragment, int id) {
    812         return fragment.getView().findViewById(id);
    813     }
    814 
    815     private View findGreen() {
    816         return mActivityRule.getActivity().findViewById(R.id.greenSquare);
    817     }
    818 
    819     private View findBlue() {
    820         return mActivityRule.getActivity().findViewById(R.id.blueSquare);
    821     }
    822 
    823     private View findRed() {
    824         return mActivityRule.getActivity().findViewById(R.id.redSquare);
    825     }
    826 
    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 
    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 
    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 
    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 
   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 
   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 
   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
   1103         public void onViewCreated(View view, Bundle savedInstanceState) {
   1104             view.setVisibility(View.INVISIBLE);
   1105             super.onViewCreated(view, savedInstanceState);
   1106         }
   1107     }
   1108 }
   1109