1 /* 2 * Copyright 2018 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 package androidx.fragment.app; 17 18 import static org.junit.Assert.assertEquals; 19 20 import android.app.Activity; 21 import android.app.Instrumentation; 22 import android.os.Build; 23 import android.os.Handler; 24 import android.os.Looper; 25 import android.os.Parcelable; 26 import android.os.SystemClock; 27 import android.support.test.InstrumentationRegistry; 28 import android.support.test.rule.ActivityTestRule; 29 import android.util.Pair; 30 import android.view.ViewGroup; 31 import android.view.animation.Animation; 32 33 import androidx.fragment.app.test.FragmentTestActivity; 34 35 import java.lang.ref.WeakReference; 36 import java.util.ArrayList; 37 38 public class FragmentTestUtil { 39 private static final Runnable DO_NOTHING = new Runnable() { 40 @Override 41 public void run() { 42 } 43 }; 44 waitForExecution(final ActivityTestRule<? extends FragmentActivity> rule)45 public static void waitForExecution(final ActivityTestRule<? extends FragmentActivity> rule) { 46 // Wait for two cycles. When starting a postponed transition, it will post to 47 // the UI thread and then the execution will be added onto the queue after that. 48 // The two-cycle wait makes sure fragments have the opportunity to complete both 49 // before returning. 50 try { 51 rule.runOnUiThread(DO_NOTHING); 52 rule.runOnUiThread(DO_NOTHING); 53 } catch (Throwable throwable) { 54 throw new RuntimeException(throwable); 55 } 56 } 57 runOnUiThreadRethrow(ActivityTestRule<? extends Activity> rule, Runnable r)58 private static void runOnUiThreadRethrow(ActivityTestRule<? extends Activity> rule, 59 Runnable r) { 60 if (Looper.getMainLooper() == Looper.myLooper()) { 61 r.run(); 62 } else { 63 try { 64 rule.runOnUiThread(r); 65 } catch (Throwable t) { 66 throw new RuntimeException(t); 67 } 68 } 69 } 70 executePendingTransactions( final ActivityTestRule<? extends FragmentActivity> rule)71 public static boolean executePendingTransactions( 72 final ActivityTestRule<? extends FragmentActivity> rule) { 73 FragmentManager fragmentManager = rule.getActivity().getSupportFragmentManager(); 74 return executePendingTransactions(rule, fragmentManager); 75 } 76 executePendingTransactions( final ActivityTestRule<? extends Activity> rule, final FragmentManager fm)77 public static boolean executePendingTransactions( 78 final ActivityTestRule<? extends Activity> rule, final FragmentManager fm) { 79 final boolean[] ret = new boolean[1]; 80 runOnUiThreadRethrow(rule, new Runnable() { 81 @Override 82 public void run() { 83 ret[0] = fm.executePendingTransactions(); 84 } 85 }); 86 return ret[0]; 87 } 88 popBackStackImmediate(final ActivityTestRule<FragmentTestActivity> rule)89 public static boolean popBackStackImmediate(final ActivityTestRule<FragmentTestActivity> rule) { 90 Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); 91 final boolean[] ret = new boolean[1]; 92 instrumentation.runOnMainSync(new Runnable() { 93 @Override 94 public void run() { 95 ret[0] = rule.getActivity().getSupportFragmentManager().popBackStackImmediate(); 96 } 97 }); 98 return ret[0]; 99 } 100 popBackStackImmediate(final ActivityTestRule<FragmentTestActivity> rule, final int id, final int flags)101 public static boolean popBackStackImmediate(final ActivityTestRule<FragmentTestActivity> rule, 102 final int id, final int flags) { 103 Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); 104 final boolean[] ret = new boolean[1]; 105 instrumentation.runOnMainSync(new Runnable() { 106 @Override 107 public void run() { 108 ret[0] = rule.getActivity().getSupportFragmentManager().popBackStackImmediate(id, 109 flags); 110 } 111 }); 112 return ret[0]; 113 } 114 popBackStackImmediate(final ActivityTestRule<FragmentTestActivity> rule, final String name, final int flags)115 public static boolean popBackStackImmediate(final ActivityTestRule<FragmentTestActivity> rule, 116 final String name, final int flags) { 117 Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); 118 final boolean[] ret = new boolean[1]; 119 instrumentation.runOnMainSync(new Runnable() { 120 @Override 121 public void run() { 122 ret[0] = rule.getActivity().getSupportFragmentManager().popBackStackImmediate(name, 123 flags); 124 } 125 }); 126 return ret[0]; 127 } 128 setContentView(final ActivityTestRule<FragmentTestActivity> rule, final int layoutId)129 public static void setContentView(final ActivityTestRule<FragmentTestActivity> rule, 130 final int layoutId) { 131 Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); 132 instrumentation.runOnMainSync(new Runnable() { 133 @Override 134 public void run() { 135 rule.getActivity().setContentView(layoutId); 136 } 137 }); 138 } 139 assertChildren(ViewGroup container, Fragment... fragments)140 public static void assertChildren(ViewGroup container, Fragment... fragments) { 141 final int numFragments = fragments == null ? 0 : fragments.length; 142 assertEquals("There aren't the correct number of fragment Views in its container", 143 numFragments, container.getChildCount()); 144 for (int i = 0; i < numFragments; i++) { 145 assertEquals("Wrong Fragment View order for [" + i + "]", container.getChildAt(i), 146 fragments[i].getView()); 147 } 148 } 149 createController( ActivityTestRule<? extends FragmentActivity> rule)150 public static FragmentController createController( 151 ActivityTestRule<? extends FragmentActivity> rule) { 152 final FragmentController[] controller = new FragmentController[1]; 153 final FragmentActivity activity = rule.getActivity(); 154 runOnUiThreadRethrow(rule, new Runnable() { 155 @Override 156 public void run() { 157 Handler handler = new Handler(); 158 HostCallbacks hostCallbacks = new HostCallbacks(activity, handler, 0); 159 controller[0] = FragmentController.createController(hostCallbacks); 160 } 161 }); 162 return controller[0]; 163 } 164 resume(ActivityTestRule<? extends Activity> rule, final FragmentController fragmentController, final Pair<Parcelable, FragmentManagerNonConfig> savedState)165 public static void resume(ActivityTestRule<? extends Activity> rule, 166 final FragmentController fragmentController, 167 final Pair<Parcelable, FragmentManagerNonConfig> savedState) { 168 runOnUiThreadRethrow(rule, new Runnable() { 169 @Override 170 public void run() { 171 fragmentController.attachHost(null); 172 if (savedState != null) { 173 fragmentController.restoreAllState(savedState.first, savedState.second); 174 } 175 fragmentController.dispatchCreate(); 176 fragmentController.dispatchActivityCreated(); 177 fragmentController.noteStateNotSaved(); 178 fragmentController.execPendingActions(); 179 fragmentController.dispatchStart(); 180 fragmentController.dispatchResume(); 181 fragmentController.execPendingActions(); 182 } 183 }); 184 } 185 destroy( ActivityTestRule<? extends Activity> rule, final FragmentController fragmentController)186 public static Pair<Parcelable, FragmentManagerNonConfig> destroy( 187 ActivityTestRule<? extends Activity> rule, 188 final FragmentController fragmentController) { 189 final Pair<Parcelable, FragmentManagerNonConfig>[] result = new Pair[1]; 190 runOnUiThreadRethrow(rule, new Runnable() { 191 @Override 192 public void run() { 193 fragmentController.dispatchPause(); 194 final Parcelable savedState = fragmentController.saveAllState(); 195 final FragmentManagerNonConfig nonConfig = 196 fragmentController.retainNestedNonConfig(); 197 fragmentController.dispatchStop(); 198 fragmentController.dispatchDestroy(); 199 result[0] = Pair.create(savedState, nonConfig); 200 } 201 }); 202 return result[0]; 203 } 204 waitForAnimationEnd(long timeout, final Animation animation)205 public static boolean waitForAnimationEnd(long timeout, final Animation animation) { 206 long endTime = SystemClock.uptimeMillis() + timeout; 207 final boolean[] hasEnded = new boolean[1]; 208 Runnable check = new Runnable() { 209 @Override 210 public void run() { 211 hasEnded[0] = animation.hasEnded(); 212 } 213 }; 214 Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation(); 215 do { 216 SystemClock.sleep(10); 217 instrumentation.runOnMainSync(check); 218 } while (!hasEnded[0] && SystemClock.uptimeMillis() < endTime); 219 return hasEnded[0]; 220 } 221 222 /** 223 * Allocates until a garbage collection occurs. 224 */ forceGC()225 public static void forceGC() { 226 if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N_MR1) { 227 // The following works on O+ 228 Runtime.getRuntime().gc(); 229 Runtime.getRuntime().gc(); 230 Runtime.getRuntime().runFinalization(); 231 } else { 232 // The following works on older versions 233 for (int i = 0; i < 2; i++) { 234 // Use a random index in the list to detect the garbage collection each time because 235 // .get() may accidentally trigger a strong reference during collection. 236 ArrayList<WeakReference<byte[]>> leak = new ArrayList<>(); 237 do { 238 WeakReference<byte[]> arr = new WeakReference<byte[]>(new byte[100]); 239 leak.add(arr); 240 } while (leak.get((int) (Math.random() * leak.size())).get() != null); 241 } 242 } 243 } 244 } 245