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
17package androidx.transition;
18
19
20import static org.hamcrest.CoreMatchers.equalTo;
21import static org.hamcrest.CoreMatchers.is;
22import static org.hamcrest.CoreMatchers.not;
23import static org.hamcrest.CoreMatchers.nullValue;
24import static org.hamcrest.MatcherAssert.assertThat;
25import static org.hamcrest.Matchers.hasItem;
26import static org.hamcrest.Matchers.sameInstance;
27import static org.junit.Assert.assertNotNull;
28import static org.junit.Assert.fail;
29import static org.mockito.Matchers.any;
30import static org.mockito.Mockito.mock;
31import static org.mockito.Mockito.never;
32import static org.mockito.Mockito.verify;
33
34import android.animation.Animator;
35import android.animation.ObjectAnimator;
36import android.animation.TimeInterpolator;
37import android.graphics.Rect;
38import android.support.test.annotation.UiThreadTest;
39import android.support.test.filters.MediumTest;
40import android.view.LayoutInflater;
41import android.view.View;
42import android.view.ViewGroup;
43import android.view.animation.LinearInterpolator;
44import android.widget.Button;
45import android.widget.FrameLayout;
46import android.widget.ImageView;
47
48import androidx.annotation.NonNull;
49import androidx.annotation.Nullable;
50import androidx.core.view.ViewCompat;
51import androidx.transition.test.R;
52
53import org.junit.Before;
54import org.junit.Test;
55
56import java.util.List;
57
58@MediumTest
59public class TransitionTest extends BaseTest {
60
61    private Scene[] mScenes = new Scene[2];
62    private View[] mViews = new View[3];
63
64    @Before
65    public void prepareScenes() {
66        TransitionActivity activity = rule.getActivity();
67        ViewGroup root = activity.getRoot();
68        mScenes[0] = Scene.getSceneForLayout(root, R.layout.support_scene0, activity);
69        mScenes[1] = Scene.getSceneForLayout(root, R.layout.support_scene1, activity);
70    }
71
72    @Test
73    public void testName() {
74        Transition transition = new EmptyTransition();
75        assertThat(transition.getName(),
76                is(equalTo("androidx.transition.TransitionTest$EmptyTransition")));
77    }
78
79    @Test
80    public void testDuration() {
81        Transition transition = new EmptyTransition();
82        long duration = 12345;
83        assertThat(transition.setDuration(duration), is(sameInstance(transition)));
84        assertThat(transition.getDuration(), is(duration));
85    }
86
87    @Test
88    public void testInterpolator() {
89        Transition transition = new EmptyTransition();
90        TimeInterpolator interpolator = new LinearInterpolator();
91        assertThat(transition.setInterpolator(interpolator), is(sameInstance(transition)));
92        assertThat(transition.getInterpolator(), is(interpolator));
93    }
94
95    @Test
96    public void testStartDelay() {
97        Transition transition = new EmptyTransition();
98        long startDelay = 12345;
99        assertThat(transition.setStartDelay(startDelay), is(sameInstance(transition)));
100        assertThat(transition.getStartDelay(), is(startDelay));
101    }
102
103    @Test
104    public void testTargetIds() {
105        Transition transition = new EmptyTransition();
106        assertThat(transition.addTarget(R.id.view0), is(sameInstance(transition)));
107        assertThat(transition.addTarget(R.id.view1), is(sameInstance(transition)));
108        List<Integer> targetIds = transition.getTargetIds();
109        assertThat(targetIds.size(), is(2));
110        assertThat(targetIds, hasItem(R.id.view0));
111        assertThat(targetIds, hasItem(R.id.view1));
112        assertThat(transition.removeTarget(R.id.view0), is(sameInstance(transition)));
113        targetIds = transition.getTargetIds();
114        assertThat(targetIds.size(), is(1));
115        assertThat(targetIds, not(hasItem(R.id.view0)));
116        assertThat(targetIds, hasItem(R.id.view1));
117    }
118
119    @Test
120    @UiThreadTest
121    public void testTargetView() {
122        // Set up views
123        TransitionActivity activity = rule.getActivity();
124        ViewGroup root = activity.getRoot();
125        View container = LayoutInflater.from(activity)
126                .inflate(R.layout.support_scene0, root, false);
127        root.addView(container);
128        View view0 = container.findViewById(R.id.view0);
129        View view1 = container.findViewById(R.id.view1);
130        // Test transition targets
131        Transition transition = new EmptyTransition();
132        assertThat(transition.addTarget(view0), is(sameInstance(transition)));
133        assertThat(transition.addTarget(view1), is(sameInstance(transition)));
134        List<View> targets = transition.getTargets();
135        assertThat(targets.size(), is(2));
136        assertThat(targets, hasItem(sameInstance(view0)));
137        assertThat(targets, hasItem(sameInstance(view1)));
138        assertThat(transition.removeTarget(view0), is(sameInstance(transition)));
139        targets = transition.getTargets();
140        assertThat(targets.size(), is(1));
141        assertThat(targets, not(hasItem(sameInstance(view0))));
142        assertThat(targets, hasItem(sameInstance(view1)));
143    }
144
145    @Test
146    public void testTargetName() {
147        Transition transition = new EmptyTransition();
148        assertThat(transition.addTarget("a"), is(sameInstance(transition)));
149        assertThat(transition.addTarget("b"), is(sameInstance(transition)));
150        List<String> targetNames = transition.getTargetNames();
151        assertNotNull(targetNames);
152        assertThat(targetNames.size(), is(2));
153        assertThat(targetNames, hasItem("a"));
154        assertThat(targetNames, hasItem("b"));
155        transition.removeTarget("a");
156        assertThat(targetNames.size(), is(1));
157        assertThat(targetNames, not(hasItem("a")));
158        assertThat(targetNames, hasItem("b"));
159    }
160
161    @Test
162    public void testTargetType() {
163        Transition transition = new EmptyTransition();
164        assertThat(transition.addTarget(Button.class), is(sameInstance(transition)));
165        assertThat(transition.addTarget(ImageView.class), is(sameInstance(transition)));
166        List<Class> targetTypes = transition.getTargetTypes();
167        assertNotNull(targetTypes);
168        assertThat(targetTypes.size(), is(2));
169        assertThat(targetTypes, hasItem(Button.class));
170        assertThat(targetTypes, hasItem(ImageView.class));
171        transition.removeTarget(Button.class);
172        assertThat(targetTypes.size(), is(1));
173        assertThat(targetTypes, not(hasItem(Button.class)));
174        assertThat(targetTypes, hasItem(ImageView.class));
175    }
176
177    @Test
178    public void testExcludeTargetId() throws Throwable {
179        showInitialScene();
180        Transition transition = new EmptyTransition();
181        transition.addTarget(R.id.view0);
182        transition.addTarget(R.id.view1);
183        View view0 = rule.getActivity().findViewById(R.id.view0);
184        View view1 = rule.getActivity().findViewById(R.id.view1);
185        assertThat(transition.isValidTarget(view0), is(true));
186        assertThat(transition.isValidTarget(view1), is(true));
187        transition.excludeTarget(R.id.view0, true);
188        assertThat(transition.isValidTarget(view0), is(false));
189        assertThat(transition.isValidTarget(view1), is(true));
190    }
191
192    @Test
193    public void testExcludeTargetView() throws Throwable {
194        showInitialScene();
195        Transition transition = new EmptyTransition();
196        View view0 = rule.getActivity().findViewById(R.id.view0);
197        View view1 = rule.getActivity().findViewById(R.id.view1);
198        transition.addTarget(view0);
199        transition.addTarget(view1);
200        assertThat(transition.isValidTarget(view0), is(true));
201        assertThat(transition.isValidTarget(view1), is(true));
202        transition.excludeTarget(view0, true);
203        assertThat(transition.isValidTarget(view0), is(false));
204        assertThat(transition.isValidTarget(view1), is(true));
205    }
206
207    @Test
208    public void testExcludeTargetName() throws Throwable {
209        showInitialScene();
210        Transition transition = new EmptyTransition();
211        View view0 = rule.getActivity().findViewById(R.id.view0);
212        View view1 = rule.getActivity().findViewById(R.id.view1);
213        ViewCompat.setTransitionName(view0, "zero");
214        ViewCompat.setTransitionName(view1, "one");
215        transition.addTarget("zero");
216        transition.addTarget("one");
217        assertThat(transition.isValidTarget(view0), is(true));
218        assertThat(transition.isValidTarget(view1), is(true));
219        transition.excludeTarget("zero", true);
220        assertThat(transition.isValidTarget(view0), is(false));
221        assertThat(transition.isValidTarget(view1), is(true));
222    }
223
224    @Test
225    public void testExcludeTargetType() throws Throwable {
226        showInitialScene();
227        Transition transition = new EmptyTransition();
228        FrameLayout container = (FrameLayout) rule.getActivity().findViewById(R.id.container);
229        View view0 = rule.getActivity().findViewById(R.id.view0);
230        transition.addTarget(View.class);
231        assertThat(transition.isValidTarget(container), is(true));
232        assertThat(transition.isValidTarget(view0), is(true));
233        transition.excludeTarget(FrameLayout.class, true);
234        assertThat(transition.isValidTarget(container), is(false));
235        assertThat(transition.isValidTarget(view0), is(true));
236    }
237
238    @Test
239    public void testListener() {
240        Transition transition = new EmptyTransition();
241        Transition.TransitionListener listener = new EmptyTransitionListener();
242        assertThat(transition.addListener(listener), is(sameInstance(transition)));
243        assertThat(transition.removeListener(listener), is(sameInstance(transition)));
244    }
245
246    @Test
247    public void testMatchOrder() throws Throwable {
248        showInitialScene();
249        final Transition transition = new ChangeBounds() {
250            @Nullable
251            @Override
252            public Animator createAnimator(@NonNull ViewGroup sceneRoot,
253                    @Nullable TransitionValues startValues, @Nullable TransitionValues endValues) {
254                if (startValues != null && endValues != null) {
255                    fail("Match by View ID should be prevented");
256                }
257                return super.createAnimator(sceneRoot, startValues, endValues);
258            }
259        };
260        transition.setDuration(0);
261        // This prevents matches between start and end scenes because they have different set of
262        // View instances. They will be regarded as independent views even though they share the
263        // same View IDs.
264        transition.setMatchOrder(Transition.MATCH_INSTANCE);
265        SyncRunnable enter1 = new SyncRunnable();
266        mScenes[1].setEnterAction(enter1);
267        goToScene(mScenes[1], transition);
268        if (!enter1.await()) {
269            fail("Timed out while waiting for scene change");
270        }
271    }
272
273    @Test
274    public void testExcludedTransitionAnimator() throws Throwable {
275        showInitialScene();
276        final Animator.AnimatorListener animatorListener = mock(Animator.AnimatorListener.class);
277        final DummyTransition transition = new DummyTransition(animatorListener);
278        final SyncTransitionListener transitionListener = new SyncTransitionListener(
279                SyncTransitionListener.EVENT_END);
280        transition.addListener(transitionListener);
281        transition.addTarget(mViews[0]);
282        transition.excludeTarget(mViews[0], true);
283        rule.runOnUiThread(new Runnable() {
284            @Override
285            public void run() {
286                TransitionManager.beginDelayedTransition(rule.getActivity().getRoot(), transition);
287                mViews[0].setTranslationX(3.f);
288            }
289        });
290        if (!transitionListener.await()) {
291            fail("Timed out waiting for the TransitionListener");
292        }
293        verify(animatorListener, never()).onAnimationStart(any(Animator.class));
294    }
295
296    @Test
297    public void testEpicenter() throws Throwable {
298        final Transition transition = new EmptyTransition();
299        final Transition.EpicenterCallback epicenterCallback = new Transition.EpicenterCallback() {
300            private Rect mRect = new Rect();
301
302            @Override
303            public Rect onGetEpicenter(@NonNull Transition t) {
304                assertThat(t, is(sameInstance(transition)));
305                mRect.set(1, 2, 3, 4);
306                return mRect;
307            }
308        };
309        transition.setEpicenterCallback(epicenterCallback);
310        assertThat(transition.getEpicenterCallback(),
311                is(sameInstance(transition.getEpicenterCallback())));
312        Rect rect = transition.getEpicenter();
313        assertNotNull(rect);
314        assertThat(rect.left, is(1));
315        assertThat(rect.top, is(2));
316        assertThat(rect.right, is(3));
317        assertThat(rect.bottom, is(4));
318    }
319
320    @Test
321    public void testSetPropagation() throws Throwable {
322        final Transition transition = new EmptyTransition();
323        assertThat(transition.getPropagation(), is(nullValue()));
324        final TransitionPropagation propagation = new CircularPropagation();
325        transition.setPropagation(propagation);
326        assertThat(propagation, is(sameInstance(propagation)));
327    }
328
329    @Test
330    public void testIsTransitionRequired() throws Throwable {
331        final EmptyTransition transition = new EmptyTransition();
332        assertThat(transition.isTransitionRequired(null, null), is(false));
333        final TransitionValues start = new TransitionValues();
334        final String propname = "android:transition:dummy";
335        start.values.put(propname, 1);
336        final TransitionValues end = new TransitionValues();
337        end.values.put(propname, 1);
338        assertThat(transition.isTransitionRequired(start, end), is(false));
339        end.values.put(propname, 2);
340        assertThat(transition.isTransitionRequired(start, end), is(true));
341    }
342
343    private void showInitialScene() throws Throwable {
344        SyncRunnable enter0 = new SyncRunnable();
345        mScenes[0].setEnterAction(enter0);
346        AutoTransition transition1 = new AutoTransition();
347        transition1.setDuration(0);
348        goToScene(mScenes[0], transition1);
349        if (!enter0.await()) {
350            fail("Timed out while waiting for scene change");
351        }
352        mViews[0] = rule.getActivity().findViewById(R.id.view0);
353        mViews[1] = rule.getActivity().findViewById(R.id.view1);
354        mViews[2] = rule.getActivity().findViewById(R.id.view2);
355    }
356
357    private void goToScene(final Scene scene, final Transition transition) throws Throwable {
358        rule.runOnUiThread(new Runnable() {
359            @Override
360            public void run() {
361                TransitionManager.go(scene, transition);
362            }
363        });
364    }
365
366    public static class EmptyTransition extends Transition {
367
368        @Override
369        public void captureEndValues(@NonNull TransitionValues transitionValues) {
370        }
371
372        @Override
373        public void captureStartValues(@NonNull TransitionValues transitionValues) {
374        }
375
376        @Override
377        public Animator createAnimator(@NonNull ViewGroup sceneRoot,
378                @Nullable TransitionValues startValues,
379                @Nullable TransitionValues endValues) {
380            return null;
381        }
382
383    }
384
385    public static class EmptyTransitionListener implements Transition.TransitionListener {
386
387        @Override
388        public void onTransitionStart(@NonNull Transition transition) {
389        }
390
391        @Override
392        public void onTransitionEnd(@NonNull Transition transition) {
393        }
394
395        @Override
396        public void onTransitionCancel(@NonNull Transition transition) {
397        }
398
399        @Override
400        public void onTransitionPause(@NonNull Transition transition) {
401        }
402
403        @Override
404        public void onTransitionResume(@NonNull Transition transition) {
405        }
406
407    }
408
409    /**
410     * A dummy transition for monitoring use of its animator by the Transition framework.
411     */
412    private static class DummyTransition extends Transition {
413
414        private final Animator.AnimatorListener mListener;
415
416        DummyTransition(Animator.AnimatorListener listener) {
417            mListener = listener;
418        }
419
420        @Override
421        public void captureStartValues(@NonNull TransitionValues transitionValues) {
422            transitionValues.values.put("state", 1);
423        }
424
425        @Override
426        public void captureEndValues(@NonNull TransitionValues transitionValues) {
427            transitionValues.values.put("state", 2);
428        }
429
430        @Override
431        public Animator createAnimator(@NonNull ViewGroup sceneRoot, TransitionValues startValues,
432                TransitionValues endValues) {
433            if (startValues == null || endValues == null) {
434                return null;
435            }
436            final ObjectAnimator animator = ObjectAnimator
437                    .ofFloat(startValues.view, "translationX", 1.f, 2.f);
438            animator.addListener(mListener);
439            return animator;
440        }
441
442    }
443}
444