org.robolectric.shadows.ShadowLooper Maven / Gradle / Ivy
package org.robolectric.shadows;
import static android.os.Looper.getMainLooper;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.robolectric.annotation.LooperMode.Mode.LEGACY;
import android.os.Looper;
import com.google.errorprone.annotations.InlineMe;
import com.google.errorprone.annotations.concurrent.GuardedBy;
import java.time.Duration;
import java.util.Collection;
import java.util.concurrent.TimeUnit;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.LooperMode;
import org.robolectric.annotation.Resetter;
import org.robolectric.config.ConfigurationRegistry;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.util.Scheduler;
/**
* The base shadow API class for controlling Loopers.
*
* It will delegate calls to the appropriate shadow based on the current LooperMode.
*/
@Implements(value = Looper.class, shadowPicker = ShadowLooper.Picker.class)
public abstract class ShadowLooper {
// cache for looperMode(), since this can be an expensive call.
@GuardedBy("looperModeLock")
private static LooperMode.Mode cachedLooperMode = null;
private static final Object looperModeLock = new Object();
public static void assertLooperMode(LooperMode.Mode expectedMode) {
if (looperMode() != expectedMode) {
throw new IllegalStateException("this action is not supported in " + looperMode() + " mode.");
}
}
private static ShadowLooper shadowLooper(Looper looper) {
return Shadow.extract(looper);
}
/** @deprecated Use {@code shadowOf({@link Looper#getMainLooper()})} instead. */
@Deprecated
public static ShadowLooper getShadowMainLooper() {
return shadowLooper(getMainLooper());
}
// TODO: should probably remove this
public static ShadowLooper shadowMainLooper() {
return shadowLooper(getMainLooper());
}
public static Looper getLooperForThread(Thread thread) {
if (looperMode() == LEGACY) {
return ShadowLegacyLooper.getLooperForThread(thread);
}
throw new UnsupportedOperationException(
"this action is not supported in " + looperMode() + " mode.");
}
/** Return all created loopers. */
public static Collection getAllLoopers() {
if (looperMode() == LEGACY) {
return ShadowLegacyLooper.getLoopers();
} else {
return ShadowPausedLooper.getLoopers();
}
}
/** Should not be called directly - Robolectric internal use only. */
public static void resetThreadLoopers() {
if (looperMode() == LEGACY) {
ShadowLegacyLooper.resetThreadLoopers();
return;
}
throw new UnsupportedOperationException(
"this action is not supported in " + looperMode() + " mode.");
}
/** Return the current {@link LooperMode}. */
public static LooperMode.Mode looperMode() {
synchronized (looperModeLock) {
if (cachedLooperMode == null) {
cachedLooperMode = ConfigurationRegistry.get(LooperMode.Mode.class);
}
return cachedLooperMode;
}
}
@Resetter
public static synchronized void clearLooperMode() {
synchronized (looperModeLock) {
cachedLooperMode = null;
}
}
/**
* Pauses execution of tasks posted to the ShadowLegacyLooper. This means that during tests, tasks
* sent to the looper will not execute immediately, but will be queued in a way that is similar to
* how a real looper works. These queued tasks must be executed explicitly by calling {@link
* #runToEndOftasks} or a similar method, otherwise they will not run at all before your test
* ends.
*
* @param looper the looper to pause
*/
public static void pauseLooper(Looper looper) {
shadowLooper(looper).pause();
}
/**
* Puts the shadow looper in an "unpaused" state (this is the default state). This means that
* during tests, tasks sent to the looper will execute inline, immediately, on the calling (main)
* thread instead of being queued, in a way similar to how Guava's "DirectExecutorService" works.
* This is likely not to be what you want: it will cause code to be potentially executed in a
* different order than how it would execute on the device, and if you are using certain Android
* APIs (such as view animations) that are non-reentrant, they may not work at all or do
* unpredictable things. For more information, see this discussion.
*
* @param looper the looper to pause
*/
public static void unPauseLooper(Looper looper) {
shadowLooper(looper).unPause();
}
/**
* Puts the main ShadowLegacyLooper in an "paused" state.
*
* @see #pauseLooper
*/
public static void pauseMainLooper() {
getShadowMainLooper().pause();
}
/**
* Puts the main ShadowLegacyLooper in an "unpaused" state.
*
* @see #unPauseLooper
*/
public static void unPauseMainLooper() {
getShadowMainLooper().unPause();
}
public static void idleMainLooper() {
getShadowMainLooper().idle();
}
/** @deprecated Use {@link #idleMainLooper(long, TimeUnit)}. */
@InlineMe(
replacement = "ShadowLooper.idleMainLooper(interval, MILLISECONDS)",
imports = "org.robolectric.shadows.ShadowLooper",
staticImports = "java.util.concurrent.TimeUnit.MILLISECONDS")
@Deprecated
public static void idleMainLooper(long interval) {
idleMainLooper(interval, MILLISECONDS);
}
public static void idleMainLooper(long amount, TimeUnit unit) {
getShadowMainLooper().idleFor(amount, unit);
}
public static void idleMainLooperConstantly(boolean shouldIdleConstantly) {
getShadowMainLooper().idleConstantly(shouldIdleConstantly);
}
public static void runMainLooperOneTask() {
getShadowMainLooper().runOneTask();
}
public static void runMainLooperToNextTask() {
getShadowMainLooper().runToNextTask();
}
/**
* Runs any immediately runnable tasks previously queued on the UI thread, e.g. by {@link
* android.app.Activity#runOnUiThread(Runnable)} or {@link
* android.os.AsyncTask#onPostExecute(Object)}.
*
* **Note:** calling this method does not pause or un-pause the scheduler.
*
* @see #runUiThreadTasksIncludingDelayedTasks
*/
public static void runUiThreadTasks() {
getShadowMainLooper().idle();
}
/**
* Runs all runnable tasks (pending and future) that have been queued on the UI thread. Such tasks
* may be queued by e.g. {@link android.app.Activity#runOnUiThread(Runnable)} or {@link
* android.os.AsyncTask#onPostExecute(Object)}.
*
*
**Note:** calling this method does not pause or un-pause the scheduler, however the clock is
* advanced as future tasks are run.
*
* @see #runUiThreadTasks
*/
public static void runUiThreadTasksIncludingDelayedTasks() {
getShadowMainLooper().runToEndOfTasks();
}
public abstract void quitUnchecked();
public abstract boolean hasQuit();
/** Executes all posted tasks scheduled before or at the current time. */
public abstract void idle();
/**
* Advances the system clock by the given time, then executes all posted tasks scheduled before or
* at the given time.
*/
public abstract void idleFor(long time, TimeUnit timeUnit);
/** A variant of {@link #idleFor(long, TimeUnit)} that accepts a Duration. */
@SuppressWarnings("AndroidJdkLibsChecker")
public void idleFor(Duration duration) {
idleFor(duration.toMillis(), TimeUnit.MILLISECONDS);
}
/** Returns true if there are no pending tasks scheduled to be executed before current time. */
public abstract boolean isIdle();
/** Not supported for the main Looper in {@link LooperMode.Mode.PAUSED}. */
public abstract void unPause();
public abstract boolean isPaused();
/**
* Control the paused state of the Looper.
*
*
Not supported for the main Looper in {@link LooperMode.Mode.PAUSED}.
*/
public abstract boolean setPaused(boolean shouldPause);
/** Only supported for {@link LooperMode.Mode.LEGACY}. */
public abstract void resetScheduler();
/** Causes all enqueued tasks to be discarded, and pause state to be reset */
public abstract void reset();
/**
* Returns the {@link org.robolectric.util.Scheduler} that is being used to manage the enqueued
* tasks. This scheduler is managed by the Looper's associated queue.
*
*
Only supported for {@link LooperMode.Mode.LEGACY}.
*
* @return the {@link org.robolectric.util.Scheduler} that is being used to manage the enqueued
* tasks.
*/
public abstract Scheduler getScheduler();
/**
* Runs the current task with the looper paused.
*
*
When LooperMode is PAUSED, this will execute all pending tasks scheduled before the current
* time.
*/
public abstract void runPaused(Runnable run);
/**
* Helper method to selectively call idle() only if LooperMode is PAUSED.
*
*
Intended for backwards compatibility, to avoid changing behavior for tests still using
* LEGACY LooperMode.
*/
public abstract void idleIfPaused();
/**
* Causes {@link Runnable}s that have been scheduled to run within the next {@code intervalMillis}
* milliseconds to run while advancing the scheduler's clock.
*
* @deprecated Use {@link #idleFor(Duration)}.
*/
@Deprecated
@InlineMe(
replacement = "this.idleFor(Duration.ofMillis(intervalMillis))",
imports = "java.time.Duration")
public final void idle(long intervalMillis) {
idleFor(Duration.ofMillis(intervalMillis));
}
/**
* Causes {@link Runnable}s that have been scheduled to run within the next specified amount of
* time to run while advancing the clock.
*
* @deprecated use {@link #idleFor(long, TimeUnit)}
*/
@Deprecated
@InlineMe(replacement = "this.idleFor(amount, unit)")
public final void idle(long amount, TimeUnit unit) {
idleFor(amount, unit);
}
public abstract void idleConstantly(boolean shouldIdleConstantly);
/**
* Causes all of the {@link Runnable}s that have been scheduled to run while advancing the clock
* to the start time of the last scheduled {@link Runnable}.
*/
public abstract void runToEndOfTasks();
/**
* Causes the next {@link Runnable}(s) that have been scheduled to run while advancing the clock
* to its start time. If more than one {@link Runnable} is scheduled to run at this time then they
* will all be run.
*/
public abstract void runToNextTask();
/**
* Causes only one of the next {@link Runnable}s that have been scheduled to run while advancing
* the clock to its start time. Only one {@link Runnable} will run even if more than one has been
* scheduled to run at the same time.
*/
public abstract void runOneTask();
/**
* Enqueue a task to be run later.
*
* @param runnable the task to be run
* @param delayMillis how many milliseconds into the (virtual) future to run it
* @return true if the runnable is enqueued
* @see android.os.Handler#postDelayed(Runnable,long)
* @deprecated Use a {@link android.os.Handler} instance to post to a looper.
*/
@Deprecated
public abstract boolean post(Runnable runnable, long delayMillis);
/**
* Enqueue a task to be run ahead of all other delayed tasks.
*
* @param runnable the task to be run
* @return true if the runnable is enqueued
* @see android.os.Handler#postAtFrontOfQueue(Runnable)
* @deprecated Use a {@link android.os.Handler} instance to post to a looper.
*/
@Deprecated
public abstract boolean postAtFrontOfQueue(Runnable runnable);
/**
* Pause the looper.
*
*
Has no practical effect for realistic looper, since it is always paused.
*/
public abstract void pause();
/**
* @return the scheduled time of the next posted task; Duration.ZERO if there is no currently
* scheduled task.
*/
public abstract Duration getNextScheduledTaskTime();
/**
* @return the scheduled time of the last posted task; Duration.ZERO 0 if there is no currently
* scheduled task.
*/
public abstract Duration getLastScheduledTaskTime();
public static class Picker extends LooperShadowPicker {
public Picker() {
super(ShadowLegacyLooper.class, ShadowPausedLooper.class);
}
}
}