1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5 * except in compliance with the License. You may obtain a copy of the License at
6 *
7 *      http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the
10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11 * KIND, either express or implied. See the License for the specific language governing
12 * permissions and limitations under the License.
13 */
14
15package android.testing;
16
17import static org.junit.Assert.assertNotNull;
18
19import android.annotation.Nullable;
20import android.app.Fragment;
21import android.app.FragmentController;
22import android.app.FragmentHostCallback;
23import android.app.FragmentManagerNonConfig;
24import android.graphics.PixelFormat;
25import android.os.Handler;
26import android.os.Parcelable;
27import android.support.test.InstrumentationRegistry;
28import android.view.LayoutInflater;
29import android.view.View;
30import android.view.WindowManager;
31import android.view.WindowManager.LayoutParams;
32import android.widget.FrameLayout;
33
34import org.junit.After;
35import org.junit.Before;
36import org.junit.Rule;
37import org.junit.Test;
38
39import java.io.FileDescriptor;
40import java.io.PrintWriter;
41
42/**
43 * Base class for fragment class tests.  Just adding one for any fragment will push it through
44 * general lifecycle events and ensure no basic leaks are happening.  This class also implements
45 * the host for subclasses, so they can push it into desired states and do any unit testing
46 * required.
47 */
48public abstract class BaseFragmentTest {
49
50    private static final int VIEW_ID = 42;
51    private final Class<? extends Fragment> mCls;
52    private Handler mHandler;
53    protected FrameLayout mView;
54    protected FragmentController mFragments;
55    protected Fragment mFragment;
56
57    @Rule
58    public final TestableContext mContext = getContext();
59
60    public BaseFragmentTest(Class<? extends Fragment> cls) {
61        mCls = cls;
62    }
63
64    protected void createRootView() {
65        mView = new FrameLayout(mContext);
66    }
67
68    @Before
69    public void setupFragment() throws Exception {
70        createRootView();
71        mView.setId(VIEW_ID);
72
73        assertNotNull("BaseFragmentTest must be tagged with @RunWithLooper",
74                TestableLooper.get(this));
75        TestableLooper.get(this).runWithLooper(() -> {
76            mHandler = new Handler();
77
78            mFragment = mCls.newInstance();
79            mFragments = FragmentController.createController(new HostCallbacks());
80            mFragments.attachHost(null);
81            mFragments.getFragmentManager().beginTransaction()
82                    .replace(VIEW_ID, mFragment)
83                    .commit();
84        });
85    }
86
87    protected TestableContext getContext() {
88        return new TestableContext(InstrumentationRegistry.getContext());
89    }
90
91    @After
92    public void tearDown() throws Exception {
93        if (mFragments != null) {
94            // Set mFragments to null to let it know not to destroy.
95            TestableLooper.get(this).runWithLooper(() -> mFragments.dispatchDestroy());
96        }
97    }
98
99    @Test
100    public void testCreateDestroy() {
101        mFragments.dispatchCreate();
102        processAllMessages();
103        destroyFragments();
104    }
105
106    @Test
107    public void testStartStop() {
108        mFragments.dispatchStart();
109        processAllMessages();
110        mFragments.dispatchStop();
111        processAllMessages();
112    }
113
114    @Test
115    public void testResumePause() {
116        mFragments.dispatchResume();
117        processAllMessages();
118        mFragments.dispatchPause();
119        processAllMessages();
120    }
121
122    @Test
123    public void testAttachDetach() {
124        WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
125                LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT,
126                LayoutParams.TYPE_SYSTEM_ALERT,
127                0, PixelFormat.TRANSLUCENT);
128        mFragments.dispatchResume();
129        processAllMessages();
130        attachFragmentToWindow();
131        detachFragmentToWindow();
132        mFragments.dispatchPause();
133        processAllMessages();
134    }
135
136    @Test
137    public void testRecreate() {
138        mFragments.dispatchResume();
139        processAllMessages();
140        recreateFragment();
141        processAllMessages();
142    }
143
144    @Test
145    public void testMultipleResumes() {
146        mFragments.dispatchResume();
147        processAllMessages();
148        mFragments.dispatchStop();
149        processAllMessages();
150        mFragments.dispatchResume();
151        processAllMessages();
152    }
153
154    protected void recreateFragment() {
155        mFragments.dispatchPause();
156        Parcelable p = mFragments.saveAllState();
157        mFragments.dispatchDestroy();
158
159        mFragments = FragmentController.createController(new HostCallbacks());
160        mFragments.attachHost(null);
161        mFragments.restoreAllState(p, (FragmentManagerNonConfig) null);
162        mFragments.dispatchResume();
163        mFragment = mFragments.getFragmentManager().findFragmentById(VIEW_ID);
164    }
165
166    protected void attachFragmentToWindow() {
167        ViewUtils.attachView(mView);
168        TestableLooper.get(this).processAllMessages();
169    }
170
171    protected void detachFragmentToWindow() {
172        ViewUtils.detachView(mView);
173        TestableLooper.get(this).processAllMessages();
174    }
175
176    protected void destroyFragments() {
177        mFragments.dispatchDestroy();
178        processAllMessages();
179        mFragments = null;
180    }
181
182    protected void processAllMessages() {
183        TestableLooper.get(this).processAllMessages();
184    }
185
186    private View findViewById(int id) {
187        return mView.findViewById(id);
188    }
189
190    private class HostCallbacks extends FragmentHostCallback<BaseFragmentTest> {
191        public HostCallbacks() {
192            super(mContext, BaseFragmentTest.this.mHandler, 0);
193        }
194
195        @Override
196        public BaseFragmentTest onGetHost() {
197            return BaseFragmentTest.this;
198        }
199
200        @Override
201        public void onDump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
202        }
203
204        @Override
205        public boolean onShouldSaveFragmentState(Fragment fragment) {
206            return true; // True for now.
207        }
208
209        @Override
210        public LayoutInflater onGetLayoutInflater() {
211            return LayoutInflater.from(mContext);
212        }
213
214        @Override
215        public boolean onUseFragmentManagerInflaterFactory() {
216            return true;
217        }
218
219        @Override
220        public boolean onHasWindowAnimations() {
221            return false;
222        }
223
224        @Override
225        public int onGetWindowAnimations() {
226            return 0;
227        }
228
229        @Override
230        public void onAttachFragment(Fragment fragment) {
231        }
232
233        @Nullable
234        @Override
235        public View onFindViewById(int id) {
236            return BaseFragmentTest.this.findViewById(id);
237        }
238
239        @Override
240        public boolean onHasView() {
241            return true;
242        }
243    }
244}
245