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 15 package android.testing; 16 17 import static org.junit.Assert.assertNotNull; 18 19 import android.annotation.Nullable; 20 import android.app.Fragment; 21 import android.app.FragmentController; 22 import android.app.FragmentHostCallback; 23 import android.app.FragmentManagerNonConfig; 24 import android.graphics.PixelFormat; 25 import android.os.Handler; 26 import android.os.Parcelable; 27 import android.support.test.InstrumentationRegistry; 28 import android.view.LayoutInflater; 29 import android.view.View; 30 import android.view.WindowManager; 31 import android.view.WindowManager.LayoutParams; 32 import android.widget.FrameLayout; 33 34 import org.junit.After; 35 import org.junit.Before; 36 import org.junit.Rule; 37 import org.junit.Test; 38 39 import java.io.FileDescriptor; 40 import 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 */ 48 public 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 BaseFragmentTest(Class<? extends Fragment> cls)60 public BaseFragmentTest(Class<? extends Fragment> cls) { 61 mCls = cls; 62 } 63 createRootView()64 protected void createRootView() { 65 mView = new FrameLayout(mContext); 66 } 67 68 @Before setupFragment()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 /** 88 * Allows tests to sub-class TestableContext if they want to provide any extended functionality 89 * or provide a {@link LeakCheck} to the TestableContext upon instantiation. 90 */ getContext()91 protected TestableContext getContext() { 92 return new TestableContext(InstrumentationRegistry.getContext()); 93 } 94 95 @After tearDown()96 public void tearDown() throws Exception { 97 if (mFragments != null) { 98 // Set mFragments to null to let it know not to destroy. 99 TestableLooper.get(this).runWithLooper(() -> mFragments.dispatchDestroy()); 100 } 101 } 102 103 @Test testCreateDestroy()104 public void testCreateDestroy() { 105 mFragments.dispatchCreate(); 106 processAllMessages(); 107 destroyFragments(); 108 } 109 110 @Test testStartStop()111 public void testStartStop() { 112 mFragments.dispatchStart(); 113 processAllMessages(); 114 mFragments.dispatchStop(); 115 processAllMessages(); 116 } 117 118 @Test testResumePause()119 public void testResumePause() { 120 mFragments.dispatchResume(); 121 processAllMessages(); 122 mFragments.dispatchPause(); 123 processAllMessages(); 124 } 125 126 @Test testAttachDetach()127 public void testAttachDetach() { 128 WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 129 LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, 130 LayoutParams.TYPE_SYSTEM_ALERT, 131 0, PixelFormat.TRANSLUCENT); 132 mFragments.dispatchResume(); 133 processAllMessages(); 134 attachFragmentToWindow(); 135 detachFragmentToWindow(); 136 mFragments.dispatchPause(); 137 processAllMessages(); 138 } 139 140 @Test testRecreate()141 public void testRecreate() { 142 mFragments.dispatchResume(); 143 processAllMessages(); 144 recreateFragment(); 145 processAllMessages(); 146 } 147 148 @Test testMultipleResumes()149 public void testMultipleResumes() { 150 mFragments.dispatchResume(); 151 processAllMessages(); 152 mFragments.dispatchStop(); 153 processAllMessages(); 154 mFragments.dispatchResume(); 155 processAllMessages(); 156 } 157 recreateFragment()158 protected void recreateFragment() { 159 mFragments.dispatchPause(); 160 Parcelable p = mFragments.saveAllState(); 161 mFragments.dispatchDestroy(); 162 163 mFragments = FragmentController.createController(new HostCallbacks()); 164 mFragments.attachHost(null); 165 mFragments.restoreAllState(p, (FragmentManagerNonConfig) null); 166 mFragments.dispatchResume(); 167 mFragment = mFragments.getFragmentManager().findFragmentById(VIEW_ID); 168 } 169 attachFragmentToWindow()170 protected void attachFragmentToWindow() { 171 ViewUtils.attachView(mView); 172 TestableLooper.get(this).processAllMessages(); 173 } 174 detachFragmentToWindow()175 protected void detachFragmentToWindow() { 176 ViewUtils.detachView(mView); 177 TestableLooper.get(this).processAllMessages(); 178 } 179 destroyFragments()180 protected void destroyFragments() { 181 mFragments.dispatchDestroy(); 182 processAllMessages(); 183 mFragments = null; 184 } 185 processAllMessages()186 protected void processAllMessages() { 187 TestableLooper.get(this).processAllMessages(); 188 } 189 findViewById(int id)190 private View findViewById(int id) { 191 return mView.findViewById(id); 192 } 193 194 private class HostCallbacks extends FragmentHostCallback<BaseFragmentTest> { HostCallbacks()195 public HostCallbacks() { 196 super(mContext, BaseFragmentTest.this.mHandler, 0); 197 } 198 199 @Override onGetHost()200 public BaseFragmentTest onGetHost() { 201 return BaseFragmentTest.this; 202 } 203 204 @Override onDump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)205 public void onDump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 206 } 207 208 @Override onShouldSaveFragmentState(Fragment fragment)209 public boolean onShouldSaveFragmentState(Fragment fragment) { 210 return true; // True for now. 211 } 212 213 @Override onGetLayoutInflater()214 public LayoutInflater onGetLayoutInflater() { 215 return LayoutInflater.from(mContext); 216 } 217 218 @Override onUseFragmentManagerInflaterFactory()219 public boolean onUseFragmentManagerInflaterFactory() { 220 return true; 221 } 222 223 @Override onHasWindowAnimations()224 public boolean onHasWindowAnimations() { 225 return false; 226 } 227 228 @Override onGetWindowAnimations()229 public int onGetWindowAnimations() { 230 return 0; 231 } 232 233 @Override onAttachFragment(Fragment fragment)234 public void onAttachFragment(Fragment fragment) { 235 } 236 237 @Nullable 238 @Override onFindViewById(int id)239 public View onFindViewById(int id) { 240 return BaseFragmentTest.this.findViewById(id); 241 } 242 243 @Override onHasView()244 public boolean onHasView() { 245 return true; 246 } 247 } 248 } 249