Home | History | Annotate | Download | only in cts
      1 /*
      2  * Copyright (C) 2015 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.transition.cts;
     17 
     18 import static com.android.compatibility.common.util.CtsMockitoUtils.within;
     19 
     20 import static org.hamcrest.CoreMatchers.allOf;
     21 import static org.hamcrest.Matchers.greaterThanOrEqualTo;
     22 import static org.hamcrest.Matchers.lessThan;
     23 import static org.junit.Assert.assertEquals;
     24 import static org.junit.Assert.assertFalse;
     25 import static org.junit.Assert.assertNotNull;
     26 import static org.junit.Assert.assertNull;
     27 import static org.junit.Assert.assertSame;
     28 import static org.junit.Assert.assertThat;
     29 import static org.junit.Assert.assertTrue;
     30 import static org.junit.Assert.fail;
     31 import static org.mockito.Matchers.any;
     32 import static org.mockito.Matchers.eq;
     33 import static org.mockito.Mockito.doAnswer;
     34 import static org.mockito.Mockito.mock;
     35 import static org.mockito.Mockito.never;
     36 import static org.mockito.Mockito.times;
     37 import static org.mockito.Mockito.verify;
     38 
     39 import android.animation.Animator;
     40 import android.graphics.Rect;
     41 import android.os.SystemClock;
     42 import android.transition.ArcMotion;
     43 import android.transition.AutoTransition;
     44 import android.transition.ChangeBounds;
     45 import android.transition.CircularPropagation;
     46 import android.transition.PathMotion;
     47 import android.transition.Scene;
     48 import android.transition.Transition;
     49 import android.transition.Transition.EpicenterCallback;
     50 import android.transition.Transition.TransitionListener;
     51 import android.transition.TransitionListenerAdapter;
     52 import android.transition.TransitionManager;
     53 import android.transition.TransitionPropagation;
     54 import android.transition.TransitionValues;
     55 import android.view.View;
     56 import android.view.ViewGroup;
     57 import android.view.ViewTreeObserver;
     58 import android.view.animation.AccelerateInterpolator;
     59 import android.widget.ImageView;
     60 import android.widget.RelativeLayout;
     61 import android.widget.TextView;
     62 
     63 import androidx.test.filters.MediumTest;
     64 import androidx.test.runner.AndroidJUnit4;
     65 
     66 import org.junit.Test;
     67 import org.junit.runner.RunWith;
     68 import org.mockito.invocation.InvocationOnMock;
     69 
     70 import java.util.HashMap;
     71 import java.util.concurrent.CountDownLatch;
     72 import java.util.concurrent.TimeUnit;
     73 
     74 @MediumTest
     75 @RunWith(AndroidJUnit4.class)
     76 public class TransitionTest extends BaseTransitionTest {
     77     @Override
     78     public void setup() {
     79         super.setup();
     80         // We want to be able to catch the transition in the middle of running, so
     81         // it should be long enough that devices can catch it without trouble.
     82         mTransition.setDuration(1000);
     83     }
     84 
     85     @Test
     86     public void testAddListener() throws Throwable {
     87         startTransition(R.layout.scene1);
     88         waitForStart();
     89 
     90         final Transition.TransitionListener listener2 = mock(Transition.TransitionListener.class);
     91 
     92         mActivityRule.runOnUiThread(() -> {
     93             AutoTransition autoTransition = new AutoTransition();
     94             autoTransition.setDuration(100);
     95             autoTransition.addListener(listener2);
     96             Scene scene = Scene.getSceneForLayout(mSceneRoot, R.layout.scene2, mActivity);
     97             TransitionManager.go(scene, autoTransition);
     98         });
     99 
    100         waitForStart(listener2);
    101 
    102         verify(mListener, times(1)).onTransitionPause(any());
    103         verify(mListener, times(1)).onTransitionResume(any());
    104         verify(mListener, never()).onTransitionCancel(any());
    105         verify(mListener, never()).onTransitionEnd(any());
    106         verify(mListener, times(1)).onTransitionStart(any());
    107 
    108         verify(listener2, never()).onTransitionPause(any());
    109         verify(listener2, never()).onTransitionResume(any());
    110         verify(listener2, never()).onTransitionCancel(any());
    111         verify(listener2, never()).onTransitionEnd(any());
    112         verify(listener2, times(1)).onTransitionStart(any());
    113         endTransition();
    114     }
    115 
    116     @Test
    117     public void testRemoveListener() throws Throwable {
    118         TransitionListener listener = mock(TransitionListener.class);
    119         mTransition.addListener(listener);
    120         startTransition(R.layout.scene1);
    121         waitForStart();
    122 
    123         mActivityRule.runOnUiThread(() -> mTransition.removeListener(listener));
    124         waitForEnd(2000);
    125         mActivityRule.runOnUiThread(() -> verify(listener, never()).onTransitionEnd(any()));
    126     }
    127 
    128     @Test
    129     public void testAddTargetId() throws Throwable {
    130         enterScene(R.layout.scene4);
    131         assertNotNull(mTransition.getTargetIds());
    132         assertTrue(mTransition.getTargetIds().isEmpty());
    133         mTransition.addTarget(R.id.holder);
    134         mTransition.addTarget(R.id.hello);
    135         assertEquals(2, mTransition.getTargetIds().size());
    136         startTransition(R.layout.scene1);
    137         assertEquals(1, mTargets.size());
    138         assertEquals(R.id.hello, mTargets.get(0).getId());
    139         endTransition();
    140     }
    141 
    142     @Test
    143     public void testRemoveTargetId() throws Throwable {
    144         enterScene(R.layout.scene4);
    145         mTransition.addTarget(R.id.holder);
    146         mTransition.addTarget(R.id.hello);
    147         mTransition.addTarget(R.id.redSquare);
    148         assertEquals(3, mTransition.getTargetIds().size());
    149         mTransition.removeTarget(0); // nothing should happen
    150         mTransition.removeTarget(R.id.redSquare);
    151         assertEquals(2, mTransition.getTargetIds().size());
    152 
    153         startTransition(R.layout.scene1);
    154         assertEquals(1, mTargets.size());
    155         assertEquals(R.id.hello, mTargets.get(0).getId());
    156         endTransition();
    157     }
    158 
    159     @Test
    160     public void testAddTargetClass() throws Throwable {
    161         enterScene(R.layout.scene4);
    162         assertNull(mTransition.getTargetTypes());
    163         mTransition.addTarget(RelativeLayout.class);
    164         mTransition.addTarget(TextView.class);
    165         assertEquals(2, mTransition.getTargetTypes().size());
    166         startTransition(R.layout.scene1);
    167         assertEquals(1, mTargets.size());
    168         assertTrue(mTargets.get(0) instanceof TextView);
    169         endTransition();
    170     }
    171 
    172     @Test
    173     public void testRemoveTargetClass() throws Throwable {
    174         enterScene(R.layout.scene4);
    175         mTransition.addTarget(TextView.class);
    176         mTransition.addTarget(View.class);
    177         mTransition.addTarget(RelativeLayout.class);
    178         assertEquals(3, mTransition.getTargetTypes().size());
    179         mTransition.removeTarget(ImageView.class); // should do nothing
    180         mTransition.removeTarget(View.class);
    181         assertEquals(2, mTransition.getTargetTypes().size());
    182         startTransition(R.layout.scene1);
    183         assertEquals(1, mTargets.size());
    184         assertTrue(mTargets.get(0) instanceof TextView);
    185         endTransition();
    186     }
    187 
    188     @Test
    189     public void testAddTargetView() throws Throwable {
    190         enterScene(R.layout.scene1);
    191 
    192         final View[] target = new View[1];
    193         mActivityRule.runOnUiThread(() -> target[0] = mActivity.findViewById(R.id.hello));
    194         mTransition.addTarget(target[0]);
    195         assertEquals(1, mTransition.getTargets().size());
    196         mActivityRule.runOnUiThread(() -> {
    197             TransitionManager.beginDelayedTransition(mSceneRoot, mTransition);
    198             target[0].setVisibility(View.GONE);
    199         });
    200         waitForStart();
    201         assertEquals(1, mTargets.size());
    202         assertEquals(target[0], mTargets.get(0));
    203         endTransition();
    204     }
    205 
    206     @Test
    207     public void testRemoveTargetView() throws Throwable {
    208         enterScene(R.layout.scene1);
    209 
    210         final View[] target = new View[3];
    211         mActivityRule.runOnUiThread(() -> {
    212             target[0] = mActivity.findViewById(R.id.hello);
    213             target[1] = mActivity.findViewById(R.id.greenSquare);
    214             target[2] = mActivity.findViewById(R.id.redSquare);
    215         });
    216 
    217         mTransition.addTarget(target[0]);
    218         mTransition.addTarget(target[1]);
    219         assertEquals(2, mTransition.getTargets().size());
    220         mTransition.removeTarget(target[2]); // should do nothing
    221         mTransition.removeTarget(target[1]);
    222         assertEquals(1, mTransition.getTargets().size());
    223         mActivityRule.runOnUiThread(() -> {
    224             TransitionManager.beginDelayedTransition(mSceneRoot, mTransition);
    225             target[0].setVisibility(View.GONE);
    226         });
    227         waitForStart();
    228         assertEquals(1, mTargets.size());
    229         assertEquals(target[0], mTargets.get(0));
    230         endTransition();
    231     }
    232 
    233     @Test
    234     public void testAddTargetName() throws Throwable {
    235         enterScene(R.layout.scene4);
    236         assertNull(mTransition.getTargetNames());
    237         mTransition.addTarget("red");
    238         mTransition.addTarget("holder");
    239         assertEquals(2, mTransition.getTargetNames().size());
    240         assertEquals(0, mTargets.size());
    241         startTransition(R.layout.scene2);
    242         assertEquals(1, mTargets.size());
    243         assertEquals(R.id.redSquare, mTargets.get(0).getId());
    244         endTransition();
    245     }
    246 
    247     @Test
    248     public void testRemoveTargetName() throws Throwable {
    249         enterScene(R.layout.scene4);
    250         mTransition.addTarget("holder");
    251         mTransition.addTarget("red");
    252         mTransition.addTarget("green");
    253         assertEquals(3, mTransition.getTargetNames().size());
    254         mTransition.removeTarget("purple"); // should do nothing
    255         // try to force a different String instance
    256         String greenName = new StringBuilder("gre").append("en").toString();
    257         mTransition.removeTarget(greenName);
    258         assertEquals(2, mTransition.getTargetNames().size());
    259         startTransition(R.layout.scene1);
    260         assertEquals(1, mTargets.size());
    261         assertEquals(R.id.redSquare, mTargets.get(0).getId());
    262         endTransition();
    263     }
    264 
    265     @Test
    266     public void testIsTransitionRequired() throws Throwable {
    267         enterScene(R.layout.scene1);
    268         mTransition = new NotRequiredTransition();
    269         assertFalse(mTransition.isTransitionRequired(null, null));
    270         resetListener();
    271         mActivityRule.runOnUiThread(() -> {
    272             TransitionManager.beginDelayedTransition(mSceneRoot, mTransition);
    273             mActivity.findViewById(R.id.hello).setVisibility(View.GONE);
    274         });
    275         waitForStart();
    276         assertEquals(0, mTargets.size());
    277         endTransition();
    278     }
    279 
    280     @Test
    281     public void testCanRemoveViews() throws Throwable {
    282         enterScene(R.layout.scene1);
    283         assertFalse(mTransition.canRemoveViews());
    284         mTransition.addListener(new TransitionListener() {
    285             @Override
    286             public void onTransitionStart(Transition transition) {
    287                 assertTrue(transition.canRemoveViews());
    288             }
    289 
    290             @Override
    291             public void onTransitionEnd(Transition transition) {
    292             }
    293 
    294             @Override
    295             public void onTransitionCancel(Transition transition) {
    296             }
    297 
    298             @Override
    299             public void onTransitionPause(Transition transition) {
    300             }
    301 
    302             @Override
    303             public void onTransitionResume(Transition transition) {
    304             }
    305         });
    306         startTransition(R.layout.scene2);
    307     }
    308 
    309     @Test
    310     public void testExcludeChildrenView() throws Throwable {
    311         View layout1 = loadLayout(R.layout.scene1);
    312         Scene scene1 = loadScene(layout1);
    313         enterScene(scene1);
    314         View holder1 = layout1.findViewById(R.id.holder);
    315         mTransition.excludeChildren(holder1, true);
    316         View layout2 = loadLayout(R.layout.scene2);
    317         Scene scene2 = loadScene(layout2);
    318         View holder2 = layout2.findViewById(R.id.holder);
    319         mTransition.excludeChildren(holder2, true);
    320         startTransition(scene2);
    321         mActivityRule.runOnUiThread(() -> {
    322             // Should already be ended, since no children are transitioning
    323             verify(mListener, times(1)).onTransitionEnd(any());
    324         });
    325 
    326         mTransition.excludeChildren(holder1, false); // remove it
    327         mTransition.excludeChildren(holder2, false); // remove it
    328         resetListener();
    329         startTransition(scene1);
    330         verify(mListener, never()).onTransitionEnd(any()); // it is running as expected
    331         endTransition();
    332     }
    333 
    334     @Test
    335     public void testExcludeChildrenId() throws Throwable {
    336         enterScene(R.layout.scene1);
    337         mTransition.excludeChildren(R.id.holder, true);
    338         startTransition(R.layout.scene2);
    339         mActivityRule.runOnUiThread(() -> {
    340             // Should already be ended, since no children are transitioning
    341             verify(mListener, times(1)).onTransitionEnd(any());
    342         });
    343 
    344         resetListener();
    345         mTransition.excludeChildren(R.id.holder, false); // remove it
    346         startTransition(R.layout.scene1);
    347         verify(mListener, never()).onTransitionEnd(any()); // it is running as expected
    348         endTransition();
    349     }
    350 
    351     @Test
    352     public void testExcludeChildrenClass() throws Throwable {
    353         enterScene(R.layout.scene1);
    354         mTransition.excludeChildren(RelativeLayout.class, true);
    355         startTransition(R.layout.scene2);
    356         mActivityRule.runOnUiThread(() -> {
    357             // Should already be ended, since no children are transitioning
    358             verify(mListener, times(1)).onTransitionEnd(any());
    359         });
    360 
    361         resetListener();
    362         mTransition.excludeChildren(RelativeLayout.class, false); // remove it
    363         startTransition(R.layout.scene1);
    364         verify(mListener, never()).onTransitionEnd(any()); // it is running as expected
    365         endTransition();
    366     }
    367 
    368     @Test
    369     public void testExcludeTargetView() throws Throwable {
    370         View layout1 = loadLayout(R.layout.scene1);
    371         Scene scene1 = loadScene(layout1);
    372         enterScene(R.layout.scene1);
    373         View redSquare1 = layout1.findViewById(R.id.redSquare);
    374         mTransition.excludeTarget(redSquare1, true);
    375         startTransition(R.layout.scene7);
    376         waitForEnd(2000);
    377 
    378         mTransition.excludeTarget(redSquare1, false); // remove it
    379         resetListener();
    380         startTransition(scene1);
    381         verify(mListener, never()).onTransitionEnd(any()); // it is running as expected
    382         endTransition();
    383     }
    384 
    385     @Test
    386     public void testExcludeTargetId() throws Throwable {
    387         enterScene(R.layout.scene1);
    388         mTransition.excludeTarget(R.id.redSquare, true);
    389         startTransition(R.layout.scene7);
    390         mActivityRule.runOnUiThread(() -> {
    391             // Should already be ended, since no children are transitioning
    392             verify(mListener, times(1)).onTransitionEnd(any());
    393         });
    394 
    395         resetListener();
    396         mTransition.excludeTarget(R.id.redSquare, false); // remove it
    397         startTransition(R.layout.scene1);
    398         verify(mListener, never()).onTransitionEnd(any()); // it is running as expected
    399         endTransition();
    400     }
    401 
    402     @Test
    403     public void testExcludeTargetClass() throws Throwable {
    404         enterScene(R.layout.scene1);
    405         mTransition.excludeTarget(TextView.class, true);
    406         startTransition(R.layout.scene3);
    407         mActivityRule.runOnUiThread(() -> {
    408             // Should already be ended, since no children are transitioning
    409             verify(mListener, times(1)).onTransitionEnd(any());
    410         });
    411 
    412         resetListener();
    413         mTransition.excludeTarget(TextView.class, false); // remove it
    414         startTransition(R.layout.scene1);
    415         verify(mListener, never()).onTransitionEnd(any()); // it is running as expected
    416         endTransition();
    417     }
    418 
    419     @Test
    420     public void testExcludeTargetName() throws Throwable {
    421         enterScene(R.layout.scene1);
    422         mTransition.excludeTarget("hello", true);
    423         startTransition(R.layout.scene3);
    424         mActivityRule.runOnUiThread(() -> {
    425             // Should already be ended, since no children are transitioning
    426             verify(mListener, times(1)).onTransitionEnd(any());
    427         });
    428 
    429         resetListener();
    430         mTransition.excludeTarget("hello", false); // remove it
    431         startTransition(R.layout.scene1);
    432         verify(mListener, never()).onTransitionEnd(any()); // it is running as expected
    433         endTransition();
    434     }
    435 
    436     @Test
    437     public void testDuration() throws Throwable {
    438         Transition transition = new AutoTransition();
    439         assertEquals(-1, transition.getDuration());
    440         enterScene(R.layout.scene1);
    441         mTransition.setDuration(1000);
    442         assertEquals(1000, mTransition.getDuration());
    443         DurationListener durationListener = new DurationListener();
    444         mTransition.addListener(durationListener);
    445         startTransition(R.layout.scene3);
    446         waitForEnd(5000);
    447         // We can't be certain that the onTransitionStart() and onTransitionEnd()
    448         // are going to be called exactly 1000ms apart. There could be more of a
    449         // delay at the beginning than the end. So, we give it some room at the
    450         // minimum. It can also take a lot longer on the larger side because of
    451         // slow devices.
    452         assertThat(durationListener.getDuration(),
    453                 allOf(greaterThanOrEqualTo(500L), lessThan(2000L)));
    454     }
    455 
    456     @Test
    457     public void testEpicenter() throws Throwable {
    458         assertNull(mTransition.getEpicenter());
    459         EpicenterCallback callback = new EpicenterCallback() {
    460             @Override
    461             public Rect onGetEpicenter(Transition transition) {
    462                 return new Rect(0, 0, 1, 1);
    463             }
    464         };
    465         mTransition.setEpicenterCallback(callback);
    466         assertSame(callback, mTransition.getEpicenterCallback());
    467         assertEquals(new Rect(0, 0, 1, 1), mTransition.getEpicenter());
    468     }
    469 
    470     @Test
    471     public void testInterpolator() throws Throwable {
    472         enterScene(R.layout.scene1);
    473         View redSquare = mActivity.findViewById(R.id.redSquare);
    474         CaptureAnimatorTransition transition = new CaptureAnimatorTransition();
    475         assertNull(transition.getInterpolator());
    476         AccelerateInterpolator interpolator = new AccelerateInterpolator();
    477         transition.setInterpolator(interpolator);
    478         assertSame(interpolator, transition.getInterpolator());
    479         mTransition = transition;
    480         resetListener();
    481         startTransition(R.layout.scene4);
    482         assertFalse(transition.animators.isEmpty());
    483         Animator animator = transition.animators.get(redSquare);
    484         Animator.AnimatorListener listener = transition.listeners.get(redSquare);
    485         verify(listener, within(1000)).onAnimationStart(any(), eq(false));
    486         assertSame(interpolator, animator.getInterpolator());
    487         endTransition();
    488     }
    489 
    490     @Test
    491     public void testName() throws Throwable {
    492         assertEquals("android.transition.cts.BaseTransitionTest$TestTransition",
    493                 mTransition.getName());
    494     }
    495 
    496     @Test
    497     public void testPathMotion() throws Throwable {
    498         PathMotion pathMotion = new ArcMotion();
    499         mTransition.setPathMotion(pathMotion);
    500         assertEquals(pathMotion, mTransition.getPathMotion());
    501     }
    502 
    503     @Test
    504     public void testPropagation() throws Throwable {
    505         enterScene(R.layout.scene1);
    506         CaptureAnimatorTransition transition = new CaptureAnimatorTransition();
    507         mTransition = transition;
    508         TransitionPropagation yPropagation = new TransitionPropagation() {
    509             private static final String TOP = "top value";
    510             private final String[] PROPERTIES = {TOP};
    511 
    512             @Override
    513             public long getStartDelay(ViewGroup viewGroup, Transition transition,
    514                     TransitionValues startValues, TransitionValues endValues) {
    515                 int startTop = startValues == null ? 0 : (Integer) startValues.values.get(TOP);
    516                 int endTop = endValues == null ? 0 : (Integer) endValues.values.get(TOP);
    517                 return (startTop == 0) ? endTop : startTop;
    518             }
    519 
    520             @Override
    521             public void captureValues(TransitionValues transitionValues) {
    522                 if (transitionValues.view != null) {
    523                     transitionValues.values.put(TOP, transitionValues.view.getTop());
    524                 }
    525             }
    526 
    527             @Override
    528             public String[] getPropagationProperties() {
    529                 return PROPERTIES;
    530             }
    531         };
    532         mTransition.setPropagation(yPropagation);
    533         resetListener();
    534 
    535         View redSquare = mActivity.findViewById(R.id.redSquare);
    536         View greenSquare = mActivity.findViewById(R.id.greenSquare);
    537         int diffTop = greenSquare.getTop() - redSquare.getTop();
    538         startTransition(R.layout.scene4);
    539         Animator redSquareAnimator = transition.animators.get(redSquare);
    540         Animator greenSquareAnimator = transition.animators.get(greenSquare);
    541         Animator.AnimatorListener listener = transition.listeners.get(redSquare);
    542         verify(listener, within(1000)).onAnimationStart(any(), eq(false));
    543         assertEquals(0, redSquareAnimator.getStartDelay());
    544         assertEquals(diffTop, greenSquareAnimator.getStartDelay());
    545         endTransition();
    546     }
    547 
    548     @Test
    549     public void testSetPropagation() throws Throwable {
    550         Transition transition = new ChangeBounds();
    551         assertNull(transition.getPropagation());
    552         TransitionPropagation propagation = new CircularPropagation();
    553         transition.setPropagation(propagation);
    554         assertSame(propagation, transition.getPropagation());
    555     }
    556 
    557     @Test
    558     public void testForceToEndTransitionsDependedEachOther() throws Throwable {
    559         final Transition.TransitionListener listener1 = mock(Transition.TransitionListener.class);
    560         final Transition.TransitionListener listener2 = mock(Transition.TransitionListener.class);
    561 
    562         Scene scene1 = loadScene(R.layout.scene1);
    563         Scene scene2 = loadScene(R.layout.scene2);
    564 
    565         final ViewGroup scene1Root = scene1.getSceneRoot();
    566         final ViewTreeObserver.OnPreDrawListener transition1OnPreDrawListener =
    567                 new ViewTreeObserver.OnPreDrawListener() {
    568                     public boolean onPreDraw() {
    569                         // Start the 2nd transition after the 1st transiton starts and does predraw.
    570                         Transition transition2 = new TestTransition();
    571                         transition2.setDuration(1000);
    572                         transition2.addListener(listener2);
    573                         TransitionManager.go(scene2, transition2);
    574                         scene1Root.getViewTreeObserver().removeOnPreDrawListener(this);
    575                         return true;
    576                     }
    577                 };
    578 
    579         // Start the 1st transition.
    580         mActivityRule.runOnUiThread(
    581                 () -> {
    582                     Transition transition1 = new TestTransition();
    583                     transition1.setDuration(1000);
    584 
    585                     transition1.addListener(listener1);
    586                     TransitionManager.go(scene1, transition1);
    587                     scene1Root
    588                             .getViewTreeObserver()
    589                             .addOnPreDrawListener(transition1OnPreDrawListener);
    590                 });
    591 
    592         // When the 1st transition ends, end the other (2nd) transition if it is still alive.
    593         doAnswer(
    594                 (InvocationOnMock invocation) -> {
    595                     TransitionManager.endTransitions(
    596                             mActivity.findViewById(R.id.container));
    597                     return null;
    598                 })
    599                 .when(listener1)
    600                 .onTransitionEnd(any());
    601 
    602         // When the 2st transition ends, end the other (1nd) transition if it is still alive.
    603         doAnswer(
    604                 (InvocationOnMock invocation) -> {
    605                     TransitionManager.endTransitions(
    606                             mActivity.findViewById(R.id.container));
    607                     return null;
    608                 })
    609                 .when(listener2)
    610                 .onTransitionEnd(any());
    611 
    612         verify(listener1, within(4000)).onTransitionStart(any());
    613         verify(listener2, within(4000)).onTransitionStart(any());
    614 
    615         // End both transitions forcibly.
    616         mActivityRule.runOnUiThread(
    617                 () -> {
    618                     TransitionManager.endTransitions(mActivity.findViewById(R.id.container));
    619                 });
    620 
    621         verify(listener1, within(4000)).onTransitionEnd(any());
    622         verify(listener2, within(4000)).onTransitionEnd(any());
    623     }
    624 
    625     @Test
    626     public void testStartDelay() throws Throwable {
    627         CaptureAnimatorTransition transition = new CaptureAnimatorTransition();
    628         mTransition = transition;
    629         resetListener();
    630         enterScene(R.layout.scene1);
    631         View redSquare = mActivity.findViewById(R.id.redSquare);
    632 
    633         assertEquals(-1, mTransition.getStartDelay());
    634         mTransition.setStartDelay(200);
    635         assertEquals(200, mTransition.getStartDelay());
    636 
    637         startTransition(R.layout.scene4);
    638 
    639         Animator animator = transition.animators.get(redSquare);
    640         assertFalse(animator.isRunning());
    641         Animator.AnimatorListener listener = transition.listeners.get(redSquare);
    642         verify(listener, within(1000)).onAnimationStart(any(), eq(false));
    643         endTransition();
    644     }
    645 
    646     @Test
    647     public void testTransitionValues() throws Throwable {
    648         enterScene(R.layout.scene1);
    649         CheckTransitionValuesTransition transition = new CheckTransitionValuesTransition();
    650         mTransition = transition;
    651         mTransition.setDuration(10);
    652         resetListener();
    653         startTransition(R.layout.scene2);
    654         assertTrue(transition.onDisappearCalled.await(2000, TimeUnit.MILLISECONDS));
    655         assertTrue(transition.onAppearCalled.await(2000, TimeUnit.MILLISECONDS));
    656         // The transition has all the asserts in it, so we can just end it now.
    657         endTransition();
    658     }
    659 
    660     @Test
    661     public void testMatchOrder() throws Throwable {
    662         mTransition = new ChangeBounds();
    663         resetListener();
    664         enterScene(R.layout.scene1);
    665         startTransition(R.layout.scene8);
    666 
    667         // scene 8 swaps the ids, but not the names. No transition should happen.
    668         waitForEnd(2000);
    669 
    670         // now change the match order to prefer the id
    671         mTransition.setMatchOrder(new int[] {Transition.MATCH_ID, Transition.MATCH_NAME});
    672 
    673         resetListener();
    674         startTransition(R.layout.scene1);
    675         verify(mListener, never()).onTransitionEnd(any()); // it is running as expected
    676         waitForEnd(2000);
    677     }
    678 
    679     @Test
    680     public void testGetTransitionProperties() throws Throwable {
    681         enterScene(R.layout.scene1);
    682         // Make the transition make changes to properties in getTransitionProperties.
    683         TransitionPropertiesTransition transition = new TransitionPropertiesTransition(false);
    684         mTransition = transition;
    685         resetListener();
    686         startTransition(R.layout.scene2);
    687         assertTrue(transition.latch.await(2000, TimeUnit.MILLISECONDS));
    688         endTransition();
    689 
    690         // Now make the transition only make changes to unimportant properties.
    691         transition = new TransitionPropertiesTransition(true);
    692         mTransition = transition;
    693         resetListener();
    694         startTransition(R.layout.scene1);
    695         verify(mListener, within(2000)).onTransitionEnd(any());
    696         // createAnimator shouldn't have been called.
    697         assertEquals(1, transition.latch.getCount());
    698 
    699         assertNotNull(transition.getTransitionProperties());
    700         assertEquals(1, transition.getTransitionProperties().length);
    701     }
    702 
    703     @Test
    704     public void testGoWithNullParameter() throws Throwable {
    705         final View layout1 = loadLayout(R.layout.scene1);
    706         final Scene scene1 = loadScene(layout1);
    707 
    708         final View layout3 = loadLayout(R.layout.scene3);
    709         final Scene scene3 = loadScene(layout3);
    710 
    711         enterScene(scene1);
    712 
    713         mActivityRule.runOnUiThread(() -> {
    714             // scene1
    715             assertSame(layout1, mActivity.findViewById(R.id.holder));
    716             assertNotNull(mActivity.findViewById(R.id.hello));
    717 
    718             TransitionManager.go(scene3, null);
    719             // now at scene3
    720             assertSame(layout3, mActivity.findViewById(R.id.holder));
    721             assertNull(mActivity.findViewById(R.id.hello));
    722 
    723             TransitionManager.go(scene1, null);
    724 
    725             // now at scene1
    726             assertSame(layout1, mActivity.findViewById(R.id.holder));
    727             assertNotNull(mActivity.findViewById(R.id.hello));
    728         });
    729     }
    730 
    731     private class NotRequiredTransition extends TestTransition {
    732         @Override
    733         public boolean isTransitionRequired(TransitionValues startValues,
    734                 TransitionValues newValues) {
    735             return false;
    736         }
    737     }
    738 
    739     private class CaptureAnimatorTransition extends TestTransition {
    740         public HashMap<View, Animator> animators = new HashMap<>();
    741         public HashMap<View, Animator.AnimatorListener> listeners = new HashMap<>();
    742 
    743         @Override
    744         public Animator onAppear(ViewGroup sceneRoot, View view, TransitionValues startValues,
    745                 TransitionValues endValues) {
    746             return setupAnimator(super.onAppear(sceneRoot, view, startValues, endValues),
    747                     endValues.view);
    748         }
    749 
    750         @Override
    751         public Animator onDisappear(ViewGroup sceneRoot, View view, TransitionValues startValues,
    752                 TransitionValues endValues) {
    753             return setupAnimator(super.onDisappear(sceneRoot, view, startValues, endValues),
    754                     startValues.view);
    755         }
    756 
    757         private Animator setupAnimator(Animator animator, View view) {
    758             animators.put(view, animator);
    759             Animator.AnimatorListener listener = mock(Animator.AnimatorListener.class);
    760             animator.addListener(listener);
    761             listeners.put(view, listener);
    762             return animator;
    763         }
    764     }
    765 
    766     private class CheckTransitionValuesTransition extends TestTransition {
    767         public CountDownLatch onAppearCalled = new CountDownLatch(1);
    768         public CountDownLatch onDisappearCalled = new CountDownLatch(1);
    769         @Override
    770         public Animator onAppear(ViewGroup sceneRoot, View view, TransitionValues startValues,
    771                 TransitionValues endValues) {
    772             onAppearCalled.countDown();
    773             assertNull(getTransitionValues(endValues.view, true));
    774             assertEquals(endValues, getTransitionValues(endValues.view, false));
    775             return super.onAppear(sceneRoot, view, startValues, endValues);
    776         }
    777 
    778         @Override
    779         public Animator onDisappear(ViewGroup sceneRoot, View view, TransitionValues startValues,
    780                 TransitionValues endValues) {
    781             onDisappearCalled.countDown();
    782             assertNull(getTransitionValues(startValues.view, false));
    783             assertEquals(startValues, getTransitionValues(startValues.view, true));
    784             return super.onDisappear(sceneRoot, view, startValues, endValues);
    785         }
    786     }
    787 
    788     private static class TransitionPropertiesTransition extends Transition {
    789         private static final String SIDE_PROP = "prop1";
    790         private static final String IMPORTANT_PROP = "prop2";
    791         private static final String[] PROPERTIES = {
    792                 IMPORTANT_PROP
    793         };
    794 
    795         private boolean mOnlyUnimportant;
    796         public CountDownLatch latch = new CountDownLatch(1);
    797 
    798         public TransitionPropertiesTransition(boolean onlyUnimportant) {
    799             mOnlyUnimportant = onlyUnimportant;
    800         }
    801 
    802         @Override
    803         public String[] getTransitionProperties() {
    804             return PROPERTIES;
    805         }
    806 
    807         @Override
    808         public void captureStartValues(TransitionValues transitionValues) {
    809             transitionValues.values.put(SIDE_PROP, 1);
    810             transitionValues.values.put(IMPORTANT_PROP, 1);
    811         }
    812 
    813         @Override
    814         public void captureEndValues(TransitionValues transitionValues) {
    815             transitionValues.values.put(SIDE_PROP, 2);
    816             int val = mOnlyUnimportant ? 1 : 2;
    817             transitionValues.values.put(IMPORTANT_PROP, val);
    818         }
    819 
    820         @Override
    821         public Animator createAnimator(ViewGroup sceneRoot, TransitionValues startValues,
    822                 TransitionValues endValues) {
    823             if (startValues != null && endValues != null) {
    824                 latch.countDown();
    825             }
    826 
    827             return null;
    828         }
    829     }
    830 
    831     private static class DurationListener extends TransitionListenerAdapter {
    832 
    833         private long mElapsedMillisStart = -1;
    834         private long mDuration = -1;
    835 
    836         @Override
    837         public void onTransitionStart(Transition transition) {
    838             mElapsedMillisStart = SystemClock.elapsedRealtime();
    839         }
    840 
    841         @Override
    842         public void onTransitionEnd(Transition transition) {
    843             mDuration = SystemClock.elapsedRealtime() - mElapsedMillisStart;
    844         }
    845 
    846         public long getDuration() {
    847             if (mDuration < 0) {
    848                 fail("Requested duration measurement of an incomplete transition.");
    849             }
    850             return mDuration;
    851         }
    852     }
    853 }
    854