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