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