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 getContext()87 protected TestableContext getContext() { 88 return new TestableContext(InstrumentationRegistry.getContext()); 89 } 90 91 @After tearDown()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 testCreateDestroy()100 public void testCreateDestroy() { 101 mFragments.dispatchCreate(); 102 processAllMessages(); 103 destroyFragments(); 104 } 105 106 @Test testStartStop()107 public void testStartStop() { 108 mFragments.dispatchStart(); 109 processAllMessages(); 110 mFragments.dispatchStop(); 111 processAllMessages(); 112 } 113 114 @Test testResumePause()115 public void testResumePause() { 116 mFragments.dispatchResume(); 117 processAllMessages(); 118 mFragments.dispatchPause(); 119 processAllMessages(); 120 } 121 122 @Test testAttachDetach()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 testRecreate()137 public void testRecreate() { 138 mFragments.dispatchResume(); 139 processAllMessages(); 140 recreateFragment(); 141 processAllMessages(); 142 } 143 144 @Test testMultipleResumes()145 public void testMultipleResumes() { 146 mFragments.dispatchResume(); 147 processAllMessages(); 148 mFragments.dispatchStop(); 149 processAllMessages(); 150 mFragments.dispatchResume(); 151 processAllMessages(); 152 } 153 recreateFragment()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 attachFragmentToWindow()166 protected void attachFragmentToWindow() { 167 ViewUtils.attachView(mView); 168 TestableLooper.get(this).processAllMessages(); 169 } 170 detachFragmentToWindow()171 protected void detachFragmentToWindow() { 172 ViewUtils.detachView(mView); 173 TestableLooper.get(this).processAllMessages(); 174 } 175 destroyFragments()176 protected void destroyFragments() { 177 mFragments.dispatchDestroy(); 178 processAllMessages(); 179 mFragments = null; 180 } 181 processAllMessages()182 protected void processAllMessages() { 183 TestableLooper.get(this).processAllMessages(); 184 } 185 findViewById(int id)186 private View findViewById(int id) { 187 return mView.findViewById(id); 188 } 189 190 private class HostCallbacks extends FragmentHostCallback<BaseFragmentTest> { HostCallbacks()191 public HostCallbacks() { 192 super(mContext, BaseFragmentTest.this.mHandler, 0); 193 } 194 195 @Override onGetHost()196 public BaseFragmentTest onGetHost() { 197 return BaseFragmentTest.this; 198 } 199 200 @Override onDump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args)201 public void onDump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { 202 } 203 204 @Override onShouldSaveFragmentState(Fragment fragment)205 public boolean onShouldSaveFragmentState(Fragment fragment) { 206 return true; // True for now. 207 } 208 209 @Override onGetLayoutInflater()210 public LayoutInflater onGetLayoutInflater() { 211 return LayoutInflater.from(mContext); 212 } 213 214 @Override onUseFragmentManagerInflaterFactory()215 public boolean onUseFragmentManagerInflaterFactory() { 216 return true; 217 } 218 219 @Override onHasWindowAnimations()220 public boolean onHasWindowAnimations() { 221 return false; 222 } 223 224 @Override onGetWindowAnimations()225 public int onGetWindowAnimations() { 226 return 0; 227 } 228 229 @Override onAttachFragment(Fragment fragment)230 public void onAttachFragment(Fragment fragment) { 231 } 232 233 @Nullable 234 @Override onFindViewById(int id)235 public View onFindViewById(int id) { 236 return BaseFragmentTest.this.findViewById(id); 237 } 238 239 @Override onHasView()240 public boolean onHasView() { 241 return true; 242 } 243 } 244 } 245