• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package org.robolectric.shadows;
2 
3 import static android.os.Looper.getMainLooper;
4 import static java.util.concurrent.TimeUnit.MILLISECONDS;
5 import static org.robolectric.annotation.LooperMode.Mode.LEGACY;
6 
7 import android.os.Looper;
8 import com.google.errorprone.annotations.InlineMe;
9 import com.google.errorprone.annotations.concurrent.GuardedBy;
10 import java.time.Duration;
11 import java.util.Collection;
12 import java.util.concurrent.TimeUnit;
13 import org.robolectric.annotation.Implements;
14 import org.robolectric.annotation.LooperMode;
15 import org.robolectric.annotation.Resetter;
16 import org.robolectric.config.ConfigurationRegistry;
17 import org.robolectric.shadow.api.Shadow;
18 import org.robolectric.util.Scheduler;
19 
20 /**
21  * The base shadow API class for controlling Loopers.
22  *
23  * <p>It will delegate calls to the appropriate shadow based on the current LooperMode.
24  */
25 @Implements(value = Looper.class, shadowPicker = ShadowLooper.Picker.class)
26 public abstract class ShadowLooper {
27 
28   // cache for looperMode(), since this can be an expensive call.
29   @GuardedBy("looperModeLock")
30   private static LooperMode.Mode cachedLooperMode = null;
31 
32   private static final Object looperModeLock = new Object();
33 
assertLooperMode(LooperMode.Mode expectedMode)34   public static void assertLooperMode(LooperMode.Mode expectedMode) {
35     if (looperMode() != expectedMode) {
36       throw new IllegalStateException("this action is not supported in " + looperMode() + " mode.");
37     }
38   }
39 
shadowLooper(Looper looper)40   private static ShadowLooper shadowLooper(Looper looper) {
41     return Shadow.extract(looper);
42   }
43 
44   /** @deprecated Use {@code shadowOf({@link Looper#getMainLooper()})} instead. */
45   @Deprecated
getShadowMainLooper()46   public static ShadowLooper getShadowMainLooper() {
47     return shadowLooper(getMainLooper());
48   }
49 
50   // TODO: should probably remove this
shadowMainLooper()51   public static ShadowLooper shadowMainLooper() {
52     return shadowLooper(getMainLooper());
53   }
54 
getLooperForThread(Thread thread)55   public static Looper getLooperForThread(Thread thread) {
56     if (looperMode() == LEGACY) {
57       return ShadowLegacyLooper.getLooperForThread(thread);
58     }
59     throw new UnsupportedOperationException(
60         "this action is not supported in " + looperMode() + " mode.");
61   }
62 
63   /** Return all created loopers. */
getAllLoopers()64   public static Collection<Looper> getAllLoopers() {
65     if (looperMode() == LEGACY) {
66       return ShadowLegacyLooper.getLoopers();
67     } else {
68       return ShadowPausedLooper.getLoopers();
69     }
70   }
71 
72   /** Should not be called directly - Robolectric internal use only. */
resetThreadLoopers()73   public static void resetThreadLoopers() {
74     if (looperMode() == LEGACY) {
75       ShadowLegacyLooper.resetThreadLoopers();
76       return;
77     }
78     throw new UnsupportedOperationException(
79         "this action is not supported in " + looperMode() + " mode.");
80   }
81 
82   /** Return the current {@link LooperMode}. */
looperMode()83   public static LooperMode.Mode looperMode() {
84     synchronized (looperModeLock) {
85       if (cachedLooperMode == null) {
86         cachedLooperMode = ConfigurationRegistry.get(LooperMode.Mode.class);
87       }
88       return cachedLooperMode;
89     }
90   }
91 
92   @Resetter
clearLooperMode()93   public static synchronized void clearLooperMode() {
94     synchronized (looperModeLock) {
95       cachedLooperMode = null;
96     }
97   }
98 
99   /**
100    * Pauses execution of tasks posted to the ShadowLegacyLooper. This means that during tests, tasks
101    * sent to the looper will not execute immediately, but will be queued in a way that is similar to
102    * how a real looper works. These queued tasks must be executed explicitly by calling {@link
103    * #runToEndOftasks} or a similar method, otherwise they will not run at all before your test
104    * ends.
105    *
106    * @param looper the looper to pause
107    */
pauseLooper(Looper looper)108   public static void pauseLooper(Looper looper) {
109     shadowLooper(looper).pause();
110   }
111 
112   /**
113    * Puts the shadow looper in an "unpaused" state (this is the default state). This means that
114    * during tests, tasks sent to the looper will execute inline, immediately, on the calling (main)
115    * thread instead of being queued, in a way similar to how Guava's "DirectExecutorService" works.
116    * This is likely not to be what you want: it will cause code to be potentially executed in a
117    * different order than how it would execute on the device, and if you are using certain Android
118    * APIs (such as view animations) that are non-reentrant, they may not work at all or do
119    * unpredictable things. For more information, see <a
120    * href="https://github.com/robolectric/robolectric/issues/3369">this discussion</a>.
121    *
122    * @param looper the looper to pause
123    */
unPauseLooper(Looper looper)124   public static void unPauseLooper(Looper looper) {
125     shadowLooper(looper).unPause();
126   }
127 
128   /**
129    * Puts the main ShadowLegacyLooper in an "paused" state.
130    *
131    * @see #pauseLooper
132    */
pauseMainLooper()133   public static void pauseMainLooper() {
134     getShadowMainLooper().pause();
135   }
136 
137   /**
138    * Puts the main ShadowLegacyLooper in an "unpaused" state.
139    *
140    * @see #unPauseLooper
141    */
unPauseMainLooper()142   public static void unPauseMainLooper() {
143     getShadowMainLooper().unPause();
144   }
145 
idleMainLooper()146   public static void idleMainLooper() {
147     getShadowMainLooper().idle();
148   }
149 
150   /** @deprecated Use {@link #idleMainLooper(long, TimeUnit)}. */
151   @InlineMe(
152       replacement = "ShadowLooper.idleMainLooper(interval, MILLISECONDS)",
153       imports = "org.robolectric.shadows.ShadowLooper",
154       staticImports = "java.util.concurrent.TimeUnit.MILLISECONDS")
155   @Deprecated
idleMainLooper(long interval)156   public static void idleMainLooper(long interval) {
157     idleMainLooper(interval, MILLISECONDS);
158   }
159 
idleMainLooper(long amount, TimeUnit unit)160   public static void idleMainLooper(long amount, TimeUnit unit) {
161     getShadowMainLooper().idleFor(amount, unit);
162   }
163 
idleMainLooperConstantly(boolean shouldIdleConstantly)164   public static void idleMainLooperConstantly(boolean shouldIdleConstantly) {
165     getShadowMainLooper().idleConstantly(shouldIdleConstantly);
166   }
167 
runMainLooperOneTask()168   public static void runMainLooperOneTask() {
169     getShadowMainLooper().runOneTask();
170   }
171 
runMainLooperToNextTask()172   public static void runMainLooperToNextTask() {
173     getShadowMainLooper().runToNextTask();
174   }
175 
176   /**
177    * Runs any immediately runnable tasks previously queued on the UI thread, e.g. by {@link
178    * android.app.Activity#runOnUiThread(Runnable)} or {@link
179    * android.os.AsyncTask#onPostExecute(Object)}.
180    *
181    * <p>**Note:** calling this method does not pause or un-pause the scheduler.
182    *
183    * @see #runUiThreadTasksIncludingDelayedTasks
184    */
runUiThreadTasks()185   public static void runUiThreadTasks() {
186     getShadowMainLooper().idle();
187   }
188 
189   /**
190    * Runs all runnable tasks (pending and future) that have been queued on the UI thread. Such tasks
191    * may be queued by e.g. {@link android.app.Activity#runOnUiThread(Runnable)} or {@link
192    * android.os.AsyncTask#onPostExecute(Object)}.
193    *
194    * <p>**Note:** calling this method does not pause or un-pause the scheduler, however the clock is
195    * advanced as future tasks are run.
196    *
197    * @see #runUiThreadTasks
198    */
runUiThreadTasksIncludingDelayedTasks()199   public static void runUiThreadTasksIncludingDelayedTasks() {
200     getShadowMainLooper().runToEndOfTasks();
201   }
202 
quitUnchecked()203   public abstract void quitUnchecked();
204 
hasQuit()205   public abstract boolean hasQuit();
206 
207   /** Executes all posted tasks scheduled before or at the current time. */
idle()208   public abstract void idle();
209 
210   /**
211    * Advances the system clock by the given time, then executes all posted tasks scheduled before or
212    * at the given time.
213    */
idleFor(long time, TimeUnit timeUnit)214   public abstract void idleFor(long time, TimeUnit timeUnit);
215 
216   /** A variant of {@link #idleFor(long, TimeUnit)} that accepts a Duration. */
217   @SuppressWarnings("AndroidJdkLibsChecker")
idleFor(Duration duration)218   public void idleFor(Duration duration) {
219     idleFor(duration.toMillis(), TimeUnit.MILLISECONDS);
220   }
221 
222   /** Returns true if there are no pending tasks scheduled to be executed before current time. */
isIdle()223   public abstract boolean isIdle();
224 
225   /** Not supported for the main Looper in {@link LooperMode.Mode.PAUSED}. */
unPause()226   public abstract void unPause();
227 
isPaused()228   public abstract boolean isPaused();
229 
230   /**
231    * Control the paused state of the Looper.
232    *
233    * <p>Not supported for the main Looper in {@link LooperMode.Mode.PAUSED}.
234    */
setPaused(boolean shouldPause)235   public abstract boolean setPaused(boolean shouldPause);
236 
237   /** Only supported for {@link LooperMode.Mode.LEGACY}. */
resetScheduler()238   public abstract void resetScheduler();
239 
240   /** Causes all enqueued tasks to be discarded, and pause state to be reset */
reset()241   public abstract void reset();
242 
243   /**
244    * Returns the {@link org.robolectric.util.Scheduler} that is being used to manage the enqueued
245    * tasks. This scheduler is managed by the Looper's associated queue.
246    *
247    * <p>Only supported for {@link LooperMode.Mode.LEGACY}.
248    *
249    * @return the {@link org.robolectric.util.Scheduler} that is being used to manage the enqueued
250    *     tasks.
251    */
getScheduler()252   public abstract Scheduler getScheduler();
253 
254   /**
255    * Runs the current task with the looper paused.
256    *
257    * <p>When LooperMode is PAUSED, this will execute all pending tasks scheduled before the current
258    * time.
259    */
runPaused(Runnable run)260   public abstract void runPaused(Runnable run);
261 
262   /**
263    * Helper method to selectively call idle() only if LooperMode is PAUSED.
264    *
265    * <p>Intended for backwards compatibility, to avoid changing behavior for tests still using
266    * LEGACY LooperMode.
267    */
idleIfPaused()268   public abstract void idleIfPaused();
269 
270   /**
271    * Causes {@link Runnable}s that have been scheduled to run within the next {@code intervalMillis}
272    * milliseconds to run while advancing the scheduler's clock.
273    *
274    * @deprecated Use {@link #idleFor(Duration)}.
275    */
276   @Deprecated
277   @InlineMe(
278       replacement = "this.idleFor(Duration.ofMillis(intervalMillis))",
279       imports = "java.time.Duration")
idle(long intervalMillis)280   public final void idle(long intervalMillis) {
281     idleFor(Duration.ofMillis(intervalMillis));
282   }
283 
284   /**
285    * Causes {@link Runnable}s that have been scheduled to run within the next specified amount of
286    * time to run while advancing the clock.
287    *
288    * @deprecated use {@link #idleFor(long, TimeUnit)}
289    */
290   @Deprecated
291   @InlineMe(replacement = "this.idleFor(amount, unit)")
idle(long amount, TimeUnit unit)292   public final void idle(long amount, TimeUnit unit) {
293     idleFor(amount, unit);
294   }
295 
idleConstantly(boolean shouldIdleConstantly)296   public abstract void idleConstantly(boolean shouldIdleConstantly);
297 
298   /**
299    * Causes all of the {@link Runnable}s that have been scheduled to run while advancing the clock
300    * to the start time of the last scheduled {@link Runnable}.
301    */
runToEndOfTasks()302   public abstract void runToEndOfTasks();
303 
304   /**
305    * Causes the next {@link Runnable}(s) that have been scheduled to run while advancing the clock
306    * to its start time. If more than one {@link Runnable} is scheduled to run at this time then they
307    * will all be run.
308    */
runToNextTask()309   public abstract void runToNextTask();
310 
311   /**
312    * Causes only one of the next {@link Runnable}s that have been scheduled to run while advancing
313    * the clock to its start time. Only one {@link Runnable} will run even if more than one has been
314    * scheduled to run at the same time.
315    */
runOneTask()316   public abstract void runOneTask();
317 
318   /**
319    * Enqueue a task to be run later.
320    *
321    * @param runnable the task to be run
322    * @param delayMillis how many milliseconds into the (virtual) future to run it
323    * @return true if the runnable is enqueued
324    * @see android.os.Handler#postDelayed(Runnable,long)
325    * @deprecated Use a {@link android.os.Handler} instance to post to a looper.
326    */
327   @Deprecated
post(Runnable runnable, long delayMillis)328   public abstract boolean post(Runnable runnable, long delayMillis);
329 
330   /**
331    * Enqueue a task to be run ahead of all other delayed tasks.
332    *
333    * @param runnable the task to be run
334    * @return true if the runnable is enqueued
335    * @see android.os.Handler#postAtFrontOfQueue(Runnable)
336    * @deprecated Use a {@link android.os.Handler} instance to post to a looper.
337    */
338   @Deprecated
postAtFrontOfQueue(Runnable runnable)339   public abstract boolean postAtFrontOfQueue(Runnable runnable);
340 
341   /**
342    * Pause the looper.
343    *
344    * <p>Has no practical effect for realistic looper, since it is always paused.
345    */
pause()346   public abstract void pause();
347 
348   /**
349    * @return the scheduled time of the next posted task; Duration.ZERO if there is no currently
350    *     scheduled task.
351    */
getNextScheduledTaskTime()352   public abstract Duration getNextScheduledTaskTime();
353 
354   /**
355    * @return the scheduled time of the last posted task; Duration.ZERO 0 if there is no currently
356    *     scheduled task.
357    */
getLastScheduledTaskTime()358   public abstract Duration getLastScheduledTaskTime();
359 
360   public static class Picker extends LooperShadowPicker<ShadowLooper> {
361 
Picker()362     public Picker() {
363       super(ShadowLegacyLooper.class, ShadowPausedLooper.class);
364     }
365   }
366 }
367