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