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