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
setup()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
testAddListener()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
testRemoveListener()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
testAddTargetId()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
testRemoveTargetId()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
testAddTargetClass()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
testRemoveTargetClass()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
testAddTargetView()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
testRemoveTargetView()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
testAddTargetName()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
testRemoveTargetName()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
testIsTransitionRequired()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
testCanRemoveViews()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
testExcludeChildrenView()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
testExcludeChildrenId()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
testExcludeChildrenClass()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
testExcludeTargetView()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
testExcludeTargetId()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
testExcludeTargetClass()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
testExcludeTargetName()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
testDuration()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
testEpicenter()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
testInterpolator()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
testName()491     public void testName() throws Throwable {
492         assertEquals("android.transition.cts.BaseTransitionTest$TestTransition",
493                 mTransition.getName());
494     }
495 
496     @Test
testPathMotion()497     public void testPathMotion() throws Throwable {
498         PathMotion pathMotion = new ArcMotion();
499         mTransition.setPathMotion(pathMotion);
500         assertEquals(pathMotion, mTransition.getPathMotion());
501     }
502 
503     @Test
testPropagation()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
testSetPropagation()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
testForceToEndTransitionsDependedEachOther()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
testStartDelay()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
testTransitionValues()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
testMatchOrder()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
testGetTransitionProperties()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
testGoWithNullParameter()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
isTransitionRequired(TransitionValues startValues, TransitionValues newValues)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
onAppear(ViewGroup sceneRoot, View view, TransitionValues startValues, TransitionValues endValues)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
onDisappear(ViewGroup sceneRoot, View view, TransitionValues startValues, TransitionValues endValues)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 
setupAnimator(Animator animator, View view)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
onAppear(ViewGroup sceneRoot, View view, TransitionValues startValues, TransitionValues endValues)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
onDisappear(ViewGroup sceneRoot, View view, TransitionValues startValues, TransitionValues endValues)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 
TransitionPropertiesTransition(boolean onlyUnimportant)798         public TransitionPropertiesTransition(boolean onlyUnimportant) {
799             mOnlyUnimportant = onlyUnimportant;
800         }
801 
802         @Override
getTransitionProperties()803         public String[] getTransitionProperties() {
804             return PROPERTIES;
805         }
806 
807         @Override
captureStartValues(TransitionValues transitionValues)808         public void captureStartValues(TransitionValues transitionValues) {
809             transitionValues.values.put(SIDE_PROP, 1);
810             transitionValues.values.put(IMPORTANT_PROP, 1);
811         }
812 
813         @Override
captureEndValues(TransitionValues transitionValues)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
createAnimator(ViewGroup sceneRoot, TransitionValues startValues, TransitionValues endValues)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
onTransitionStart(Transition transition)837         public void onTransitionStart(Transition transition) {
838             mElapsedMillisStart = SystemClock.elapsedRealtime();
839         }
840 
841         @Override
onTransitionEnd(Transition transition)842         public void onTransitionEnd(Transition transition) {
843             mDuration = SystemClock.elapsedRealtime() - mElapsedMillisStart;
844         }
845 
getDuration()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