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