1/*
2 * Copyright 2018 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 */
16package androidx.fragment.app;
17
18import static org.junit.Assert.assertEquals;
19import static org.junit.Assert.assertFalse;
20import static org.junit.Assert.assertNotNull;
21import static org.junit.Assert.assertNull;
22import static org.junit.Assert.assertTrue;
23import static org.junit.Assert.fail;
24
25import android.app.Instrumentation;
26import android.os.Build;
27import android.os.Bundle;
28import android.os.Parcelable;
29import android.support.test.InstrumentationRegistry;
30import android.support.test.filters.LargeTest;
31import android.support.test.filters.SdkSuppress;
32import android.support.test.rule.ActivityTestRule;
33import android.support.test.runner.AndroidJUnit4;
34import android.util.Pair;
35import android.view.LayoutInflater;
36import android.view.View;
37import android.view.ViewGroup;
38
39import androidx.fragment.app.test.FragmentTestActivity;
40import androidx.fragment.test.R;
41
42import org.junit.Before;
43import org.junit.Rule;
44import org.junit.Test;
45import org.junit.runner.RunWith;
46
47@LargeTest
48@RunWith(AndroidJUnit4.class)
49@SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP)
50public class PostponedTransitionTest {
51    @Rule
52    public ActivityTestRule<FragmentTestActivity> mActivityRule =
53            new ActivityTestRule<FragmentTestActivity>(FragmentTestActivity.class);
54
55    private Instrumentation mInstrumentation;
56    private PostponedFragment1 mBeginningFragment;
57
58    @Before
59    public void setupContainer() throws Throwable {
60        mInstrumentation = InstrumentationRegistry.getInstrumentation();
61        FragmentTestUtil.setContentView(mActivityRule, R.layout.simple_container);
62        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
63        mBeginningFragment = new PostponedFragment1();
64        fm.beginTransaction()
65                .add(R.id.fragmentContainer, mBeginningFragment)
66                .setReorderingAllowed(true)
67                .commit();
68        FragmentTestUtil.waitForExecution(mActivityRule);
69
70        mBeginningFragment.startPostponedEnterTransition();
71        mBeginningFragment.waitForTransition();
72        clearTargets(mBeginningFragment);
73    }
74
75    // Ensure that replacing with a fragment that has a postponed transition
76    // will properly postpone it, both adding and popping.
77    @Test
78    public void replaceTransition() throws Throwable {
79        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
80        final View startBlue = mActivityRule.getActivity().findViewById(R.id.blueSquare);
81
82        final PostponedFragment2 fragment = new PostponedFragment2();
83        fm.beginTransaction()
84                .addSharedElement(startBlue, "blueSquare")
85                .replace(R.id.fragmentContainer, fragment)
86                .addToBackStack(null)
87                .setReorderingAllowed(true)
88                .commit();
89
90        FragmentTestUtil.waitForExecution(mActivityRule);
91
92        // should be postponed now
93        assertPostponedTransition(mBeginningFragment, fragment, null);
94
95        // start the postponed transition
96        fragment.startPostponedEnterTransition();
97
98        // make sure it ran
99        assertForwardTransition(mBeginningFragment, fragment);
100
101        FragmentTestUtil.popBackStackImmediate(mActivityRule);
102
103        // should be postponed going back, too
104        assertPostponedTransition(fragment, mBeginningFragment, null);
105
106        // start the postponed transition
107        mBeginningFragment.startPostponedEnterTransition();
108
109        // make sure it ran
110        assertBackTransition(fragment, mBeginningFragment);
111    }
112
113    // Ensure that replacing a fragment doesn't cause problems with the back stack nesting level
114    @Test
115    public void backStackNestingLevel() throws Throwable {
116        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
117        View startBlue = mActivityRule.getActivity().findViewById(R.id.blueSquare);
118
119        final TransitionFragment fragment1 = new TransitionFragment2();
120        fm.beginTransaction()
121                .addSharedElement(startBlue, "blueSquare")
122                .replace(R.id.fragmentContainer, fragment1)
123                .addToBackStack(null)
124                .setReorderingAllowed(true)
125                .commit();
126
127        // make sure transition ran
128        assertForwardTransition(mBeginningFragment, fragment1);
129
130        FragmentTestUtil.popBackStackImmediate(mActivityRule);
131
132        // should be postponed going back
133        assertPostponedTransition(fragment1, mBeginningFragment, null);
134
135        // start the postponed transition
136        mBeginningFragment.startPostponedEnterTransition();
137
138        // make sure it ran
139        assertBackTransition(fragment1, mBeginningFragment);
140
141        startBlue = mActivityRule.getActivity().findViewById(R.id.blueSquare);
142
143        final TransitionFragment fragment2 = new TransitionFragment2();
144        fm.beginTransaction()
145                .addSharedElement(startBlue, "blueSquare")
146                .replace(R.id.fragmentContainer, fragment2)
147                .addToBackStack(null)
148                .setReorderingAllowed(true)
149                .commit();
150
151        // make sure transition ran
152        assertForwardTransition(mBeginningFragment, fragment2);
153
154        FragmentTestUtil.popBackStackImmediate(mActivityRule);
155
156        // should be postponed going back
157        assertPostponedTransition(fragment2, mBeginningFragment, null);
158
159        // start the postponed transition
160        mBeginningFragment.startPostponedEnterTransition();
161
162        // make sure it ran
163        assertBackTransition(fragment2, mBeginningFragment);
164    }
165
166    // Ensure that postponed transition is forced after another has been committed.
167    // This tests when the transactions are executed together
168    @Test
169    public void forcedTransition1() throws Throwable {
170        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
171        final View startBlue = mActivityRule.getActivity().findViewById(R.id.blueSquare);
172
173        final PostponedFragment2 fragment2 = new PostponedFragment2();
174        final PostponedFragment1 fragment3 = new PostponedFragment1();
175
176        final int[] commit = new int[1];
177        // Need to run this on the UI thread so that the transaction doesn't start
178        // between the two
179        mInstrumentation.runOnMainSync(new Runnable() {
180            @Override
181            public void run() {
182                commit[0] = fm.beginTransaction()
183                        .addSharedElement(startBlue, "blueSquare")
184                        .replace(R.id.fragmentContainer, fragment2)
185                        .addToBackStack(null)
186                        .setReorderingAllowed(true)
187                        .commit();
188
189                fm.beginTransaction()
190                        .addSharedElement(startBlue, "blueSquare")
191                        .replace(R.id.fragmentContainer, fragment3)
192                        .addToBackStack(null)
193                        .setReorderingAllowed(true)
194                        .commit();
195            }
196        });
197        FragmentTestUtil.waitForExecution(mActivityRule);
198
199        // transition to fragment2 should be started
200        assertForwardTransition(mBeginningFragment, fragment2);
201
202        // fragment3 should be postponed, but fragment2 should be executed with no transition.
203        assertPostponedTransition(fragment2, fragment3, mBeginningFragment);
204
205        // start the postponed transition
206        fragment3.startPostponedEnterTransition();
207
208        // make sure it ran
209        assertForwardTransition(fragment2, fragment3);
210
211        FragmentTestUtil.popBackStackImmediate(mActivityRule, commit[0],
212                FragmentManager.POP_BACK_STACK_INCLUSIVE);
213
214        assertBackTransition(fragment3, fragment2);
215
216        assertPostponedTransition(fragment2, mBeginningFragment, fragment3);
217
218        // start the postponed transition
219        mBeginningFragment.startPostponedEnterTransition();
220
221        // make sure it ran
222        assertBackTransition(fragment2, mBeginningFragment);
223    }
224
225    // Ensure that postponed transition is forced after another has been committed.
226    // This tests when the transactions are processed separately.
227    @Test
228    public void forcedTransition2() throws Throwable {
229        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
230        final View startBlue = mActivityRule.getActivity().findViewById(R.id.blueSquare);
231
232        final PostponedFragment2 fragment2 = new PostponedFragment2();
233
234        fm.beginTransaction()
235                .addSharedElement(startBlue, "blueSquare")
236                .replace(R.id.fragmentContainer, fragment2)
237                .addToBackStack(null)
238                .setReorderingAllowed(true)
239                .commit();
240
241        FragmentTestUtil.waitForExecution(mActivityRule);
242
243        assertPostponedTransition(mBeginningFragment, fragment2, null);
244
245        final PostponedFragment1 fragment3 = new PostponedFragment1();
246        fm.beginTransaction()
247                .addSharedElement(startBlue, "blueSquare")
248                .replace(R.id.fragmentContainer, fragment3)
249                .addToBackStack(null)
250                .setReorderingAllowed(true)
251                .commit();
252
253        // This should cancel the mBeginningFragment -> fragment2 transition
254        // and start fragment2 -> fragment3 transition postponed
255        FragmentTestUtil.waitForExecution(mActivityRule);
256
257        // fragment3 should be postponed, but fragment2 should be executed with no transition.
258        assertPostponedTransition(fragment2, fragment3, mBeginningFragment);
259
260        // start the postponed transition
261        fragment3.startPostponedEnterTransition();
262
263        // make sure it ran
264        assertForwardTransition(fragment2, fragment3);
265
266        // Pop back to fragment2, but it should be postponed
267        FragmentTestUtil.popBackStackImmediate(mActivityRule);
268
269        assertPostponedTransition(fragment3, fragment2, null);
270
271        // Pop to mBeginningFragment -- should cancel the fragment2 transition and
272        // start the mBeginningFragment transaction postponed
273
274        FragmentTestUtil.popBackStackImmediate(mActivityRule);
275
276        assertPostponedTransition(fragment2, mBeginningFragment, fragment3);
277
278        // start the postponed transition
279        mBeginningFragment.startPostponedEnterTransition();
280
281        // make sure it ran
282        assertBackTransition(fragment2, mBeginningFragment);
283    }
284
285    // Do a bunch of things to one fragment in a transaction and see if it can screw things up.
286    @Test
287    public void crazyTransition() throws Throwable {
288        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
289        final View startBlue = mActivityRule.getActivity().findViewById(R.id.blueSquare);
290
291        final PostponedFragment2 fragment2 = new PostponedFragment2();
292
293        fm.beginTransaction()
294                .addSharedElement(startBlue, "blueSquare")
295                .hide(mBeginningFragment)
296                .replace(R.id.fragmentContainer, fragment2)
297                .hide(fragment2)
298                .detach(fragment2)
299                .attach(fragment2)
300                .show(fragment2)
301                .addToBackStack(null)
302                .setReorderingAllowed(true)
303                .commit();
304
305        FragmentTestUtil.waitForExecution(mActivityRule);
306
307        assertPostponedTransition(mBeginningFragment, fragment2, null);
308
309        // start the postponed transition
310        fragment2.startPostponedEnterTransition();
311
312        // make sure it ran
313        assertForwardTransition(mBeginningFragment, fragment2);
314
315        // Pop back to fragment2, but it should be postponed
316        FragmentTestUtil.popBackStackImmediate(mActivityRule);
317
318        assertPostponedTransition(fragment2, mBeginningFragment, null);
319
320        // start the postponed transition
321        mBeginningFragment.startPostponedEnterTransition();
322
323        // make sure it ran
324        assertBackTransition(fragment2, mBeginningFragment);
325    }
326
327    // Execute transactions on different containers and ensure that they don't conflict
328    @Test
329    public void differentContainers() throws Throwable {
330        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
331        fm.beginTransaction()
332                .remove(mBeginningFragment)
333                .setReorderingAllowed(true)
334                .commit();
335        FragmentTestUtil.waitForExecution(mActivityRule);
336        FragmentTestUtil.setContentView(mActivityRule, R.layout.double_container);
337
338        TransitionFragment fragment1 = new PostponedFragment1();
339        TransitionFragment fragment2 = new PostponedFragment1();
340
341        fm.beginTransaction()
342                .add(R.id.fragmentContainer1, fragment1)
343                .add(R.id.fragmentContainer2, fragment2)
344                .setReorderingAllowed(true)
345                .commit();
346        FragmentTestUtil.waitForExecution(mActivityRule);
347        fragment1.startPostponedEnterTransition();
348        fragment2.startPostponedEnterTransition();
349        fragment1.waitForTransition();
350        fragment2.waitForTransition();
351        clearTargets(fragment1);
352        clearTargets(fragment2);
353
354        final View startBlue1 = fragment1.getView().findViewById(R.id.blueSquare);
355        final View startBlue2 = fragment2.getView().findViewById(R.id.blueSquare);
356
357        final TransitionFragment fragment3 = new PostponedFragment2();
358
359        fm.beginTransaction()
360                .addSharedElement(startBlue1, "blueSquare")
361                .replace(R.id.fragmentContainer1, fragment3)
362                .addToBackStack(null)
363                .setReorderingAllowed(true)
364                .commit();
365
366        FragmentTestUtil.waitForExecution(mActivityRule);
367
368        assertPostponedTransition(fragment1, fragment3, null);
369
370        final TransitionFragment fragment4 = new PostponedFragment2();
371
372        fm.beginTransaction()
373                .addSharedElement(startBlue2, "blueSquare")
374                .replace(R.id.fragmentContainer2, fragment4)
375                .addToBackStack(null)
376                .setReorderingAllowed(true)
377                .commit();
378
379        FragmentTestUtil.waitForExecution(mActivityRule);
380
381        assertPostponedTransition(fragment1, fragment3, null);
382        assertPostponedTransition(fragment2, fragment4, null);
383
384        // start the postponed transition
385        fragment3.startPostponedEnterTransition();
386
387        // make sure only one ran
388        assertForwardTransition(fragment1, fragment3);
389        assertPostponedTransition(fragment2, fragment4, null);
390
391        // start the postponed transition
392        fragment4.startPostponedEnterTransition();
393
394        // make sure it ran
395        assertForwardTransition(fragment2, fragment4);
396
397        // Pop back to fragment2 -- should be postponed
398        FragmentTestUtil.popBackStackImmediate(mActivityRule);
399
400        assertPostponedTransition(fragment4, fragment2, null);
401
402        // Pop back to fragment1 -- also should be postponed
403        FragmentTestUtil.popBackStackImmediate(mActivityRule);
404
405        assertPostponedTransition(fragment4, fragment2, null);
406        assertPostponedTransition(fragment3, fragment1, null);
407
408        // start the postponed transition
409        fragment2.startPostponedEnterTransition();
410
411        // make sure it ran
412        assertBackTransition(fragment4, fragment2);
413
414        // but not the postponed one
415        assertPostponedTransition(fragment3, fragment1, null);
416
417        // start the postponed transition
418        fragment1.startPostponedEnterTransition();
419
420        // make sure it ran
421        assertBackTransition(fragment3, fragment1);
422    }
423
424    // Execute transactions on different containers and ensure that they don't conflict.
425    // The postponement can be started out-of-order
426    @Test
427    public void outOfOrderContainers() throws Throwable {
428        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
429        fm.beginTransaction()
430                .remove(mBeginningFragment)
431                .setReorderingAllowed(true)
432                .commit();
433        FragmentTestUtil.waitForExecution(mActivityRule);
434        FragmentTestUtil.setContentView(mActivityRule, R.layout.double_container);
435
436        TransitionFragment fragment1 = new PostponedFragment1();
437        TransitionFragment fragment2 = new PostponedFragment1();
438
439        fm.beginTransaction()
440                .add(R.id.fragmentContainer1, fragment1)
441                .add(R.id.fragmentContainer2, fragment2)
442                .setReorderingAllowed(true)
443                .commit();
444        FragmentTestUtil.waitForExecution(mActivityRule);
445        fragment1.startPostponedEnterTransition();
446        fragment2.startPostponedEnterTransition();
447        fragment1.waitForTransition();
448        fragment2.waitForTransition();
449        clearTargets(fragment1);
450        clearTargets(fragment2);
451
452        final View startBlue1 = fragment1.getView().findViewById(R.id.blueSquare);
453        final View startBlue2 = fragment2.getView().findViewById(R.id.blueSquare);
454
455        final TransitionFragment fragment3 = new PostponedFragment2();
456
457        fm.beginTransaction()
458                .addSharedElement(startBlue1, "blueSquare")
459                .replace(R.id.fragmentContainer1, fragment3)
460                .addToBackStack(null)
461                .setReorderingAllowed(true)
462                .commit();
463
464        FragmentTestUtil.waitForExecution(mActivityRule);
465
466        assertPostponedTransition(fragment1, fragment3, null);
467
468        final TransitionFragment fragment4 = new PostponedFragment2();
469
470        fm.beginTransaction()
471                .addSharedElement(startBlue2, "blueSquare")
472                .replace(R.id.fragmentContainer2, fragment4)
473                .addToBackStack(null)
474                .setReorderingAllowed(true)
475                .commit();
476
477        FragmentTestUtil.waitForExecution(mActivityRule);
478
479        assertPostponedTransition(fragment1, fragment3, null);
480        assertPostponedTransition(fragment2, fragment4, null);
481
482        // start the postponed transition
483        fragment4.startPostponedEnterTransition();
484
485        // make sure only one ran
486        assertForwardTransition(fragment2, fragment4);
487        assertPostponedTransition(fragment1, fragment3, null);
488
489        // start the postponed transition
490        fragment3.startPostponedEnterTransition();
491
492        // make sure it ran
493        assertForwardTransition(fragment1, fragment3);
494
495        // Pop back to fragment2 -- should be postponed
496        FragmentTestUtil.popBackStackImmediate(mActivityRule);
497
498        assertPostponedTransition(fragment4, fragment2, null);
499
500        // Pop back to fragment1 -- also should be postponed
501        FragmentTestUtil.popBackStackImmediate(mActivityRule);
502
503        assertPostponedTransition(fragment4, fragment2, null);
504        assertPostponedTransition(fragment3, fragment1, null);
505
506        // start the postponed transition
507        fragment1.startPostponedEnterTransition();
508
509        // make sure it ran
510        assertBackTransition(fragment3, fragment1);
511
512        // but not the postponed one
513        assertPostponedTransition(fragment4, fragment2, null);
514
515        // start the postponed transition
516        fragment2.startPostponedEnterTransition();
517
518        // make sure it ran
519        assertBackTransition(fragment4, fragment2);
520    }
521
522    // Make sure that commitNow for a transaction on a different fragment container doesn't
523    // affect the postponed transaction
524    @Test
525    public void commitNowNoEffect() throws Throwable {
526        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
527        fm.beginTransaction()
528                .remove(mBeginningFragment)
529                .setReorderingAllowed(true)
530                .commit();
531        FragmentTestUtil.waitForExecution(mActivityRule);
532        FragmentTestUtil.setContentView(mActivityRule, R.layout.double_container);
533
534        final TransitionFragment fragment1 = new PostponedFragment1();
535        final TransitionFragment fragment2 = new PostponedFragment1();
536
537        fm.beginTransaction()
538                .add(R.id.fragmentContainer1, fragment1)
539                .add(R.id.fragmentContainer2, fragment2)
540                .setReorderingAllowed(true)
541                .commit();
542        FragmentTestUtil.waitForExecution(mActivityRule);
543        fragment1.startPostponedEnterTransition();
544        fragment2.startPostponedEnterTransition();
545        fragment1.waitForTransition();
546        fragment2.waitForTransition();
547        clearTargets(fragment1);
548        clearTargets(fragment2);
549
550        final View startBlue1 = fragment1.getView().findViewById(R.id.blueSquare);
551        final View startBlue2 = fragment2.getView().findViewById(R.id.blueSquare);
552
553        final TransitionFragment fragment3 = new PostponedFragment2();
554        final StrictFragment strictFragment1 = new StrictFragment();
555
556        fm.beginTransaction()
557                .addSharedElement(startBlue1, "blueSquare")
558                .replace(R.id.fragmentContainer1, fragment3)
559                .add(strictFragment1, "1")
560                .addToBackStack(null)
561                .setReorderingAllowed(true)
562                .commit();
563
564        FragmentTestUtil.waitForExecution(mActivityRule);
565
566        assertPostponedTransition(fragment1, fragment3, null);
567
568        final TransitionFragment fragment4 = new PostponedFragment2();
569        final StrictFragment strictFragment2 = new StrictFragment();
570
571        mInstrumentation.runOnMainSync(new Runnable() {
572            @Override
573            public void run() {
574                fm.beginTransaction()
575                        .addSharedElement(startBlue2, "blueSquare")
576                        .replace(R.id.fragmentContainer2, fragment4)
577                        .remove(strictFragment1)
578                        .add(strictFragment2, "2")
579                        .setReorderingAllowed(true)
580                        .commitNow();
581            }
582        });
583
584        FragmentTestUtil.waitForExecution(mActivityRule);
585
586        assertPostponedTransition(fragment1, fragment3, null);
587        assertPostponedTransition(fragment2, fragment4, null);
588
589        // start the postponed transition
590        fragment4.startPostponedEnterTransition();
591
592        // make sure only one ran
593        assertForwardTransition(fragment2, fragment4);
594        assertPostponedTransition(fragment1, fragment3, null);
595
596        // start the postponed transition
597        fragment3.startPostponedEnterTransition();
598
599        // make sure it ran
600        assertForwardTransition(fragment1, fragment3);
601    }
602
603    // Make sure that commitNow for a transaction affecting a postponed fragment in the same
604    // container forces the postponed transition to start.
605    @Test
606    public void commitNowStartsPostponed() throws Throwable {
607        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
608        final View startBlue1 = mBeginningFragment.getView().findViewById(R.id.blueSquare);
609
610        final TransitionFragment fragment2 = new PostponedFragment2();
611        final TransitionFragment fragment1 = new PostponedFragment1();
612
613        fm.beginTransaction()
614                .addSharedElement(startBlue1, "blueSquare")
615                .replace(R.id.fragmentContainer, fragment2)
616                .addToBackStack(null)
617                .setReorderingAllowed(true)
618                .commit();
619        FragmentTestUtil.waitForExecution(mActivityRule);
620
621        final View startBlue2 = fragment2.getView().findViewById(R.id.blueSquare);
622
623        mInstrumentation.runOnMainSync(new Runnable() {
624            @Override
625            public void run() {
626                fm.beginTransaction()
627                        .addSharedElement(startBlue2, "blueSquare")
628                        .replace(R.id.fragmentContainer, fragment1)
629                        .setReorderingAllowed(true)
630                        .commitNow();
631            }
632        });
633
634        assertPostponedTransition(fragment2, fragment1, mBeginningFragment);
635
636        // start the postponed transition
637        fragment1.startPostponedEnterTransition();
638
639        assertForwardTransition(fragment2, fragment1);
640    }
641
642    // Make sure that when a transaction that removes a view is postponed that
643    // another transaction doesn't accidentally remove the view early.
644    @Test
645    public void noAccidentalRemoval() throws Throwable {
646        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
647        fm.beginTransaction()
648                .remove(mBeginningFragment)
649                .setReorderingAllowed(true)
650                .commit();
651        FragmentTestUtil.waitForExecution(mActivityRule);
652        FragmentTestUtil.setContentView(mActivityRule, R.layout.double_container);
653
654        TransitionFragment fragment1 = new PostponedFragment1();
655
656        fm.beginTransaction()
657                .add(R.id.fragmentContainer1, fragment1)
658                .setReorderingAllowed(true)
659                .commit();
660        FragmentTestUtil.waitForExecution(mActivityRule);
661        fragment1.startPostponedEnterTransition();
662        fragment1.waitForTransition();
663        clearTargets(fragment1);
664
665        TransitionFragment fragment2 = new PostponedFragment2();
666        // Create a postponed transaction that removes a view
667        fm.beginTransaction()
668                .replace(R.id.fragmentContainer1, fragment2)
669                .setReorderingAllowed(true)
670                .commit();
671        FragmentTestUtil.waitForExecution(mActivityRule);
672        assertPostponedTransition(fragment1, fragment2, null);
673
674        TransitionFragment fragment3 = new PostponedFragment1();
675        // Create a transaction that doesn't interfere with the previously postponed one
676        fm.beginTransaction()
677                .replace(R.id.fragmentContainer2, fragment3)
678                .setReorderingAllowed(true)
679                .commit();
680        FragmentTestUtil.waitForExecution(mActivityRule);
681
682        assertPostponedTransition(fragment1, fragment2, null);
683
684        fragment3.startPostponedEnterTransition();
685        fragment3.waitForTransition();
686        clearTargets(fragment3);
687
688        assertPostponedTransition(fragment1, fragment2, null);
689    }
690
691    // Ensure that a postponed transaction that is popped runs immediately and that
692    // the transaction results in the original state with no transition.
693    @Test
694    public void popPostponedTransaction() throws Throwable {
695        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
696        final View startBlue = mBeginningFragment.getView().findViewById(R.id.blueSquare);
697
698        final TransitionFragment fragment = new PostponedFragment2();
699
700        fm.beginTransaction()
701                .addSharedElement(startBlue, "blueSquare")
702                .replace(R.id.fragmentContainer, fragment)
703                .addToBackStack(null)
704                .setReorderingAllowed(true)
705                .commit();
706        FragmentTestUtil.waitForExecution(mActivityRule);
707
708        assertPostponedTransition(mBeginningFragment, fragment, null);
709
710        FragmentTestUtil.popBackStackImmediate(mActivityRule);
711
712        fragment.waitForNoTransition();
713        mBeginningFragment.waitForNoTransition();
714
715        assureNoTransition(fragment);
716        assureNoTransition(mBeginningFragment);
717
718        assertFalse(fragment.isAdded());
719        assertNull(fragment.getView());
720        assertNotNull(mBeginningFragment.getView());
721        assertEquals(View.VISIBLE, mBeginningFragment.getView().getVisibility());
722        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
723            assertEquals(1f, mBeginningFragment.getView().getAlpha(), 0f);
724        }
725        assertTrue(mBeginningFragment.getView().isAttachedToWindow());
726    }
727
728    // Make sure that when saving the state during a postponed transaction that it saves
729    // the state as if it wasn't postponed.
730    @Test
731    public void saveWhilePostponed() throws Throwable {
732        final FragmentController fc1 = FragmentTestUtil.createController(mActivityRule);
733        FragmentTestUtil.resume(mActivityRule, fc1, null);
734
735        final FragmentManager fm1 = fc1.getSupportFragmentManager();
736
737        PostponedFragment1 fragment1 = new PostponedFragment1();
738        fm1.beginTransaction()
739                .add(R.id.fragmentContainer, fragment1, "1")
740                .addToBackStack(null)
741                .setReorderingAllowed(true)
742                .commit();
743        FragmentTestUtil.waitForExecution(mActivityRule);
744
745        Pair<Parcelable, FragmentManagerNonConfig> state =
746                FragmentTestUtil.destroy(mActivityRule, fc1);
747
748        final FragmentController fc2 = FragmentTestUtil.createController(mActivityRule);
749        FragmentTestUtil.resume(mActivityRule, fc2, state);
750
751        final FragmentManager fm2 = fc2.getSupportFragmentManager();
752        Fragment fragment2 = fm2.findFragmentByTag("1");
753        assertNotNull(fragment2);
754        assertNotNull(fragment2.getView());
755        assertEquals(View.VISIBLE, fragment2.getView().getVisibility());
756        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
757            assertEquals(1f, fragment2.getView().getAlpha(), 0f);
758        }
759        assertTrue(fragment2.isResumed());
760        assertTrue(fragment2.isAdded());
761        assertTrue(fragment2.getView().isAttachedToWindow());
762
763        mInstrumentation.runOnMainSync(new Runnable() {
764            @Override
765            public void run() {
766                assertTrue(fm2.popBackStackImmediate());
767
768            }
769        });
770
771        assertFalse(fragment2.isResumed());
772        assertFalse(fragment2.isAdded());
773        assertNull(fragment2.getView());
774    }
775
776    // Ensure that the postponed fragment transactions don't allow reentrancy in fragment manager
777    @Test
778    public void postponeDoesNotAllowReentrancy() throws Throwable {
779        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
780        final View startBlue = mActivityRule.getActivity().findViewById(R.id.blueSquare);
781
782        final CommitNowFragment fragment = new CommitNowFragment();
783        fm.beginTransaction()
784                .addSharedElement(startBlue, "blueSquare")
785                .replace(R.id.fragmentContainer, fragment)
786                .setReorderingAllowed(true)
787                .addToBackStack(null)
788                .commit();
789
790        FragmentTestUtil.waitForExecution(mActivityRule);
791
792        // should be postponed now
793        assertPostponedTransition(mBeginningFragment, fragment, null);
794
795        mActivityRule.runOnUiThread(new Runnable() {
796            @Override
797            public void run() {
798                // start the postponed transition
799                fragment.startPostponedEnterTransition();
800
801                try {
802                    // This should trigger an IllegalStateException
803                    fm.executePendingTransactions();
804                    fail("commitNow() while executing a transaction should cause an "
805                            + "IllegalStateException");
806                } catch (IllegalStateException e) {
807                    // expected
808                }
809            }
810        });
811    }
812
813    private void assertPostponedTransition(TransitionFragment fromFragment,
814            TransitionFragment toFragment, TransitionFragment removedFragment)
815            throws InterruptedException {
816        if (removedFragment != null) {
817            assertNull(removedFragment.getView());
818            assureNoTransition(removedFragment);
819        }
820
821        toFragment.waitForNoTransition();
822        assertNotNull(fromFragment.getView());
823        assertNotNull(toFragment.getView());
824        assertTrue(fromFragment.getView().isAttachedToWindow());
825        assertTrue(toFragment.getView().isAttachedToWindow());
826        assertEquals(View.VISIBLE, fromFragment.getView().getVisibility());
827        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
828            assertEquals(View.VISIBLE, toFragment.getView().getVisibility());
829            assertEquals(0f, toFragment.getView().getAlpha(), 0f);
830        } else {
831            assertEquals(View.INVISIBLE, toFragment.getView().getVisibility());
832        }
833        assureNoTransition(fromFragment);
834        assureNoTransition(toFragment);
835        assertTrue(fromFragment.isResumed());
836        assertFalse(toFragment.isResumed());
837    }
838
839    private void clearTargets(TransitionFragment fragment) {
840        fragment.enterTransition.targets.clear();
841        fragment.reenterTransition.targets.clear();
842        fragment.exitTransition.targets.clear();
843        fragment.returnTransition.targets.clear();
844        fragment.sharedElementEnter.targets.clear();
845        fragment.sharedElementReturn.targets.clear();
846    }
847
848    private void assureNoTransition(TransitionFragment fragment) {
849        assertEquals(0, fragment.enterTransition.targets.size());
850        assertEquals(0, fragment.reenterTransition.targets.size());
851        assertEquals(0, fragment.enterTransition.targets.size());
852        assertEquals(0, fragment.returnTransition.targets.size());
853        assertEquals(0, fragment.sharedElementEnter.targets.size());
854        assertEquals(0, fragment.sharedElementReturn.targets.size());
855    }
856
857    private void assertForwardTransition(TransitionFragment start, TransitionFragment end)
858            throws InterruptedException {
859        start.waitForTransition();
860        end.waitForTransition();
861        assertEquals(0, start.enterTransition.targets.size());
862        assertEquals(1, end.enterTransition.targets.size());
863
864        assertEquals(0, start.reenterTransition.targets.size());
865        assertEquals(0, end.reenterTransition.targets.size());
866
867        assertEquals(0, start.returnTransition.targets.size());
868        assertEquals(0, end.returnTransition.targets.size());
869
870        assertEquals(1, start.exitTransition.targets.size());
871        assertEquals(0, end.exitTransition.targets.size());
872
873        assertEquals(0, start.sharedElementEnter.targets.size());
874        assertEquals(2, end.sharedElementEnter.targets.size());
875
876        assertEquals(0, start.sharedElementReturn.targets.size());
877        assertEquals(0, end.sharedElementReturn.targets.size());
878
879        final View blue = end.getView().findViewById(R.id.blueSquare);
880        assertTrue(end.sharedElementEnter.targets.contains(blue));
881        assertEquals("blueSquare", end.sharedElementEnter.targets.get(0).getTransitionName());
882        assertEquals("blueSquare", end.sharedElementEnter.targets.get(1).getTransitionName());
883
884        assertNoTargets(start);
885        assertNoTargets(end);
886
887        clearTargets(start);
888        clearTargets(end);
889    }
890
891    private void assertBackTransition(TransitionFragment start, TransitionFragment end)
892            throws InterruptedException {
893        start.waitForTransition();
894        end.waitForTransition();
895        assertEquals(1, end.reenterTransition.targets.size());
896        assertEquals(0, start.reenterTransition.targets.size());
897
898        assertEquals(0, end.returnTransition.targets.size());
899        assertEquals(1, start.returnTransition.targets.size());
900
901        assertEquals(0, start.enterTransition.targets.size());
902        assertEquals(0, end.enterTransition.targets.size());
903
904        assertEquals(0, start.exitTransition.targets.size());
905        assertEquals(0, end.exitTransition.targets.size());
906
907        assertEquals(0, start.sharedElementEnter.targets.size());
908        assertEquals(0, end.sharedElementEnter.targets.size());
909
910        assertEquals(2, start.sharedElementReturn.targets.size());
911        assertEquals(0, end.sharedElementReturn.targets.size());
912
913        final View blue = end.getView().findViewById(R.id.blueSquare);
914        assertTrue(start.sharedElementReturn.targets.contains(blue));
915        assertEquals("blueSquare", start.sharedElementReturn.targets.get(0).getTransitionName());
916        assertEquals("blueSquare", start.sharedElementReturn.targets.get(1).getTransitionName());
917
918        assertNoTargets(end);
919        assertNoTargets(start);
920
921        clearTargets(start);
922        clearTargets(end);
923    }
924
925    private static void assertNoTargets(TransitionFragment fragment) {
926        assertTrue(fragment.enterTransition.getTargets().isEmpty());
927        assertTrue(fragment.reenterTransition.getTargets().isEmpty());
928        assertTrue(fragment.exitTransition.getTargets().isEmpty());
929        assertTrue(fragment.returnTransition.getTargets().isEmpty());
930        assertTrue(fragment.sharedElementEnter.getTargets().isEmpty());
931        assertTrue(fragment.sharedElementReturn.getTargets().isEmpty());
932    }
933
934    public static class PostponedFragment1 extends TransitionFragment {
935        @Override
936        public View onCreateView(LayoutInflater inflater, ViewGroup container,
937                Bundle savedInstanceState) {
938            postponeEnterTransition();
939            return inflater.inflate(R.layout.scene1, container, false);
940        }
941    }
942
943    public static class PostponedFragment2 extends TransitionFragment {
944        @Override
945        public View onCreateView(LayoutInflater inflater, ViewGroup container,
946                Bundle savedInstanceState) {
947            postponeEnterTransition();
948            return inflater.inflate(R.layout.scene2, container, false);
949        }
950    }
951
952    public static class CommitNowFragment extends PostponedFragment1 {
953        @Override
954        public void onResume() {
955            super.onResume();
956            // This should throw because this happens during the execution
957            getFragmentManager().beginTransaction()
958                    .add(R.id.fragmentContainer, new PostponedFragment1())
959                    .commitNow();
960        }
961    }
962
963    public static class TransitionFragment2 extends TransitionFragment {
964        @Override
965        public View onCreateView(LayoutInflater inflater, ViewGroup container,
966                Bundle savedInstanceState) {
967            super.onCreateView(inflater, container, savedInstanceState);
968            return inflater.inflate(R.layout.scene2, container, false);
969        }
970    }
971}
972