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 */
16
17
18package androidx.fragment.app;
19
20import static org.junit.Assert.assertEquals;
21import static org.junit.Assert.assertFalse;
22import static org.junit.Assert.assertNotEquals;
23import static org.junit.Assert.assertNotNull;
24import static org.junit.Assert.assertNotSame;
25import static org.junit.Assert.assertNull;
26import static org.junit.Assert.assertSame;
27import static org.junit.Assert.assertTrue;
28import static org.junit.Assert.fail;
29import static org.mockito.Mockito.mock;
30import static org.mockito.Mockito.times;
31import static org.mockito.Mockito.verify;
32
33import android.content.Context;
34import android.content.Intent;
35import android.os.Build;
36import android.os.Bundle;
37import android.os.Parcelable;
38import android.support.test.InstrumentationRegistry;
39import android.support.test.annotation.UiThreadTest;
40import android.support.test.filters.MediumTest;
41import android.support.test.filters.SdkSuppress;
42import android.support.test.rule.ActivityTestRule;
43import android.support.test.runner.AndroidJUnit4;
44import android.util.Pair;
45import android.view.LayoutInflater;
46import android.view.Menu;
47import android.view.View;
48import android.view.ViewGroup;
49import android.view.Window;
50import android.widget.TextView;
51
52import androidx.annotation.NonNull;
53import androidx.annotation.Nullable;
54import androidx.core.app.ActivityCompat;
55import androidx.core.view.ViewCompat;
56import androidx.fragment.app.test.EmptyFragmentTestActivity;
57import androidx.fragment.app.test.FragmentTestActivity;
58import androidx.fragment.test.R;
59
60import org.junit.Assert;
61import org.junit.Rule;
62import org.junit.Test;
63import org.junit.runner.RunWith;
64
65import java.io.FileDescriptor;
66import java.io.PrintWriter;
67import java.util.concurrent.TimeUnit;
68
69@RunWith(AndroidJUnit4.class)
70@MediumTest
71public class FragmentLifecycleTest {
72
73    @Rule
74    public ActivityTestRule<EmptyFragmentTestActivity> mActivityRule =
75            new ActivityTestRule<EmptyFragmentTestActivity>(EmptyFragmentTestActivity.class);
76
77    @Test
78    public void basicLifecycle() throws Throwable {
79        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
80        final StrictFragment strictFragment = new StrictFragment();
81
82        // Add fragment; StrictFragment will throw if it detects any violation
83        // in standard lifecycle method ordering or expected preconditions.
84        fm.beginTransaction().add(strictFragment, "EmptyHeadless").commit();
85        executePendingTransactions(fm);
86
87        assertTrue("fragment is not added", strictFragment.isAdded());
88        assertFalse("fragment is detached", strictFragment.isDetached());
89        assertTrue("fragment is not resumed", strictFragment.isResumed());
90
91        // Test removal as well; StrictFragment will throw here too.
92        fm.beginTransaction().remove(strictFragment).commit();
93        executePendingTransactions(fm);
94
95        assertFalse("fragment is added", strictFragment.isAdded());
96        assertFalse("fragment is resumed", strictFragment.isResumed());
97
98        // This one is perhaps counterintuitive; "detached" means specifically detached
99        // but still managed by a FragmentManager. The .remove call above
100        // should not enter this state.
101        assertFalse("fragment is detached", strictFragment.isDetached());
102    }
103
104    @Test
105    public void detachment() throws Throwable {
106        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
107        final StrictFragment f1 = new StrictFragment();
108        final StrictFragment f2 = new StrictFragment();
109
110        fm.beginTransaction().add(f1, "1").add(f2, "2").commit();
111        executePendingTransactions(fm);
112
113        assertTrue("fragment 1 is not added", f1.isAdded());
114        assertTrue("fragment 2 is not added", f2.isAdded());
115
116        // Test detaching fragments using StrictFragment to throw on errors.
117        fm.beginTransaction().detach(f1).detach(f2).commit();
118        executePendingTransactions(fm);
119
120        assertTrue("fragment 1 is not detached", f1.isDetached());
121        assertTrue("fragment 2 is not detached", f2.isDetached());
122        assertFalse("fragment 1 is added", f1.isAdded());
123        assertFalse("fragment 2 is added", f2.isAdded());
124
125        // Only reattach f1; leave v2 detached.
126        fm.beginTransaction().attach(f1).commit();
127        executePendingTransactions(fm);
128
129        assertTrue("fragment 1 is not added", f1.isAdded());
130        assertFalse("fragment 1 is detached", f1.isDetached());
131        assertTrue("fragment 2 is not detached", f2.isDetached());
132
133        // Remove both from the FragmentManager.
134        fm.beginTransaction().remove(f1).remove(f2).commit();
135        executePendingTransactions(fm);
136
137        assertFalse("fragment 1 is added", f1.isAdded());
138        assertFalse("fragment 2 is added", f2.isAdded());
139        assertFalse("fragment 1 is detached", f1.isDetached());
140        assertFalse("fragment 2 is detached", f2.isDetached());
141    }
142
143    @Test
144    public void basicBackStack() throws Throwable {
145        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
146        final StrictFragment f1 = new StrictFragment();
147        final StrictFragment f2 = new StrictFragment();
148
149        // Add a fragment normally to set up
150        fm.beginTransaction().add(f1, "1").commit();
151        executePendingTransactions(fm);
152
153        assertTrue("fragment 1 is not added", f1.isAdded());
154
155        // Remove the first one and add a second. We're not using replace() here since
156        // these fragments are headless and as of this test writing, replace() only works
157        // for fragments with views and a container view id.
158        // Add it to the back stack so we can pop it afterwards.
159        fm.beginTransaction().remove(f1).add(f2, "2").addToBackStack("stack1").commit();
160        executePendingTransactions(fm);
161
162        assertFalse("fragment 1 is added", f1.isAdded());
163        assertTrue("fragment 2 is not added", f2.isAdded());
164
165        // Test popping the stack
166        fm.popBackStack();
167        executePendingTransactions(fm);
168
169        assertFalse("fragment 2 is added", f2.isAdded());
170        assertTrue("fragment 1 is not added", f1.isAdded());
171    }
172
173    @Test
174    public void attachBackStack() throws Throwable {
175        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
176        final StrictFragment f1 = new StrictFragment();
177        final StrictFragment f2 = new StrictFragment();
178
179        // Add a fragment normally to set up
180        fm.beginTransaction().add(f1, "1").commit();
181        executePendingTransactions(fm);
182
183        assertTrue("fragment 1 is not added", f1.isAdded());
184
185        fm.beginTransaction().detach(f1).add(f2, "2").addToBackStack("stack1").commit();
186        executePendingTransactions(fm);
187
188        assertTrue("fragment 1 is not detached", f1.isDetached());
189        assertFalse("fragment 2 is detached", f2.isDetached());
190        assertFalse("fragment 1 is added", f1.isAdded());
191        assertTrue("fragment 2 is not added", f2.isAdded());
192    }
193
194    @Test
195    public void viewLifecycle() throws Throwable {
196        // Test basic lifecycle when the fragment creates a view
197
198        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
199        final StrictViewFragment f1 = new StrictViewFragment();
200
201        fm.beginTransaction().add(android.R.id.content, f1).commit();
202        executePendingTransactions(fm);
203
204        assertTrue("fragment 1 is not added", f1.isAdded());
205        final View view = f1.getView();
206        assertNotNull("fragment 1 returned null from getView", view);
207        assertTrue("fragment 1's view is not attached to a window",
208                ViewCompat.isAttachedToWindow(view));
209
210        fm.beginTransaction().remove(f1).commit();
211        executePendingTransactions(fm);
212
213        assertFalse("fragment 1 is added", f1.isAdded());
214        assertNull("fragment 1 returned non-null from getView after removal", f1.getView());
215        assertFalse("fragment 1's previous view is still attached to a window",
216                ViewCompat.isAttachedToWindow(view));
217    }
218
219    @Test
220    public void viewReplace() throws Throwable {
221        // Replace one view with another, then reverse it with the back stack
222
223        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
224        final StrictViewFragment f1 = new StrictViewFragment();
225        final StrictViewFragment f2 = new StrictViewFragment();
226
227        fm.beginTransaction().add(android.R.id.content, f1).commit();
228        executePendingTransactions(fm);
229
230        assertTrue("fragment 1 is not added", f1.isAdded());
231
232        View origView1 = f1.getView();
233        assertNotNull("fragment 1 returned null view", origView1);
234        assertTrue("fragment 1's view not attached", ViewCompat.isAttachedToWindow(origView1));
235
236        fm.beginTransaction().replace(android.R.id.content, f2).addToBackStack("stack1").commit();
237        executePendingTransactions(fm);
238
239        assertFalse("fragment 1 is added", f1.isAdded());
240        assertTrue("fragment 2 is added", f2.isAdded());
241        assertNull("fragment 1 returned non-null view", f1.getView());
242        assertFalse("fragment 1's old view still attached",
243                ViewCompat.isAttachedToWindow(origView1));
244        View origView2 = f2.getView();
245        assertNotNull("fragment 2 returned null view", origView2);
246        assertTrue("fragment 2's view not attached", ViewCompat.isAttachedToWindow(origView2));
247
248        fm.popBackStack();
249        executePendingTransactions(fm);
250
251        assertTrue("fragment 1 is not added", f1.isAdded());
252        assertFalse("fragment 2 is added", f2.isAdded());
253        assertNull("fragment 2 returned non-null view", f2.getView());
254        assertFalse("fragment 2's view still attached", ViewCompat.isAttachedToWindow(origView2));
255        View newView1 = f1.getView();
256        assertNotSame("fragment 1 had same view from last attachment", origView1, newView1);
257        assertTrue("fragment 1's view not attached", ViewCompat.isAttachedToWindow(newView1));
258    }
259
260    @Test
261    @UiThreadTest
262    public void setInitialSavedState() throws Throwable {
263        FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
264
265        // Add a StateSaveFragment
266        StateSaveFragment fragment = new StateSaveFragment("Saved", "");
267        fm.beginTransaction().add(fragment, "tag").commit();
268        executePendingTransactions(fm);
269
270        // Change the user visible hint before we save state
271        fragment.setUserVisibleHint(false);
272
273        // Save its state and remove it
274        Fragment.SavedState state = fm.saveFragmentInstanceState(fragment);
275        fm.beginTransaction().remove(fragment).commit();
276        executePendingTransactions(fm);
277
278        // Create a new instance, calling setInitialSavedState
279        fragment = new StateSaveFragment("", "");
280        fragment.setInitialSavedState(state);
281
282        // Add the new instance
283        fm.beginTransaction().add(fragment, "tag").commit();
284        executePendingTransactions(fm);
285
286        assertEquals("setInitialSavedState did not restore saved state",
287                "Saved", fragment.getSavedState());
288        assertEquals("setInitialSavedState did not restore user visible hint",
289                false, fragment.getUserVisibleHint());
290    }
291
292    @Test
293    @UiThreadTest
294    public void setInitialSavedStateWithSetUserVisibleHint() throws Throwable {
295        FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
296
297        // Add a StateSaveFragment
298        StateSaveFragment fragment = new StateSaveFragment("Saved", "");
299        fm.beginTransaction().add(fragment, "tag").commit();
300        executePendingTransactions(fm);
301
302        // Save its state and remove it
303        Fragment.SavedState state = fm.saveFragmentInstanceState(fragment);
304        fm.beginTransaction().remove(fragment).commit();
305        executePendingTransactions(fm);
306
307        // Create a new instance, calling setInitialSavedState
308        fragment = new StateSaveFragment("", "");
309        fragment.setInitialSavedState(state);
310
311        // Change the user visible hint after we call setInitialSavedState
312        fragment.setUserVisibleHint(false);
313
314        // Add the new instance
315        fm.beginTransaction().add(fragment, "tag").commit();
316        executePendingTransactions(fm);
317
318        assertEquals("setInitialSavedState did not restore saved state",
319                "Saved", fragment.getSavedState());
320        assertEquals("setUserVisibleHint should override setInitialSavedState",
321                false, fragment.getUserVisibleHint());
322    }
323
324    @Test
325    @UiThreadTest
326    public void restoreRetainedInstanceFragments() throws Throwable {
327        // Create a new FragmentManager in isolation, nest some assorted fragments
328        // and then restore them to a second new FragmentManager.
329
330        final FragmentController fc1 = FragmentController.createController(
331                new HostCallbacks(mActivityRule.getActivity()));
332
333        final FragmentManager fm1 = fc1.getSupportFragmentManager();
334
335        fc1.attachHost(null);
336        fc1.dispatchCreate();
337
338        // Configure fragments.
339
340        // Grandparent fragment will not retain instance
341        final StateSaveFragment grandparentFragment = new StateSaveFragment("Grandparent",
342                "UnsavedGrandparent");
343        assertNotNull("grandparent fragment saved state not initialized",
344                grandparentFragment.getSavedState());
345        assertNotNull("grandparent fragment unsaved state not initialized",
346                grandparentFragment.getUnsavedState());
347        fm1.beginTransaction().add(grandparentFragment, "tag:grandparent").commitNow();
348
349        // Parent fragment will retain instance
350        final StateSaveFragment parentFragment = new StateSaveFragment("Parent", "UnsavedParent");
351        assertNotNull("parent fragment saved state not initialized",
352                parentFragment.getSavedState());
353        assertNotNull("parent fragment unsaved state not initialized",
354                parentFragment.getUnsavedState());
355        parentFragment.setRetainInstance(true);
356        grandparentFragment.getChildFragmentManager().beginTransaction()
357                .add(parentFragment, "tag:parent").commitNow();
358        assertSame("parent fragment is not a child of grandparent",
359                grandparentFragment, parentFragment.getParentFragment());
360
361        // Child fragment will not retain instance
362        final StateSaveFragment childFragment = new StateSaveFragment("Child", "UnsavedChild");
363        assertNotNull("child fragment saved state not initialized",
364                childFragment.getSavedState());
365        assertNotNull("child fragment unsaved state not initialized",
366                childFragment.getUnsavedState());
367        parentFragment.getChildFragmentManager().beginTransaction()
368                .add(childFragment, "tag:child").commitNow();
369        assertSame("child fragment is not a child of grandpanret",
370                parentFragment, childFragment.getParentFragment());
371
372        // Saved for comparison later
373        final FragmentManager parentChildFragmentManager = parentFragment.getChildFragmentManager();
374
375        fc1.dispatchActivityCreated();
376        fc1.noteStateNotSaved();
377        fc1.execPendingActions();
378        fc1.dispatchStart();
379        fc1.dispatchResume();
380        fc1.execPendingActions();
381
382        // Bring the state back down to destroyed, simulating an activity restart
383        fc1.dispatchPause();
384        final Parcelable savedState = fc1.saveAllState();
385        final FragmentManagerNonConfig nonconf = fc1.retainNestedNonConfig();
386        fc1.dispatchStop();
387        fc1.dispatchDestroy();
388
389        // Create the new controller and restore state
390        final FragmentController fc2 = FragmentController.createController(
391                new HostCallbacks(mActivityRule.getActivity()));
392
393        final FragmentManager fm2 = fc2.getSupportFragmentManager();
394
395        fc2.attachHost(null);
396        fc2.restoreAllState(savedState, nonconf);
397        fc2.dispatchCreate();
398
399        // Confirm that the restored fragments are available and in the expected states
400        final StateSaveFragment restoredGrandparent = (StateSaveFragment) fm2.findFragmentByTag(
401                "tag:grandparent");
402        assertNotNull("grandparent fragment not restored", restoredGrandparent);
403
404        assertNotSame("grandparent fragment instance was saved",
405                grandparentFragment, restoredGrandparent);
406        assertEquals("grandparent fragment saved state was not equal",
407                grandparentFragment.getSavedState(), restoredGrandparent.getSavedState());
408        assertNotEquals("grandparent fragment unsaved state was unexpectedly preserved",
409                grandparentFragment.getUnsavedState(), restoredGrandparent.getUnsavedState());
410
411        final StateSaveFragment restoredParent = (StateSaveFragment) restoredGrandparent
412                .getChildFragmentManager().findFragmentByTag("tag:parent");
413        assertNotNull("parent fragment not restored", restoredParent);
414
415        assertSame("parent fragment instance was not saved", parentFragment, restoredParent);
416        assertEquals("parent fragment saved state was not equal",
417                parentFragment.getSavedState(), restoredParent.getSavedState());
418        assertEquals("parent fragment unsaved state was not equal",
419                parentFragment.getUnsavedState(), restoredParent.getUnsavedState());
420        assertNotSame("parent fragment has the same child FragmentManager",
421                parentChildFragmentManager, restoredParent.getChildFragmentManager());
422
423        final StateSaveFragment restoredChild = (StateSaveFragment) restoredParent
424                .getChildFragmentManager().findFragmentByTag("tag:child");
425        assertNotNull("child fragment not restored", restoredChild);
426
427        assertNotSame("child fragment instance state was saved", childFragment, restoredChild);
428        assertEquals("child fragment saved state was not equal",
429                childFragment.getSavedState(), restoredChild.getSavedState());
430        assertNotEquals("child fragment saved state was unexpectedly equal",
431                childFragment.getUnsavedState(), restoredChild.getUnsavedState());
432
433        fc2.dispatchActivityCreated();
434        fc2.noteStateNotSaved();
435        fc2.execPendingActions();
436        fc2.dispatchStart();
437        fc2.dispatchResume();
438        fc2.execPendingActions();
439
440        // Test that the fragments are in the configuration we expect
441
442        // Bring the state back down to destroyed before we finish the test
443        fc2.dispatchPause();
444        fc2.saveAllState();
445        fc2.dispatchStop();
446        fc2.dispatchDestroy();
447
448        assertTrue("grandparent not destroyed", restoredGrandparent.mCalledOnDestroy);
449        assertTrue("parent not destroyed", restoredParent.mCalledOnDestroy);
450        assertTrue("child not destroyed", restoredChild.mCalledOnDestroy);
451    }
452
453    @Test
454    @UiThreadTest
455    public void saveAnimationState() throws Throwable {
456        FragmentController fc = startupFragmentController(null);
457        FragmentManager fm = fc.getSupportFragmentManager();
458
459        fm.beginTransaction()
460                .setCustomAnimations(0, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out)
461                .add(android.R.id.content, SimpleFragment.create(R.layout.fragment_a))
462                .addToBackStack(null)
463                .commit();
464        fm.executePendingTransactions();
465
466        assertAnimationsMatch(fm, 0, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out);
467
468        // Causes save and restore of fragments and back stack
469        fc = restartFragmentController(fc);
470        fm = fc.getSupportFragmentManager();
471
472        assertAnimationsMatch(fm, 0, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out);
473
474        fm.beginTransaction()
475                .setCustomAnimations(R.anim.fade_in, R.anim.fade_out, 0, 0)
476                .replace(android.R.id.content, SimpleFragment.create(R.layout.fragment_b))
477                .addToBackStack(null)
478                .commit();
479        fm.executePendingTransactions();
480
481        assertAnimationsMatch(fm, R.anim.fade_in, R.anim.fade_out, 0, 0);
482
483        // Causes save and restore of fragments and back stack
484        fc = restartFragmentController(fc);
485        fm = fc.getSupportFragmentManager();
486
487        assertAnimationsMatch(fm, R.anim.fade_in, R.anim.fade_out, 0, 0);
488
489        fm.popBackStackImmediate();
490
491        assertAnimationsMatch(fm, 0, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out);
492
493        shutdownFragmentController(fc);
494    }
495
496    /**
497     * This test confirms that as long as a parent fragment has called super.onCreate,
498     * any child fragments added, committed and with transactions executed will be brought
499     * to at least the CREATED state by the time the parent fragment receives onCreateView.
500     * This means the child fragment will have received onAttach/onCreate.
501     */
502    @Test
503    @UiThreadTest
504    public void childFragmentManagerAttach() throws Throwable {
505        FragmentController fc = FragmentController.createController(
506                new HostCallbacks(mActivityRule.getActivity()));
507        fc.attachHost(null);
508        fc.dispatchCreate();
509
510        FragmentManager.FragmentLifecycleCallbacks
511                mockLc = mock(FragmentManager.FragmentLifecycleCallbacks.class);
512        FragmentManager.FragmentLifecycleCallbacks
513                mockRecursiveLc = mock(FragmentManager.FragmentLifecycleCallbacks.class);
514
515        FragmentManager fm = fc.getSupportFragmentManager();
516        fm.registerFragmentLifecycleCallbacks(mockLc, false);
517        fm.registerFragmentLifecycleCallbacks(mockRecursiveLc, true);
518
519        ChildFragmentManagerFragment fragment = new ChildFragmentManagerFragment();
520        fm.beginTransaction()
521                .add(android.R.id.content, fragment)
522                .commitNow();
523
524        verify(mockLc, times(1)).onFragmentCreated(fm, fragment, null);
525
526        fc.dispatchActivityCreated();
527
528        Fragment childFragment = fragment.getChildFragment();
529
530        verify(mockLc, times(1)).onFragmentActivityCreated(fm, fragment, null);
531        verify(mockRecursiveLc, times(1)).onFragmentActivityCreated(fm, fragment, null);
532        verify(mockRecursiveLc, times(1)).onFragmentActivityCreated(fm, childFragment, null);
533
534        fc.dispatchStart();
535
536        verify(mockLc, times(1)).onFragmentStarted(fm, fragment);
537        verify(mockRecursiveLc, times(1)).onFragmentStarted(fm, fragment);
538        verify(mockRecursiveLc, times(1)).onFragmentStarted(fm, childFragment);
539
540        fc.dispatchResume();
541
542        verify(mockLc, times(1)).onFragmentResumed(fm, fragment);
543        verify(mockRecursiveLc, times(1)).onFragmentResumed(fm, fragment);
544        verify(mockRecursiveLc, times(1)).onFragmentResumed(fm, childFragment);
545
546        // Confirm that the parent fragment received onAttachFragment
547        assertTrue("parent fragment did not receive onAttachFragment",
548                fragment.mCalledOnAttachFragment);
549
550        fc.dispatchStop();
551
552        verify(mockLc, times(1)).onFragmentStopped(fm, fragment);
553        verify(mockRecursiveLc, times(1)).onFragmentStopped(fm, fragment);
554        verify(mockRecursiveLc, times(1)).onFragmentStopped(fm, childFragment);
555
556        fc.dispatchDestroy();
557
558        verify(mockLc, times(1)).onFragmentDestroyed(fm, fragment);
559        verify(mockRecursiveLc, times(1)).onFragmentDestroyed(fm, fragment);
560        verify(mockRecursiveLc, times(1)).onFragmentDestroyed(fm, childFragment);
561    }
562
563    /**
564     * This test checks that FragmentLifecycleCallbacks are invoked when expected.
565     */
566    @Test
567    @UiThreadTest
568    public void fragmentLifecycleCallbacks() throws Throwable {
569        FragmentController fc = FragmentController.createController(
570                new HostCallbacks(mActivityRule.getActivity()));
571        fc.attachHost(null);
572        fc.dispatchCreate();
573
574        FragmentManager fm = fc.getSupportFragmentManager();
575
576        ChildFragmentManagerFragment fragment = new ChildFragmentManagerFragment();
577        fm.beginTransaction()
578                .add(android.R.id.content, fragment)
579                .commitNow();
580
581        fc.dispatchActivityCreated();
582
583        fc.dispatchStart();
584        fc.dispatchResume();
585
586        // Confirm that the parent fragment received onAttachFragment
587        assertTrue("parent fragment did not receive onAttachFragment",
588                fragment.mCalledOnAttachFragment);
589
590        fc.dispatchStop();
591        fc.dispatchDestroy();
592    }
593
594    /**
595     * This tests that fragments call onDestroy when the activity finishes.
596     */
597    @Test
598    @UiThreadTest
599    public void fragmentDestroyedOnFinish() throws Throwable {
600        FragmentController fc = startupFragmentController(null);
601        FragmentManager fm = fc.getSupportFragmentManager();
602
603        StrictViewFragment fragmentA = StrictViewFragment.create(R.layout.fragment_a);
604        StrictViewFragment fragmentB = StrictViewFragment.create(R.layout.fragment_b);
605        fm.beginTransaction()
606                .add(android.R.id.content, fragmentA)
607                .commit();
608        fm.executePendingTransactions();
609        fm.beginTransaction()
610                .replace(android.R.id.content, fragmentB)
611                .addToBackStack(null)
612                .commit();
613        fm.executePendingTransactions();
614        shutdownFragmentController(fc);
615        assertTrue(fragmentB.mCalledOnDestroy);
616        assertTrue(fragmentA.mCalledOnDestroy);
617    }
618
619    // Make sure that executing transactions during activity lifecycle events
620    // is properly prevented.
621    @Test
622    public void preventReentrantCalls() throws Throwable {
623        testLifecycleTransitionFailure(StrictFragment.ATTACHED, StrictFragment.CREATED);
624        testLifecycleTransitionFailure(StrictFragment.CREATED, StrictFragment.ACTIVITY_CREATED);
625        testLifecycleTransitionFailure(StrictFragment.ACTIVITY_CREATED, StrictFragment.STARTED);
626        testLifecycleTransitionFailure(StrictFragment.STARTED, StrictFragment.RESUMED);
627
628        testLifecycleTransitionFailure(StrictFragment.RESUMED, StrictFragment.STARTED);
629        testLifecycleTransitionFailure(StrictFragment.STARTED, StrictFragment.CREATED);
630        testLifecycleTransitionFailure(StrictFragment.CREATED, StrictFragment.ATTACHED);
631        testLifecycleTransitionFailure(StrictFragment.ATTACHED, StrictFragment.DETACHED);
632    }
633
634    private void testLifecycleTransitionFailure(final int fromState,
635            final int toState) throws Throwable {
636        mActivityRule.runOnUiThread(new Runnable() {
637            @Override
638            public void run() {
639                final FragmentController fc1 = FragmentController.createController(
640                        new HostCallbacks(mActivityRule.getActivity()));
641                FragmentTestUtil.resume(mActivityRule, fc1, null);
642
643                final FragmentManager fm1 = fc1.getSupportFragmentManager();
644
645                final Fragment reentrantFragment = ReentrantFragment.create(fromState, toState);
646
647                fm1.beginTransaction()
648                        .add(reentrantFragment, "reentrant")
649                        .commit();
650                try {
651                    fm1.executePendingTransactions();
652                } catch (IllegalStateException e) {
653                    fail("An exception shouldn't happen when initially adding the fragment");
654                }
655
656                // Now shut down the fragment controller. When fromState > toState, this should
657                // result in an exception
658                Pair<Parcelable, FragmentManagerNonConfig> savedState = null;
659                try {
660                    savedState = FragmentTestUtil.destroy(mActivityRule, fc1);
661                    if (fromState > toState) {
662                        fail("Expected IllegalStateException when moving from "
663                                + StrictFragment.stateToString(fromState) + " to "
664                                + StrictFragment.stateToString(toState));
665                    }
666                } catch (IllegalStateException e) {
667                    if (fromState < toState) {
668                        fail("Unexpected IllegalStateException when moving from "
669                                + StrictFragment.stateToString(fromState) + " to "
670                                + StrictFragment.stateToString(toState));
671                    }
672                    return; // test passed!
673                }
674
675                // now restore from saved state. This will be reached when
676                // fromState < toState. We want to catch the fragment while it
677                // is being restored as the fragment controller state is being brought up.
678
679                final FragmentController fc2 = FragmentController.createController(
680                        new HostCallbacks(mActivityRule.getActivity()));
681                try {
682                    FragmentTestUtil.resume(mActivityRule, fc2, savedState);
683
684                    fail("Expected IllegalStateException when moving from "
685                            + StrictFragment.stateToString(fromState) + " to "
686                            + StrictFragment.stateToString(toState));
687                } catch (IllegalStateException e) {
688                    // expected, so the test passed!
689                }
690            }
691        });
692    }
693
694    /**
695     * Test to ensure that when dispatch* is called that the fragment manager
696     * doesn't cause the contained fragment states to change even if no state changes.
697     */
698    @Test
699    @UiThreadTest
700    public void noPrematureStateChange() throws Throwable {
701        FragmentController fc = startupFragmentController(null);
702        FragmentManager fm = fc.getSupportFragmentManager();
703
704        fm.beginTransaction()
705                .add(new StrictFragment(), "1")
706                .commitNow();
707
708        Parcelable savedState = shutdownFragmentController(fc);
709        fc = FragmentController.createController(
710                new HostCallbacks(mActivityRule.getActivity()));
711
712        fc.attachHost(null);
713        fc.dispatchCreate();
714        fc.dispatchActivityCreated();
715        fc.noteStateNotSaved();
716        fc.execPendingActions();
717        fc.dispatchStart();
718        fc.dispatchResume();
719        fc.restoreAllState(savedState, (FragmentManagerNonConfig) null);
720        fc.dispatchResume();
721        fm = fc.getSupportFragmentManager();
722
723        StrictFragment fragment1 = (StrictFragment) fm.findFragmentByTag("1");
724
725        assertFalse(fragment1.mCalledOnResume);
726    }
727
728    @Test
729    @UiThreadTest
730    public void testIsStateSaved() throws Throwable {
731        FragmentController fc = startupFragmentController(null);
732        FragmentManager fm = fc.getSupportFragmentManager();
733
734        Fragment f = new StrictFragment();
735        fm.beginTransaction()
736                .add(f, "1")
737                .commitNow();
738
739        assertFalse("fragment reported state saved while resumed", f.isStateSaved());
740
741        fc.dispatchPause();
742        fc.saveAllState();
743
744        assertTrue("fragment reported state not saved after saveAllState", f.isStateSaved());
745
746        fc.dispatchStop();
747
748        assertTrue("fragment reported state not saved after stop", f.isStateSaved());
749
750        fc.dispatchDestroy();
751
752        assertFalse("fragment reported state saved after destroy", f.isStateSaved());
753    }
754
755    @Test
756    @UiThreadTest
757    public void testSetArgumentsLifecycle() throws Throwable {
758        FragmentController fc = startupFragmentController(null);
759        FragmentManager fm = fc.getSupportFragmentManager();
760
761        Fragment f = new StrictFragment();
762        f.setArguments(new Bundle());
763
764        fm.beginTransaction()
765                .add(f, "1")
766                .commitNow();
767
768        f.setArguments(new Bundle());
769
770        fc.dispatchPause();
771        fc.saveAllState();
772
773        boolean threw = false;
774        try {
775            f.setArguments(new Bundle());
776        } catch (IllegalStateException ise) {
777            threw = true;
778        }
779        assertTrue("fragment allowed setArguments after state save", threw);
780
781        fc.dispatchStop();
782
783        threw = false;
784        try {
785            f.setArguments(new Bundle());
786        } catch (IllegalStateException ise) {
787            threw = true;
788        }
789        assertTrue("fragment allowed setArguments after stop", threw);
790
791        fc.dispatchDestroy();
792
793        // Fully destroyed, so fragments have been removed.
794        f.setArguments(new Bundle());
795    }
796
797    /*
798     * Test that target fragments are in a useful state when we restore them, even if they're
799     * on the back stack.
800     */
801
802    @Test
803    @UiThreadTest
804    public void targetFragmentRestoreLifecycleStateBackStack() throws Throwable {
805        final FragmentController fc1 = FragmentController.createController(
806                new HostCallbacks(mActivityRule.getActivity()));
807
808        final FragmentManager fm1 = fc1.getSupportFragmentManager();
809
810        fc1.attachHost(null);
811        fc1.dispatchCreate();
812
813        final Fragment target = new TargetFragment();
814        fm1.beginTransaction().add(target, "target").commitNow();
815
816        final Fragment referrer = new ReferrerFragment();
817        referrer.setTargetFragment(target, 0);
818
819        fm1.beginTransaction()
820                .remove(target)
821                .add(referrer, "referrer")
822                .addToBackStack(null)
823                .commit();
824
825        fc1.dispatchActivityCreated();
826        fc1.noteStateNotSaved();
827        fc1.execPendingActions();
828        fc1.dispatchStart();
829        fc1.dispatchResume();
830        fc1.execPendingActions();
831
832        // Bring the state back down to destroyed, simulating an activity restart
833        fc1.dispatchPause();
834        final Parcelable savedState = fc1.saveAllState();
835        final FragmentManagerNonConfig nonconf = fc1.retainNestedNonConfig();
836        fc1.dispatchStop();
837        fc1.dispatchDestroy();
838
839        final FragmentController fc2 = FragmentController.createController(
840                new HostCallbacks(mActivityRule.getActivity()));
841        final FragmentManager fm2 = fc2.getSupportFragmentManager();
842
843        fc2.attachHost(null);
844        fc2.restoreAllState(savedState, nonconf);
845        fc2.dispatchCreate();
846
847        fc2.dispatchActivityCreated();
848        fc2.noteStateNotSaved();
849        fc2.execPendingActions();
850        fc2.dispatchStart();
851        fc2.dispatchResume();
852        fc2.execPendingActions();
853
854        // Bring the state back down to destroyed before we finish the test
855        fc2.dispatchPause();
856        fc2.saveAllState();
857        fc2.dispatchStop();
858        fc2.dispatchDestroy();
859    }
860
861    @Test
862    @UiThreadTest
863    public void targetFragmentRestoreLifecycleStateManagerOrder() throws Throwable {
864        final FragmentController fc1 = FragmentController.createController(
865                new HostCallbacks(mActivityRule.getActivity()));
866
867        final FragmentManager fm1 = fc1.getSupportFragmentManager();
868
869        fc1.attachHost(null);
870        fc1.dispatchCreate();
871
872        final Fragment target1 = new TargetFragment();
873        final Fragment referrer1 = new ReferrerFragment();
874        referrer1.setTargetFragment(target1, 0);
875
876        fm1.beginTransaction().add(target1, "target1").add(referrer1, "referrer1").commitNow();
877
878        final Fragment target2 = new TargetFragment();
879        final Fragment referrer2 = new ReferrerFragment();
880        referrer2.setTargetFragment(target2, 0);
881
882        // Order shouldn't matter.
883        fm1.beginTransaction().add(referrer2, "referrer2").add(target2, "target2").commitNow();
884
885        fc1.dispatchActivityCreated();
886        fc1.noteStateNotSaved();
887        fc1.execPendingActions();
888        fc1.dispatchStart();
889        fc1.dispatchResume();
890        fc1.execPendingActions();
891
892        // Bring the state back down to destroyed, simulating an activity restart
893        fc1.dispatchPause();
894        final Parcelable savedState = fc1.saveAllState();
895        final FragmentManagerNonConfig nonconf = fc1.retainNestedNonConfig();
896        fc1.dispatchStop();
897        fc1.dispatchDestroy();
898
899        final FragmentController fc2 = FragmentController.createController(
900                new HostCallbacks(mActivityRule.getActivity()));
901        final FragmentManager fm2 = fc2.getSupportFragmentManager();
902
903        fc2.attachHost(null);
904        fc2.restoreAllState(savedState, nonconf);
905        fc2.dispatchCreate();
906
907        fc2.dispatchActivityCreated();
908        fc2.noteStateNotSaved();
909        fc2.execPendingActions();
910        fc2.dispatchStart();
911        fc2.dispatchResume();
912        fc2.execPendingActions();
913
914        // Bring the state back down to destroyed before we finish the test
915        fc2.dispatchPause();
916        fc2.saveAllState();
917        fc2.dispatchStop();
918        fc2.dispatchDestroy();
919    }
920
921    @Test
922    public void targetFragmentNoCycles() throws Throwable {
923        final Fragment one = new Fragment();
924        final Fragment two = new Fragment();
925        final Fragment three = new Fragment();
926
927        try {
928            one.setTargetFragment(two, 0);
929            two.setTargetFragment(three, 0);
930            three.setTargetFragment(one, 0);
931            assertTrue("creating a fragment target cycle did not throw IllegalArgumentException",
932                    false);
933        } catch (IllegalArgumentException e) {
934            // Success!
935        }
936    }
937
938    @Test
939    public void targetFragmentSetClear() throws Throwable {
940        final Fragment one = new Fragment();
941        final Fragment two = new Fragment();
942
943        one.setTargetFragment(two, 0);
944        one.setTargetFragment(null, 0);
945    }
946
947    /**
948     * FragmentActivity should not raise the state of a Fragment while it is being destroyed.
949     */
950    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.JELLY_BEAN_MR1)
951    @Test
952    public void fragmentActivityFinishEarly() throws Throwable {
953        Intent intent = new Intent(mActivityRule.getActivity(), FragmentTestActivity.class);
954        intent.putExtra("finishEarly", true);
955        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
956
957        FragmentTestActivity activity = (FragmentTestActivity)
958                InstrumentationRegistry.getInstrumentation().startActivitySync(intent);
959
960        assertTrue(activity.onDestroyLatch.await(1000, TimeUnit.MILLISECONDS));
961    }
962
963    /**
964     * When a fragment is saved in non-config, it should be restored to the same index.
965     */
966    @Test
967    @UiThreadTest
968    public void restoreNonConfig() throws Throwable {
969        FragmentController fc = FragmentTestUtil.createController(mActivityRule);
970        FragmentTestUtil.resume(mActivityRule, fc, null);
971        FragmentManager fm = fc.getSupportFragmentManager();
972
973        Fragment fragment1 = new StrictFragment();
974        fm.beginTransaction()
975                .add(fragment1, "1")
976                .addToBackStack(null)
977                .commit();
978        fm.executePendingTransactions();
979        Fragment fragment2 = new StrictFragment();
980        fragment2.setRetainInstance(true);
981        fragment2.setTargetFragment(fragment1, 0);
982        Fragment fragment3 = new StrictFragment();
983        fm.beginTransaction()
984                .remove(fragment1)
985                .add(fragment2, "2")
986                .add(fragment3, "3")
987                .addToBackStack(null)
988                .commit();
989        fm.executePendingTransactions();
990
991        Pair<Parcelable, FragmentManagerNonConfig> savedState =
992                FragmentTestUtil.destroy(mActivityRule, fc);
993
994        fc = FragmentTestUtil.createController(mActivityRule);
995        FragmentTestUtil.resume(mActivityRule, fc, savedState);
996        boolean foundFragment2 = false;
997        for (Fragment fragment : fc.getSupportFragmentManager().getFragments()) {
998            if (fragment == fragment2) {
999                foundFragment2 = true;
1000                assertNotNull(fragment.getTargetFragment());
1001                assertEquals("1", fragment.getTargetFragment().getTag());
1002            } else {
1003                assertNotEquals("2", fragment.getTag());
1004            }
1005        }
1006        assertTrue(foundFragment2);
1007    }
1008
1009    /**
1010     * Check that retained fragments in the backstack correctly restored after two "configChanges"
1011     */
1012    @Test
1013    @UiThreadTest
1014    public void retainedFragmentInBackstack() throws Throwable {
1015        FragmentController fc = FragmentTestUtil.createController(mActivityRule);
1016        FragmentTestUtil.resume(mActivityRule, fc, null);
1017        FragmentManager fm = fc.getSupportFragmentManager();
1018
1019        Fragment fragment1 = new StrictFragment();
1020        fm.beginTransaction()
1021                .add(fragment1, "1")
1022                .addToBackStack(null)
1023                .commit();
1024        fm.executePendingTransactions();
1025
1026        Fragment child = new StrictFragment();
1027        child.setRetainInstance(true);
1028        fragment1.getChildFragmentManager().beginTransaction()
1029                .add(child, "child").commit();
1030        fragment1.getChildFragmentManager().executePendingTransactions();
1031
1032        Fragment fragment2 = new StrictFragment();
1033        fm.beginTransaction()
1034                .remove(fragment1)
1035                .add(fragment2, "2")
1036                .addToBackStack(null)
1037                .commit();
1038        fm.executePendingTransactions();
1039
1040        Pair<Parcelable, FragmentManagerNonConfig> savedState =
1041                FragmentTestUtil.destroy(mActivityRule, fc);
1042
1043        fc = FragmentTestUtil.createController(mActivityRule);
1044        FragmentTestUtil.resume(mActivityRule, fc, savedState);
1045        savedState = FragmentTestUtil.destroy(mActivityRule, fc);
1046        fc = FragmentTestUtil.createController(mActivityRule);
1047        FragmentTestUtil.resume(mActivityRule, fc, savedState);
1048        fm = fc.getSupportFragmentManager();
1049        fm.popBackStackImmediate();
1050        Fragment retainedChild = fm.findFragmentByTag("1")
1051                .getChildFragmentManager().findFragmentByTag("child");
1052        assertEquals(child, retainedChild);
1053    }
1054
1055    /**
1056     * When a fragment has been optimized out, it state should still be saved during
1057     * save and restore instance state.
1058     */
1059    @Test
1060    @UiThreadTest
1061    public void saveRemovedFragment() throws Throwable {
1062        FragmentController fc = FragmentTestUtil.createController(mActivityRule);
1063        FragmentTestUtil.resume(mActivityRule, fc, null);
1064        FragmentManager fm = fc.getSupportFragmentManager();
1065
1066        SaveStateFragment fragment1 = SaveStateFragment.create(1);
1067        fm.beginTransaction()
1068                .add(android.R.id.content, fragment1, "1")
1069                .addToBackStack(null)
1070                .commit();
1071        SaveStateFragment fragment2 = SaveStateFragment.create(2);
1072        fm.beginTransaction()
1073                .replace(android.R.id.content, fragment2, "2")
1074                .addToBackStack(null)
1075                .commit();
1076        fm.executePendingTransactions();
1077
1078        Pair<Parcelable, FragmentManagerNonConfig> savedState =
1079                FragmentTestUtil.destroy(mActivityRule, fc);
1080
1081        fc = FragmentTestUtil.createController(mActivityRule);
1082        FragmentTestUtil.resume(mActivityRule, fc, savedState);
1083        fm = fc.getSupportFragmentManager();
1084        fragment2 = (SaveStateFragment) fm.findFragmentByTag("2");
1085        assertNotNull(fragment2);
1086        assertEquals(2, fragment2.getValue());
1087        fm.popBackStackImmediate();
1088        fragment1 = (SaveStateFragment) fm.findFragmentByTag("1");
1089        assertNotNull(fragment1);
1090        assertEquals(1, fragment1.getValue());
1091    }
1092
1093    /**
1094     * When there are no retained instance fragments, the FragmentManagerNonConfig's fragments
1095     * should be null
1096     */
1097    @Test
1098    @UiThreadTest
1099    public void nullNonConfig() throws Throwable {
1100        FragmentController fc = FragmentTestUtil.createController(mActivityRule);
1101        FragmentTestUtil.resume(mActivityRule, fc, null);
1102        FragmentManager fm = fc.getSupportFragmentManager();
1103
1104        Fragment fragment1 = new StrictFragment();
1105        fm.beginTransaction()
1106                .add(fragment1, "1")
1107                .addToBackStack(null)
1108                .commit();
1109        fm.executePendingTransactions();
1110        Pair<Parcelable, FragmentManagerNonConfig> savedState =
1111                FragmentTestUtil.destroy(mActivityRule, fc);
1112        assertNull(savedState.second);
1113    }
1114
1115    /**
1116     * When the FragmentManager state changes, the pending transactions should execute.
1117     */
1118    @Test
1119    @UiThreadTest
1120    public void runTransactionsOnChange() throws Throwable {
1121        FragmentController fc = FragmentTestUtil.createController(mActivityRule);
1122        FragmentTestUtil.resume(mActivityRule, fc, null);
1123        FragmentManager fm = fc.getSupportFragmentManager();
1124
1125        RemoveHelloInOnResume fragment1 = new RemoveHelloInOnResume();
1126        StrictFragment fragment2 = new StrictFragment();
1127        fm.beginTransaction()
1128                .add(fragment1, "1")
1129                .setReorderingAllowed(false)
1130                .commit();
1131        fm.beginTransaction()
1132                .add(fragment2, "Hello")
1133                .setReorderingAllowed(false)
1134                .commit();
1135        fm.executePendingTransactions();
1136
1137        assertEquals(2, fm.getFragments().size());
1138        assertTrue(fm.getFragments().contains(fragment1));
1139        assertTrue(fm.getFragments().contains(fragment2));
1140
1141        Pair<Parcelable, FragmentManagerNonConfig> savedState =
1142                FragmentTestUtil.destroy(mActivityRule, fc);
1143        fc = FragmentTestUtil.createController(mActivityRule);
1144        FragmentTestUtil.resume(mActivityRule, fc, savedState);
1145        fm = fc.getSupportFragmentManager();
1146
1147        assertEquals(1, fm.getFragments().size());
1148        for (Fragment fragment : fm.getFragments()) {
1149            assertTrue(fragment instanceof RemoveHelloInOnResume);
1150        }
1151    }
1152
1153    @Test
1154    @UiThreadTest
1155    public void optionsMenu() throws Throwable {
1156        FragmentController fc = FragmentTestUtil.createController(mActivityRule);
1157        FragmentTestUtil.resume(mActivityRule, fc, null);
1158        FragmentManager fm = fc.getSupportFragmentManager();
1159
1160        InvalidateOptionFragment fragment = new InvalidateOptionFragment();
1161        fm.beginTransaction()
1162                .add(android.R.id.content, fragment)
1163                .commit();
1164        fm.executePendingTransactions();
1165
1166        Menu menu = mock(Menu.class);
1167        fc.dispatchPrepareOptionsMenu(menu);
1168        assertTrue(fragment.onPrepareOptionsMenuCalled);
1169        fragment.onPrepareOptionsMenuCalled = false;
1170        FragmentTestUtil.destroy(mActivityRule, fc);
1171        fc.dispatchPrepareOptionsMenu(menu);
1172        assertFalse(fragment.onPrepareOptionsMenuCalled);
1173    }
1174
1175    /**
1176     * When a retained instance fragment is saved while in the back stack, it should go
1177     * through onCreate() when it is popped back.
1178     */
1179    @Test
1180    @UiThreadTest
1181    public void retainInstanceWithOnCreate() throws Throwable {
1182        FragmentController fc = FragmentTestUtil.createController(mActivityRule);
1183        FragmentTestUtil.resume(mActivityRule, fc, null);
1184        FragmentManager fm = fc.getSupportFragmentManager();
1185
1186        OnCreateFragment fragment1 = new OnCreateFragment();
1187
1188        fm.beginTransaction()
1189                .add(fragment1, "1")
1190                .commit();
1191        fm.beginTransaction()
1192                .remove(fragment1)
1193                .addToBackStack(null)
1194                .commit();
1195
1196        Pair<Parcelable, FragmentManagerNonConfig> savedState =
1197                FragmentTestUtil.destroy(mActivityRule, fc);
1198        Pair<Parcelable, FragmentManagerNonConfig> restartState =
1199                Pair.create(savedState.first, null);
1200
1201        fc = FragmentTestUtil.createController(mActivityRule);
1202        FragmentTestUtil.resume(mActivityRule, fc, restartState);
1203
1204        // Save again, but keep the state
1205        savedState = FragmentTestUtil.destroy(mActivityRule, fc);
1206
1207        fc = FragmentTestUtil.createController(mActivityRule);
1208        FragmentTestUtil.resume(mActivityRule, fc, savedState);
1209
1210        fm = fc.getSupportFragmentManager();
1211
1212        fm.popBackStackImmediate();
1213        OnCreateFragment fragment2 = (OnCreateFragment) fm.findFragmentByTag("1");
1214        assertTrue(fragment2.onCreateCalled);
1215        fm.popBackStackImmediate();
1216    }
1217
1218    /**
1219     * A retained instance fragment should go through onCreate() once, even through save and
1220     * restore.
1221     */
1222    @Test
1223    @UiThreadTest
1224    public void retainInstanceOneOnCreate() throws Throwable {
1225        FragmentController fc = FragmentTestUtil.createController(mActivityRule);
1226        FragmentTestUtil.resume(mActivityRule, fc, null);
1227        FragmentManager fm = fc.getSupportFragmentManager();
1228
1229        OnCreateFragment fragment = new OnCreateFragment();
1230
1231        fm.beginTransaction()
1232                .add(fragment, "fragment")
1233                .commit();
1234        fm.executePendingTransactions();
1235
1236        fm.beginTransaction()
1237                .remove(fragment)
1238                .addToBackStack(null)
1239                .commit();
1240
1241        assertTrue(fragment.onCreateCalled);
1242        fragment.onCreateCalled = false;
1243
1244        Pair<Parcelable, FragmentManagerNonConfig> savedState =
1245                FragmentTestUtil.destroy(mActivityRule, fc);
1246
1247        fc = FragmentTestUtil.createController(mActivityRule);
1248        FragmentTestUtil.resume(mActivityRule, fc, savedState);
1249        fm = fc.getSupportFragmentManager();
1250
1251        fm.popBackStackImmediate();
1252        assertFalse(fragment.onCreateCalled);
1253    }
1254
1255    private void assertAnimationsMatch(FragmentManager fm, int enter, int exit, int popEnter,
1256            int popExit) {
1257        FragmentManagerImpl fmImpl = (FragmentManagerImpl) fm;
1258        BackStackRecord record = fmImpl.mBackStack.get(fmImpl.mBackStack.size() - 1);
1259
1260        Assert.assertEquals(enter, record.mEnterAnim);
1261        Assert.assertEquals(exit, record.mExitAnim);
1262        Assert.assertEquals(popEnter, record.mPopEnterAnim);
1263        Assert.assertEquals(popExit, record.mPopExitAnim);
1264    }
1265
1266    private FragmentController restartFragmentController(FragmentController fc) {
1267        Parcelable savedState = shutdownFragmentController(fc);
1268        return startupFragmentController(savedState);
1269    }
1270
1271    private FragmentController startupFragmentController(Parcelable savedState) {
1272        final FragmentController fc = FragmentController.createController(
1273                new HostCallbacks(mActivityRule.getActivity()));
1274        fc.attachHost(null);
1275        fc.restoreAllState(savedState, (FragmentManagerNonConfig) null);
1276        fc.dispatchCreate();
1277        fc.dispatchActivityCreated();
1278        fc.noteStateNotSaved();
1279        fc.execPendingActions();
1280        fc.dispatchStart();
1281        fc.dispatchResume();
1282        fc.execPendingActions();
1283        return fc;
1284    }
1285
1286    private Parcelable shutdownFragmentController(FragmentController fc) {
1287        fc.dispatchPause();
1288        final Parcelable savedState = fc.saveAllState();
1289        fc.dispatchStop();
1290        fc.dispatchDestroy();
1291        return savedState;
1292    }
1293
1294    private void executePendingTransactions(final FragmentManager fm) throws Throwable {
1295        mActivityRule.runOnUiThread(new Runnable() {
1296            @Override
1297            public void run() {
1298                fm.executePendingTransactions();
1299            }
1300        });
1301    }
1302
1303    public static class StateSaveFragment extends StrictFragment {
1304        private static final String STATE_KEY = "state";
1305
1306        private String mSavedState;
1307        private String mUnsavedState;
1308
1309        public StateSaveFragment() {
1310        }
1311
1312        public StateSaveFragment(String savedState, String unsavedState) {
1313            mSavedState = savedState;
1314            mUnsavedState = unsavedState;
1315        }
1316
1317        public String getSavedState() {
1318            return mSavedState;
1319        }
1320
1321        public String getUnsavedState() {
1322            return mUnsavedState;
1323        }
1324
1325        @Override
1326        public void onCreate(Bundle savedInstanceState) {
1327            super.onCreate(savedInstanceState);
1328            if (savedInstanceState != null) {
1329                mSavedState = savedInstanceState.getString(STATE_KEY);
1330            }
1331        }
1332
1333        @Override
1334        public void onSaveInstanceState(Bundle outState) {
1335            super.onSaveInstanceState(outState);
1336            outState.putString(STATE_KEY, mSavedState);
1337        }
1338    }
1339
1340    /**
1341     * This tests a deliberately odd use of a child fragment, added in onCreateView instead
1342     * of elsewhere. It simulates creating a UI child fragment added to the view hierarchy
1343     * created by this fragment.
1344     */
1345    public static class ChildFragmentManagerFragment extends StrictFragment {
1346        private FragmentManager mSavedChildFragmentManager;
1347        private ChildFragmentManagerChildFragment mChildFragment;
1348
1349        @Override
1350        public void onAttach(Context context) {
1351            super.onAttach(context);
1352            mSavedChildFragmentManager = getChildFragmentManager();
1353        }
1354
1355        @Nullable
1356        @Override
1357        public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
1358                @Nullable Bundle savedInstanceState) {
1359            assertSame("child FragmentManagers not the same instance", mSavedChildFragmentManager,
1360                    getChildFragmentManager());
1361            ChildFragmentManagerChildFragment child =
1362                    (ChildFragmentManagerChildFragment) mSavedChildFragmentManager
1363                            .findFragmentByTag("tag");
1364            if (child == null) {
1365                child = new ChildFragmentManagerChildFragment("foo");
1366                mSavedChildFragmentManager.beginTransaction()
1367                        .add(child, "tag")
1368                        .commitNow();
1369                assertEquals("argument strings don't match", "foo", child.getString());
1370            }
1371            mChildFragment = child;
1372            return new TextView(container.getContext());
1373        }
1374
1375        @Nullable
1376        public Fragment getChildFragment() {
1377            return mChildFragment;
1378        }
1379    }
1380
1381    public static class ChildFragmentManagerChildFragment extends StrictFragment {
1382        private String mString;
1383
1384        public ChildFragmentManagerChildFragment() {
1385        }
1386
1387        public ChildFragmentManagerChildFragment(String arg) {
1388            final Bundle b = new Bundle();
1389            b.putString("string", arg);
1390            setArguments(b);
1391        }
1392
1393        @Override
1394        public void onAttach(Context context) {
1395            super.onAttach(context);
1396            mString = getArguments().getString("string", "NO VALUE");
1397        }
1398
1399        public String getString() {
1400            return mString;
1401        }
1402    }
1403
1404    static class HostCallbacks extends FragmentHostCallback<FragmentActivity> {
1405        private final FragmentActivity mActivity;
1406
1407        public HostCallbacks(FragmentActivity activity) {
1408            super(activity);
1409            mActivity = activity;
1410        }
1411
1412        @Override
1413        public void onDump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
1414        }
1415
1416        @Override
1417        public boolean onShouldSaveFragmentState(Fragment fragment) {
1418            return !mActivity.isFinishing();
1419        }
1420
1421        @Override
1422        public LayoutInflater onGetLayoutInflater() {
1423            return mActivity.getLayoutInflater().cloneInContext(mActivity);
1424        }
1425
1426        @Override
1427        public FragmentActivity onGetHost() {
1428            return mActivity;
1429        }
1430
1431        @Override
1432        public void onSupportInvalidateOptionsMenu() {
1433            mActivity.supportInvalidateOptionsMenu();
1434        }
1435
1436        @Override
1437        public void onStartActivityFromFragment(Fragment fragment, Intent intent, int requestCode) {
1438            mActivity.startActivityFromFragment(fragment, intent, requestCode);
1439        }
1440
1441        @Override
1442        public void onStartActivityFromFragment(
1443                Fragment fragment, Intent intent, int requestCode, @Nullable Bundle options) {
1444            mActivity.startActivityFromFragment(fragment, intent, requestCode, options);
1445        }
1446
1447        @Override
1448        public void onRequestPermissionsFromFragment(@NonNull Fragment fragment,
1449                @NonNull String[] permissions, int requestCode) {
1450            throw new UnsupportedOperationException();
1451        }
1452
1453        @Override
1454        public boolean onShouldShowRequestPermissionRationale(@NonNull String permission) {
1455            return ActivityCompat.shouldShowRequestPermissionRationale(
1456                    mActivity, permission);
1457        }
1458
1459        @Override
1460        public boolean onHasWindowAnimations() {
1461            return mActivity.getWindow() != null;
1462        }
1463
1464        @Override
1465        public int onGetWindowAnimations() {
1466            final Window w = mActivity.getWindow();
1467            return (w == null) ? 0 : w.getAttributes().windowAnimations;
1468        }
1469
1470        @Override
1471        public void onAttachFragment(Fragment fragment) {
1472            mActivity.onAttachFragment(fragment);
1473        }
1474
1475        @Nullable
1476        @Override
1477        public View onFindViewById(int id) {
1478            return mActivity.findViewById(id);
1479        }
1480
1481        @Override
1482        public boolean onHasView() {
1483            final Window w = mActivity.getWindow();
1484            return (w != null && w.peekDecorView() != null);
1485        }
1486    }
1487
1488    public static class SimpleFragment extends Fragment {
1489        private int mLayoutId;
1490        private static final String LAYOUT_ID = "layoutId";
1491
1492        @Override
1493        public void onCreate(Bundle savedInstanceState) {
1494            super.onCreate(savedInstanceState);
1495            if (savedInstanceState != null) {
1496                mLayoutId = savedInstanceState.getInt(LAYOUT_ID, mLayoutId);
1497            }
1498        }
1499
1500        @Override
1501        public void onSaveInstanceState(Bundle outState) {
1502            super.onSaveInstanceState(outState);
1503            outState.putInt(LAYOUT_ID, mLayoutId);
1504        }
1505
1506        @Override
1507        public View onCreateView(LayoutInflater inflater, ViewGroup container,
1508                Bundle savedInstanceState) {
1509            return inflater.inflate(mLayoutId, container, false);
1510        }
1511
1512        public static SimpleFragment create(int layoutId) {
1513            SimpleFragment fragment = new SimpleFragment();
1514            fragment.mLayoutId = layoutId;
1515            return fragment;
1516        }
1517    }
1518
1519    public static class TargetFragment extends Fragment {
1520        public boolean calledCreate;
1521
1522        @Override
1523        public void onCreate(@Nullable Bundle savedInstanceState) {
1524            super.onCreate(savedInstanceState);
1525            calledCreate = true;
1526        }
1527    }
1528
1529    public static class ReferrerFragment extends Fragment {
1530        @Override
1531        public void onCreate(@Nullable Bundle savedInstanceState) {
1532            super.onCreate(savedInstanceState);
1533
1534            Fragment target = getTargetFragment();
1535            assertNotNull("target fragment was null during referrer onCreate", target);
1536
1537            if (!(target instanceof TargetFragment)) {
1538                throw new IllegalStateException("target fragment was not a TargetFragment");
1539            }
1540
1541            assertTrue("target fragment has not yet been created",
1542                    ((TargetFragment) target).calledCreate);
1543        }
1544    }
1545
1546    public static class SaveStateFragment extends Fragment {
1547        private static final String VALUE_KEY = "SaveStateFragment.mValue";
1548        private int mValue;
1549
1550        public static SaveStateFragment create(int value) {
1551            SaveStateFragment saveStateFragment = new SaveStateFragment();
1552            saveStateFragment.mValue = value;
1553            return saveStateFragment;
1554        }
1555
1556        @Override
1557        public void onSaveInstanceState(Bundle outState) {
1558            super.onSaveInstanceState(outState);
1559            outState.putInt(VALUE_KEY, mValue);
1560        }
1561
1562        @Override
1563        public void onCreate(Bundle savedInstanceState) {
1564            super.onCreate(savedInstanceState);
1565            if (savedInstanceState != null) {
1566                mValue = savedInstanceState.getInt(VALUE_KEY, mValue);
1567            }
1568        }
1569
1570        public int getValue() {
1571            return mValue;
1572        }
1573    }
1574
1575    public static class RemoveHelloInOnResume extends Fragment {
1576        @Override
1577        public void onResume() {
1578            super.onResume();
1579            Fragment fragment = getFragmentManager().findFragmentByTag("Hello");
1580            if (fragment != null) {
1581                getFragmentManager().beginTransaction().remove(fragment).commit();
1582            }
1583        }
1584    }
1585
1586    public static class InvalidateOptionFragment extends Fragment {
1587        public boolean onPrepareOptionsMenuCalled;
1588
1589        public InvalidateOptionFragment() {
1590            setHasOptionsMenu(true);
1591        }
1592
1593        @Override
1594        public void onPrepareOptionsMenu(Menu menu) {
1595            onPrepareOptionsMenuCalled = true;
1596            assertNotNull(getContext());
1597            super.onPrepareOptionsMenu(menu);
1598        }
1599    }
1600
1601    public static class OnCreateFragment extends Fragment {
1602        public boolean onCreateCalled;
1603
1604        public OnCreateFragment() {
1605            setRetainInstance(true);
1606        }
1607
1608        @Override
1609        public void onCreate(Bundle savedInstanceState) {
1610            super.onCreate(savedInstanceState);
1611            onCreateCalled = true;
1612        }
1613    }
1614}
1615