FragmentLifecycleTest.java revision ff22d81f6561f6cdd2a91eb63238c41079927a22
1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17
18package android.support.v4.app;
19
20import android.content.Context;
21import android.content.Intent;
22import android.os.Bundle;
23import android.os.Parcelable;
24import android.support.annotation.NonNull;
25import android.support.annotation.Nullable;
26import android.support.fragment.test.R;
27import android.support.test.annotation.UiThreadTest;
28import android.support.test.rule.ActivityTestRule;
29import android.support.test.runner.AndroidJUnit4;
30import android.support.v4.app.test.EmptyFragmentTestActivity;
31import android.test.suitebuilder.annotation.MediumTest;
32import android.view.LayoutInflater;
33import android.view.View;
34import android.view.ViewGroup;
35import android.view.Window;
36
37import android.widget.TextView;
38import org.junit.Assert;
39import org.junit.Rule;
40import org.junit.Test;
41import org.junit.runner.RunWith;
42
43import java.io.FileDescriptor;
44import java.io.PrintWriter;
45
46import static junit.framework.Assert.assertEquals;
47import static junit.framework.Assert.assertFalse;
48import static junit.framework.Assert.assertNotNull;
49import static junit.framework.Assert.assertNotSame;
50import static junit.framework.Assert.assertNull;
51import static junit.framework.Assert.assertSame;
52import static junit.framework.Assert.assertTrue;
53import static org.junit.Assert.assertNotEquals;
54
55@RunWith(AndroidJUnit4.class)
56@MediumTest
57public class FragmentLifecycleTest {
58
59    @Rule
60    public ActivityTestRule<EmptyFragmentTestActivity> mActivityRule =
61            new ActivityTestRule<EmptyFragmentTestActivity>(EmptyFragmentTestActivity.class);
62
63    @Test
64    public void basicLifecycle() throws Throwable {
65        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
66        final StrictFragment strictFragment = new StrictFragment();
67
68        // Add fragment; StrictFragment will throw if it detects any violation
69        // in standard lifecycle method ordering or expected preconditions.
70        fm.beginTransaction().add(strictFragment, "EmptyHeadless").commit();
71        executePendingTransactions(fm);
72
73        assertTrue("fragment is not added", strictFragment.isAdded());
74        assertFalse("fragment is detached", strictFragment.isDetached());
75        assertTrue("fragment is not resumed", strictFragment.isResumed());
76
77        // Test removal as well; StrictFragment will throw here too.
78        fm.beginTransaction().remove(strictFragment).commit();
79        executePendingTransactions(fm);
80
81        assertFalse("fragment is added", strictFragment.isAdded());
82        assertFalse("fragment is resumed", strictFragment.isResumed());
83
84        // This one is perhaps counterintuitive; "detached" means specifically detached
85        // but still managed by a FragmentManager. The .remove call above
86        // should not enter this state.
87        assertFalse("fragment is detached", strictFragment.isDetached());
88    }
89
90    @Test
91    public void detachment() throws Throwable {
92        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
93        final StrictFragment f1 = new StrictFragment();
94        final StrictFragment f2 = new StrictFragment();
95
96        fm.beginTransaction().add(f1, "1").add(f2, "2").commit();
97        executePendingTransactions(fm);
98
99        assertTrue("fragment 1 is not added", f1.isAdded());
100        assertTrue("fragment 2 is not added", f2.isAdded());
101
102        // Test detaching fragments using StrictFragment to throw on errors.
103        fm.beginTransaction().detach(f1).detach(f2).commit();
104        executePendingTransactions(fm);
105
106        assertTrue("fragment 1 is not detached", f1.isDetached());
107        assertTrue("fragment 2 is not detached", f2.isDetached());
108        assertFalse("fragment 1 is added", f1.isAdded());
109        assertFalse("fragment 2 is added", f2.isAdded());
110
111        // Only reattach f1; leave v2 detached.
112        fm.beginTransaction().attach(f1).commit();
113        executePendingTransactions(fm);
114
115        assertTrue("fragment 1 is not added", f1.isAdded());
116        assertFalse("fragment 1 is detached", f1.isDetached());
117        assertTrue("fragment 2 is not detached", f2.isDetached());
118
119        // Remove both from the FragmentManager.
120        fm.beginTransaction().remove(f1).remove(f2).commit();
121        executePendingTransactions(fm);
122
123        assertFalse("fragment 1 is added", f1.isAdded());
124        assertFalse("fragment 2 is added", f2.isAdded());
125        assertFalse("fragment 1 is detached", f1.isDetached());
126        assertFalse("fragment 2 is detached", f2.isDetached());
127    }
128
129    @Test
130    public void basicBackStack() throws Throwable {
131        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
132        final StrictFragment f1 = new StrictFragment();
133        final StrictFragment f2 = new StrictFragment();
134
135        // Add a fragment normally to set up
136        fm.beginTransaction().add(f1, "1").commit();
137        executePendingTransactions(fm);
138
139        assertTrue("fragment 1 is not added", f1.isAdded());
140
141        // Remove the first one and add a second. We're not using replace() here since
142        // these fragments are headless and as of this test writing, replace() only works
143        // for fragments with views and a container view id.
144        // Add it to the back stack so we can pop it afterwards.
145        fm.beginTransaction().remove(f1).add(f2, "2").addToBackStack("stack1").commit();
146        executePendingTransactions(fm);
147
148        assertFalse("fragment 1 is added", f1.isAdded());
149        assertTrue("fragment 2 is not added", f2.isAdded());
150
151        // Test popping the stack
152        fm.popBackStack();
153        executePendingTransactions(fm);
154
155        assertFalse("fragment 2 is added", f2.isAdded());
156        assertTrue("fragment 1 is not added", f1.isAdded());
157    }
158
159    @Test
160    public void attachBackStack() throws Throwable {
161        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
162        final StrictFragment f1 = new StrictFragment();
163        final StrictFragment f2 = new StrictFragment();
164
165        // Add a fragment normally to set up
166        fm.beginTransaction().add(f1, "1").commit();
167        executePendingTransactions(fm);
168
169        assertTrue("fragment 1 is not added", f1.isAdded());
170
171        fm.beginTransaction().detach(f1).add(f2, "2").addToBackStack("stack1").commit();
172        executePendingTransactions(fm);
173
174        assertTrue("fragment 1 is not detached", f1.isDetached());
175        assertFalse("fragment 2 is detached", f2.isDetached());
176        assertFalse("fragment 1 is added", f1.isAdded());
177        assertTrue("fragment 2 is not added", f2.isAdded());
178    }
179
180    @Test
181    public void viewLifecycle() throws Throwable {
182        // Test basic lifecycle when the fragment creates a view
183
184        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
185        final StrictViewFragment f1 = new StrictViewFragment();
186
187        fm.beginTransaction().add(android.R.id.content, f1).commit();
188        executePendingTransactions(fm);
189
190        assertTrue("fragment 1 is not added", f1.isAdded());
191        final View view = f1.getView();
192        assertNotNull("fragment 1 returned null from getView", view);
193        assertTrue("fragment 1's view is not attached to a window", view.isAttachedToWindow());
194
195        fm.beginTransaction().remove(f1).commit();
196        executePendingTransactions(fm);
197
198        assertFalse("fragment 1 is added", f1.isAdded());
199        assertNull("fragment 1 returned non-null from getView after removal", f1.getView());
200        assertFalse("fragment 1's previous view is still attached to a window",
201                view.isAttachedToWindow());
202    }
203
204    @Test
205    public void viewReplace() throws Throwable {
206        // Replace one view with another, then reverse it with the back stack
207
208        final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
209        final StrictViewFragment f1 = new StrictViewFragment();
210        final StrictViewFragment f2 = new StrictViewFragment();
211
212        fm.beginTransaction().add(android.R.id.content, f1).commit();
213        executePendingTransactions(fm);
214
215        assertTrue("fragment 1 is not added", f1.isAdded());
216
217        View origView1 = f1.getView();
218        assertNotNull("fragment 1 returned null view", origView1);
219        assertTrue("fragment 1's view not attached", origView1.isAttachedToWindow());
220
221        fm.beginTransaction().replace(android.R.id.content, f2).addToBackStack("stack1").commit();
222        executePendingTransactions(fm);
223
224        assertFalse("fragment 1 is added", f1.isAdded());
225        assertTrue("fragment 2 is added", f2.isAdded());
226        assertNull("fragment 1 returned non-null view", f1.getView());
227        assertFalse("fragment 1's old view still attached", origView1.isAttachedToWindow());
228        View origView2 = f2.getView();
229        assertNotNull("fragment 2 returned null view", origView2);
230        assertTrue("fragment 2's view not attached", origView2.isAttachedToWindow());
231
232        fm.popBackStack();
233        executePendingTransactions(fm);
234
235        assertTrue("fragment 1 is not added", f1.isAdded());
236        assertFalse("fragment 2 is added", f2.isAdded());
237        assertNull("fragment 2 returned non-null view", f2.getView());
238        assertFalse("fragment 2's view still attached", origView2.isAttachedToWindow());
239        View newView1 = f1.getView();
240        assertNotSame("fragment 1 had same view from last attachment", origView1, newView1);
241        assertTrue("fragment 1's view not attached", newView1.isAttachedToWindow());
242    }
243
244    @Test
245    @UiThreadTest
246    public void restoreRetainedInstanceFragments() throws Throwable {
247        // Create a new FragmentManager in isolation, nest some assorted fragments
248        // and then restore them to a second new FragmentManager.
249
250        final FragmentController fc1 = FragmentController.createController(
251                new HostCallbacks(mActivityRule.getActivity()));
252
253        final FragmentManager fm1 = fc1.getSupportFragmentManager();
254
255        fc1.attachHost(null);
256        fc1.dispatchCreate();
257
258        // Configure fragments.
259
260        // Grandparent fragment will not retain instance
261        final StateSaveFragment grandparentFragment = new StateSaveFragment("Grandparent",
262                "UnsavedGrandparent");
263        assertNotNull("grandparent fragment saved state not initialized",
264                grandparentFragment.getSavedState());
265        assertNotNull("grandparent fragment unsaved state not initialized",
266                grandparentFragment.getUnsavedState());
267        fm1.beginTransaction().add(grandparentFragment, "tag:grandparent").commitNow();
268
269        // Parent fragment will retain instance
270        final StateSaveFragment parentFragment = new StateSaveFragment("Parent", "UnsavedParent");
271        assertNotNull("parent fragment saved state not initialized",
272                parentFragment.getSavedState());
273        assertNotNull("parent fragment unsaved state not initialized",
274                parentFragment.getUnsavedState());
275        parentFragment.setRetainInstance(true);
276        grandparentFragment.getChildFragmentManager().beginTransaction()
277                .add(parentFragment, "tag:parent").commitNow();
278        assertSame("parent fragment is not a child of grandparent",
279                grandparentFragment, parentFragment.getParentFragment());
280
281        // Child fragment will not retain instance
282        final StateSaveFragment childFragment = new StateSaveFragment("Child", "UnsavedChild");
283        assertNotNull("child fragment saved state not initialized",
284                childFragment.getSavedState());
285        assertNotNull("child fragment unsaved state not initialized",
286                childFragment.getUnsavedState());
287        parentFragment.getChildFragmentManager().beginTransaction()
288                .add(childFragment, "tag:child").commitNow();
289        assertSame("child fragment is not a child of grandpanret",
290                parentFragment, childFragment.getParentFragment());
291
292        // Saved for comparison later
293        final FragmentManager parentChildFragmentManager = parentFragment.getChildFragmentManager();
294
295        fc1.dispatchActivityCreated();
296        fc1.noteStateNotSaved();
297        fc1.execPendingActions();
298        fc1.doLoaderStart();
299        fc1.dispatchStart();
300        fc1.reportLoaderStart();
301        fc1.dispatchResume();
302        fc1.execPendingActions();
303
304        // Bring the state back down to destroyed, simulating an activity restart
305        fc1.dispatchPause();
306        final Parcelable savedState = fc1.saveAllState();
307        final FragmentManagerNonConfig nonconf = fc1.retainNestedNonConfig();
308        fc1.dispatchStop();
309        fc1.dispatchReallyStop();
310        fc1.dispatchDestroy();
311
312        // Create the new controller and restore state
313        final FragmentController fc2 = FragmentController.createController(
314                new HostCallbacks(mActivityRule.getActivity()));
315
316        final FragmentManager fm2 = fc2.getSupportFragmentManager();
317
318        fc2.attachHost(null);
319        fc2.restoreAllState(savedState, nonconf);
320        fc2.dispatchCreate();
321
322        // Confirm that the restored fragments are available and in the expected states
323        final StateSaveFragment restoredGrandparent = (StateSaveFragment) fm2.findFragmentByTag(
324                "tag:grandparent");
325        assertNotNull("grandparent fragment not restored", restoredGrandparent);
326
327        assertNotSame("grandparent fragment instance was saved",
328                grandparentFragment, restoredGrandparent);
329        assertEquals("grandparent fragment saved state was not equal",
330                grandparentFragment.getSavedState(), restoredGrandparent.getSavedState());
331        assertNotEquals("grandparent fragment unsaved state was unexpectedly preserved",
332                grandparentFragment.getUnsavedState(), restoredGrandparent.getUnsavedState());
333
334        final StateSaveFragment restoredParent = (StateSaveFragment) restoredGrandparent
335                .getChildFragmentManager().findFragmentByTag("tag:parent");
336        assertNotNull("parent fragment not restored", restoredParent);
337
338        assertSame("parent fragment instance was not saved", parentFragment, restoredParent);
339        assertEquals("parent fragment saved state was not equal",
340                parentFragment.getSavedState(), restoredParent.getSavedState());
341        assertEquals("parent fragment unsaved state was not equal",
342                parentFragment.getUnsavedState(), restoredParent.getUnsavedState());
343        assertNotSame("parent fragment has the same child FragmentManager",
344                parentChildFragmentManager, restoredParent.getChildFragmentManager());
345
346        final StateSaveFragment restoredChild = (StateSaveFragment) restoredParent
347                .getChildFragmentManager().findFragmentByTag("tag:child");
348        assertNotNull("child fragment not restored", restoredChild);
349
350        assertNotSame("child fragment instance state was saved", childFragment, restoredChild);
351        assertEquals("child fragment saved state was not equal",
352                childFragment.getSavedState(), restoredChild.getSavedState());
353        assertNotEquals("child fragment saved state was unexpectedly equal",
354                childFragment.getUnsavedState(), restoredChild.getUnsavedState());
355
356        fc2.dispatchActivityCreated();
357        fc2.noteStateNotSaved();
358        fc2.execPendingActions();
359        fc2.doLoaderStart();
360        fc2.dispatchStart();
361        fc2.reportLoaderStart();
362        fc2.dispatchResume();
363        fc2.execPendingActions();
364
365        // Test that the fragments are in the configuration we expect
366
367        // Bring the state back down to destroyed before we finish the test
368        fc2.dispatchPause();
369        fc2.saveAllState();
370        fc2.dispatchStop();
371        fc2.dispatchReallyStop();
372        fc2.dispatchDestroy();
373
374        assertTrue("grandparent not destroyed", restoredGrandparent.mCalledOnDestroy);
375        assertTrue("parent not destroyed", restoredParent.mCalledOnDestroy);
376        assertTrue("child not destroyed", restoredChild.mCalledOnDestroy);
377    }
378
379    @Test
380    @UiThreadTest
381    public void saveAnimationState() throws Throwable {
382        FragmentController fc = startupFragmentController(null);
383        FragmentManager fm = fc.getSupportFragmentManager();
384
385        fm.beginTransaction()
386                .setCustomAnimations(0, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out)
387                .add(android.R.id.content, SimpleFragment.create(R.layout.fragment_a))
388                .addToBackStack(null)
389                .commit();
390        fm.executePendingTransactions();
391
392        assertAnimationsMatch(fm, 0, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out);
393
394        // Causes save and restore of fragments and back stack
395        fc = restartFragmentController(fc);
396        fm = fc.getSupportFragmentManager();
397
398        assertAnimationsMatch(fm, 0, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out);
399
400        fm.beginTransaction()
401                .setCustomAnimations(R.anim.fade_in, R.anim.fade_out, 0, 0)
402                .replace(android.R.id.content, SimpleFragment.create(R.layout.fragment_b))
403                .addToBackStack(null)
404                .commit();
405        fm.executePendingTransactions();
406
407        assertAnimationsMatch(fm, R.anim.fade_in, R.anim.fade_out, 0, 0);
408
409        // Causes save and restore of fragments and back stack
410        fc = restartFragmentController(fc);
411        fm = fc.getSupportFragmentManager();
412
413        assertAnimationsMatch(fm, R.anim.fade_in, R.anim.fade_out, 0, 0);
414
415        fm.popBackStackImmediate();
416
417        assertAnimationsMatch(fm, 0, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out);
418
419        shutdownFragmentController(fc);
420    }
421
422    /**
423     * This test confirms that as long as a parent fragment has called super.onCreate,
424     * any child fragments added, committed and with transactions executed will be brought
425     * to at least the CREATED state by the time the parent fragment receives onCreateView.
426     * This means the child fragment will have received onAttach/onCreate.
427     */
428    @Test
429    @UiThreadTest
430    public void childFragmentManagerAttach() throws Throwable {
431        FragmentController fc = FragmentController.createController(
432                new HostCallbacks(mActivityRule.getActivity()));
433        fc.attachHost(null);
434        fc.dispatchCreate();
435
436        FragmentManager fm = fc.getSupportFragmentManager();
437
438        fm.beginTransaction()
439                .add(android.R.id.content, new ChildFragmentManagerFragment())
440                .commitNow();
441
442        fc.dispatchActivityCreated();
443
444        fc.dispatchStart();
445        fc.dispatchResume();
446        fc.dispatchStop();
447        fc.dispatchReallyStop();
448        fc.dispatchDestroy();
449    }
450
451    private void assertAnimationsMatch(FragmentManager fm, int enter, int exit, int popEnter,
452            int popExit) {
453        FragmentManagerImpl fmImpl = (FragmentManagerImpl) fm;
454        BackStackRecord record = fmImpl.mBackStack.get(fmImpl.mBackStack.size() - 1);
455
456        Assert.assertEquals(enter, record.mEnterAnim);
457        Assert.assertEquals(exit, record.mExitAnim);
458        Assert.assertEquals(popEnter, record.mPopEnterAnim);
459        Assert.assertEquals(popExit, record.mPopExitAnim);
460    }
461
462    private FragmentController restartFragmentController(FragmentController fc) {
463        Parcelable savedState = shutdownFragmentController(fc);
464        return startupFragmentController(savedState);
465    }
466
467    private FragmentController startupFragmentController(Parcelable savedState) {
468        final FragmentController fc = FragmentController.createController(
469                new HostCallbacks(mActivityRule.getActivity()));
470        fc.attachHost(null);
471        fc.restoreAllState(savedState, (FragmentManagerNonConfig) null);
472        fc.dispatchCreate();
473        fc.dispatchActivityCreated();
474        fc.noteStateNotSaved();
475        fc.execPendingActions();
476        fc.doLoaderStart();
477        fc.dispatchStart();
478        fc.reportLoaderStart();
479        fc.dispatchResume();
480        fc.execPendingActions();
481        return fc;
482    }
483
484    private Parcelable shutdownFragmentController(FragmentController fc) {
485        fc.dispatchPause();
486        final Parcelable savedState = fc.saveAllState();
487        fc.dispatchStop();
488        fc.dispatchReallyStop();
489        fc.dispatchDestroy();
490        return savedState;
491    }
492
493    private void executePendingTransactions(final FragmentManager fm) throws Throwable {
494        mActivityRule.runOnUiThread(new Runnable() {
495            @Override
496            public void run() {
497                fm.executePendingTransactions();
498            }
499        });
500    }
501
502    public static class StateSaveFragment extends StrictFragment {
503        private static final String STATE_KEY = "state";
504
505        private String mSavedState;
506        private String mUnsavedState;
507
508        public StateSaveFragment() {
509        }
510
511        public StateSaveFragment(String savedState, String unsavedState) {
512            mSavedState = savedState;
513            mUnsavedState = unsavedState;
514        }
515
516        public String getSavedState() {
517            return mSavedState;
518        }
519
520        public String getUnsavedState() {
521            return mUnsavedState;
522        }
523
524        @Override
525        public void onCreate(Bundle savedInstanceState) {
526            super.onCreate(savedInstanceState);
527            if (savedInstanceState != null) {
528                mSavedState = savedInstanceState.getString(STATE_KEY);
529            }
530        }
531
532        @Override
533        public void onSaveInstanceState(Bundle outState) {
534            super.onSaveInstanceState(outState);
535            outState.putString(STATE_KEY, mSavedState);
536        }
537    }
538
539    public static class ChildFragmentManagerFragment extends StrictFragment {
540        private FragmentManager mSavedChildFragmentManager;
541
542        @Override
543        public void onAttach(Context context) {
544            super.onAttach(context);
545            mSavedChildFragmentManager = getChildFragmentManager();
546        }
547
548        @Nullable
549        @Override
550        public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
551                @Nullable Bundle savedInstanceState) {
552            assertSame("child FragmentManagers not the same instance", mSavedChildFragmentManager,
553                    getChildFragmentManager());
554            ChildFragmentManagerChildFragment child = new ChildFragmentManagerChildFragment("foo");
555            mSavedChildFragmentManager.beginTransaction()
556                    .add(child, "tag")
557                    .commitNow();
558            assertEquals("argument strings don't match", "foo", child.getString());
559            return new TextView(container.getContext());
560        }
561    }
562
563    public static class ChildFragmentManagerChildFragment extends StrictFragment {
564        private String mString;
565
566        public ChildFragmentManagerChildFragment() {
567        }
568
569        public ChildFragmentManagerChildFragment(String arg) {
570            final Bundle b = new Bundle();
571            b.putString("string", arg);
572            setArguments(b);
573        }
574
575        @Override
576        public void onAttach(Context context) {
577            super.onAttach(context);
578            mString = getArguments().getString("string", "NO VALUE");
579        }
580
581        public String getString() {
582            return mString;
583        }
584    }
585
586    static class HostCallbacks extends FragmentHostCallback<FragmentActivity> {
587        private final FragmentActivity mActivity;
588
589        public HostCallbacks(FragmentActivity activity) {
590            super(activity);
591            mActivity = activity;
592        }
593
594        @Override
595        public void onDump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
596        }
597
598        @Override
599        public boolean onShouldSaveFragmentState(Fragment fragment) {
600            return !mActivity.isFinishing();
601        }
602
603        @Override
604        public LayoutInflater onGetLayoutInflater() {
605            return mActivity.getLayoutInflater().cloneInContext(mActivity);
606        }
607
608        @Override
609        public FragmentActivity onGetHost() {
610            return mActivity;
611        }
612
613        @Override
614        public void onSupportInvalidateOptionsMenu() {
615            mActivity.supportInvalidateOptionsMenu();
616        }
617
618        @Override
619        public void onStartActivityFromFragment(Fragment fragment, Intent intent, int requestCode) {
620            mActivity.startActivityFromFragment(fragment, intent, requestCode);
621        }
622
623        @Override
624        public void onStartActivityFromFragment(
625                Fragment fragment, Intent intent, int requestCode, @Nullable Bundle options) {
626            mActivity.startActivityFromFragment(fragment, intent, requestCode, options);
627        }
628
629        @Override
630        public void onRequestPermissionsFromFragment(@NonNull Fragment fragment,
631                @NonNull String[] permissions, int requestCode) {
632            throw new UnsupportedOperationException();
633        }
634
635        @Override
636        public boolean onShouldShowRequestPermissionRationale(@NonNull String permission) {
637            return ActivityCompat.shouldShowRequestPermissionRationale(
638                    mActivity, permission);
639        }
640
641        @Override
642        public boolean onHasWindowAnimations() {
643            return mActivity.getWindow() != null;
644        }
645
646        @Override
647        public int onGetWindowAnimations() {
648            final Window w = mActivity.getWindow();
649            return (w == null) ? 0 : w.getAttributes().windowAnimations;
650        }
651
652        @Override
653        public void onAttachFragment(Fragment fragment) {
654            mActivity.onAttachFragment(fragment);
655        }
656
657        @Nullable
658        @Override
659        public View onFindViewById(int id) {
660            return mActivity.findViewById(id);
661        }
662
663        @Override
664        public boolean onHasView() {
665            final Window w = mActivity.getWindow();
666            return (w != null && w.peekDecorView() != null);
667        }
668    }
669
670    public static class SimpleFragment extends Fragment {
671        private int mLayoutId;
672        private static final String LAYOUT_ID = "layoutId";
673
674        @Override
675        public void onCreate(Bundle savedInstanceState) {
676            super.onCreate(savedInstanceState);
677            if (savedInstanceState != null) {
678                mLayoutId = savedInstanceState.getInt(LAYOUT_ID, mLayoutId);
679            }
680        }
681
682        @Override
683        public void onSaveInstanceState(Bundle outState) {
684            super.onSaveInstanceState(outState);
685            outState.putInt(LAYOUT_ID, mLayoutId);
686        }
687
688        @Override
689        public View onCreateView(LayoutInflater inflater, ViewGroup container,
690                Bundle savedInstanceState) {
691            return inflater.inflate(mLayoutId, container, false);
692        }
693
694        public static SimpleFragment create(int layoutId) {
695            SimpleFragment fragment = new SimpleFragment();
696            fragment.mLayoutId = layoutId;
697            return fragment;
698        }
699    }
700}
701