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