1/*
2 * Copyright (C) 2017 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
19import static org.junit.Assert.assertNotNull;
20import static org.junit.Assert.assertNull;
21import static org.mockito.ArgumentMatchers.any;
22import static org.mockito.Mockito.mock;
23import static org.mockito.Mockito.timeout;
24import static org.mockito.Mockito.verify;
25
26import android.app.Instrumentation;
27import android.os.Bundle;
28import android.support.test.InstrumentationRegistry;
29import android.support.test.filters.MediumTest;
30import android.view.LayoutInflater;
31import android.view.View;
32import android.view.ViewGroup;
33
34import androidx.annotation.LayoutRes;
35import androidx.annotation.Nullable;
36import androidx.collection.SparseArrayCompat;
37import androidx.core.util.Pair;
38import androidx.core.view.ViewCompat;
39import androidx.fragment.app.Fragment;
40import androidx.fragment.app.FragmentManager;
41import androidx.fragment.app.FragmentTransaction;
42import androidx.transition.test.R;
43
44import org.junit.Test;
45import org.junit.runner.RunWith;
46import org.junit.runners.Parameterized;
47
48
49@MediumTest
50@RunWith(Parameterized.class)
51public class FragmentTransitionTest extends BaseTest {
52
53    @Parameterized.Parameters
54    public static Object[] data() {
55        return new Boolean[]{
56                false, true
57        };
58    }
59
60    private final boolean mReorderingAllowed;
61
62    public FragmentTransitionTest(boolean reorderingAllowed) {
63        mReorderingAllowed = reorderingAllowed;
64    }
65
66    @Test
67    public void preconditions() {
68        final TransitionFragment fragment1 = TransitionFragment.newInstance(R.layout.scene2);
69        final TransitionFragment fragment2 = TransitionFragment.newInstance(R.layout.scene3);
70        showFragment(fragment1, false, null);
71        assertNull(fragment1.mRed);
72        assertNotNull(fragment1.mGreen);
73        assertNotNull(fragment1.mBlue);
74        showFragment(fragment2, true, new Pair<>(fragment1.mGreen, "green"));
75        assertNotNull(fragment2.mRed);
76        assertNotNull(fragment2.mGreen);
77        assertNotNull(fragment2.mBlue);
78    }
79
80    @Test
81    public void nonSharedTransition() {
82        final TransitionFragment fragment1 = TransitionFragment.newInstance(R.layout.scene2);
83        final TransitionFragment fragment2 = TransitionFragment.newInstance(R.layout.scene3);
84        showFragment(fragment1, false, null);
85        showFragment(fragment2, true, null);
86        verify(fragment1.mListeners.get(TransitionFragment.TRANSITION_EXIT))
87                .onTransitionStart(any(Transition.class));
88        verify(fragment1.mListeners.get(TransitionFragment.TRANSITION_EXIT), timeout(3000))
89                .onTransitionEnd(any(Transition.class));
90        verify(fragment2.mListeners.get(TransitionFragment.TRANSITION_ENTER))
91                .onTransitionStart(any(Transition.class));
92        verify(fragment2.mListeners.get(TransitionFragment.TRANSITION_ENTER), timeout(3000))
93                .onTransitionEnd(any(Transition.class));
94        popBackStack();
95        verify(fragment1.mListeners.get(TransitionFragment.TRANSITION_REENTER))
96                .onTransitionStart(any(Transition.class));
97        verify(fragment2.mListeners.get(TransitionFragment.TRANSITION_RETURN))
98                .onTransitionStart(any(Transition.class));
99    }
100
101    @Test
102    public void sharedTransition() {
103        final TransitionFragment fragment1 = TransitionFragment.newInstance(R.layout.scene2);
104        final TransitionFragment fragment2 = TransitionFragment.newInstance(R.layout.scene3);
105        showFragment(fragment1, false, null);
106        showFragment(fragment2, true, new Pair<>(fragment1.mGreen, "green"));
107        verify(fragment2.mListeners.get(TransitionFragment.TRANSITION_SHARED_ENTER))
108                .onTransitionStart(any(Transition.class));
109        verify(fragment2.mListeners.get(TransitionFragment.TRANSITION_SHARED_ENTER), timeout(3000))
110                .onTransitionEnd(any(Transition.class));
111        popBackStack();
112        verify(fragment2.mListeners.get(TransitionFragment.TRANSITION_SHARED_RETURN))
113                .onTransitionStart(any(Transition.class));
114    }
115
116    private void showFragment(final Fragment fragment, final boolean addToBackStack,
117            final Pair<View, String> sharedElement) {
118        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
119        instrumentation.runOnMainSync(new Runnable() {
120            @Override
121            public void run() {
122                final FragmentTransaction transaction = getFragmentManager().beginTransaction();
123                transaction.replace(R.id.root, fragment);
124                transaction.setReorderingAllowed(mReorderingAllowed);
125                if (sharedElement != null) {
126                    transaction.addSharedElement(sharedElement.first, sharedElement.second);
127                }
128                if (addToBackStack) {
129                    transaction.addToBackStack(null);
130                    transaction.commit();
131                    getFragmentManager().executePendingTransactions();
132                } else {
133                    transaction.commitNow();
134                }
135            }
136        });
137        instrumentation.waitForIdleSync();
138    }
139
140    private void popBackStack() {
141        final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
142        instrumentation.runOnMainSync(new Runnable() {
143            @Override
144            public void run() {
145                getFragmentManager().popBackStackImmediate();
146            }
147        });
148        instrumentation.waitForIdleSync();
149    }
150
151    private FragmentManager getFragmentManager() {
152        return rule.getActivity().getSupportFragmentManager();
153    }
154
155    /**
156     * A {@link Fragment} with all kinds of {@link Transition} with tracking listeners.
157     */
158    public static class TransitionFragment extends Fragment {
159
160        static final int TRANSITION_ENTER = 1;
161        static final int TRANSITION_EXIT = 2;
162        static final int TRANSITION_REENTER = 3;
163        static final int TRANSITION_RETURN = 4;
164        static final int TRANSITION_SHARED_ENTER = 5;
165        static final int TRANSITION_SHARED_RETURN = 6;
166
167        private static final String ARG_LAYOUT_ID = "layout_id";
168
169        View mRed;
170        View mGreen;
171        View mBlue;
172
173        SparseArrayCompat<Transition.TransitionListener> mListeners = new SparseArrayCompat<>();
174
175        public static TransitionFragment newInstance(@LayoutRes int layout) {
176            final Bundle args = new Bundle();
177            args.putInt(ARG_LAYOUT_ID, layout);
178            final TransitionFragment fragment = new TransitionFragment();
179            fragment.setArguments(args);
180            return fragment;
181        }
182
183        public TransitionFragment() {
184            setEnterTransition(createTransition(TRANSITION_ENTER));
185            setExitTransition(createTransition(TRANSITION_EXIT));
186            setReenterTransition(createTransition(TRANSITION_REENTER));
187            setReturnTransition(createTransition(TRANSITION_RETURN));
188            setSharedElementEnterTransition(createTransition(TRANSITION_SHARED_ENTER));
189            setSharedElementReturnTransition(createTransition(TRANSITION_SHARED_RETURN));
190        }
191
192        @Nullable
193        @Override
194        public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
195                @Nullable Bundle savedInstanceState) {
196            return inflater.inflate(getArguments().getInt(ARG_LAYOUT_ID), container, false);
197        }
198
199        @Override
200        public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
201            mRed = view.findViewById(R.id.redSquare);
202            mGreen = view.findViewById(R.id.greenSquare);
203            mBlue = view.findViewById(R.id.blueSquare);
204            if (mRed != null) {
205                ViewCompat.setTransitionName(mRed, "red");
206            }
207            if (mGreen != null) {
208                ViewCompat.setTransitionName(mGreen, "green");
209            }
210            if (mBlue != null) {
211                ViewCompat.setTransitionName(mBlue, "blue");
212            }
213        }
214
215        private Transition createTransition(int type) {
216            final Transition.TransitionListener listener = mock(
217                    Transition.TransitionListener.class);
218            final AutoTransition transition = new AutoTransition();
219            transition.addListener(listener);
220            transition.setDuration(10);
221            mListeners.put(type, listener);
222            return transition;
223        }
224
225    }
226
227}
228