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