org.robolectric.shadows.ShadowLooper Maven / Gradle / Ivy
package org.robolectric.shadows;
import android.os.Looper;
import java.util.Collections;
import java.util.Map;
import java.util.WeakHashMap;
import org.robolectric.RoboSettings;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
import org.robolectric.annotation.Resetter;
import org.robolectric.annotation.HiddenApi;
import org.robolectric.util.Scheduler;
import static org.robolectric.RuntimeEnvironment.isMainThread;
import static org.robolectric.Shadows.shadowOf;
import static org.robolectric.internal.Shadow.*;
import static org.robolectric.util.ReflectionHelpers.ClassParameter.from;
/**
* Shadow for {@link android.os.Looper} that enqueues posted {@link Runnable}s to be run
* (on this thread) later. {@code Runnable}s that are scheduled to run immediately can be
* triggered by calling {@link #idle()}.
*
* @see ShadowMessageQueue
*/
@Implements(Looper.class)
public class ShadowLooper {
// Replaced SoftThreadLocal with a WeakHashMap, because ThreadLocal make it impossible to access their contents from other
// threads, but we need to be able to access the loopers for all threads so that we can shut them down when resetThreadLoopers()
// is called. This also allows us to implement the useful getLooperForThread() method.
// Note that the main looper is handled differently and is not put in this hash, because we need to be able to
// "switch" the thread that the main looper is associated with.
private static Map loopingLoopers = Collections.synchronizedMap(new WeakHashMap());
private static Looper mainLooper;
private @RealObject Looper realObject;
boolean quit;
@Resetter
public static synchronized void resetThreadLoopers() {
// Blech. We need to keep the main looper because somebody might refer to it in a static
// field. The other loopers need to be wrapped in WeakReferences so that they are not prevented from
// being garbage collected.
if (!isMainThread()) {
throw new IllegalStateException("you should only be calling this from the main thread!");
}
synchronized (loopingLoopers) {
for (Looper looper : loopingLoopers.values()) {
synchronized (looper) {
if (!shadowOf(looper).quit) {
looper.quit();
}
}
}
}
// Because resetStaticState() is called by ParallelUniverse on startup before prepareMainLooper() is
// called, this might be null on that occasion.
if (mainLooper != null) {
shadowOf(mainLooper).reset();
}
}
@Implementation
public void __constructor__(boolean quitAllowed) {
invokeConstructor(Looper.class, realObject, from(boolean.class, quitAllowed));
if (isMainThread()) {
mainLooper = realObject;
} else {
loopingLoopers.put(Thread.currentThread(), realObject);
}
resetScheduler();
}
@Implementation
public static Looper getMainLooper() {
return mainLooper;
}
@Implementation
public static Looper myLooper() {
return getLooperForThread(Thread.currentThread());
}
@Implementation
public static void loop() {
shadowOf(Looper.myLooper()).doLoop();
}
private void doLoop() {
if (this != getShadowMainLooper()) {
synchronized (realObject) {
while (!quit) {
try {
realObject.wait();
} catch (InterruptedException ignore) {
}
}
}
}
}
@Implementation
public void quit() {
if (this == getShadowMainLooper()) throw new RuntimeException("Main thread not allowed to quit");
quitUnchecked();
}
public void quitUnchecked() {
synchronized (realObject) {
quit = true;
realObject.notifyAll();
getScheduler().reset();
}
}
@HiddenApi @Implementation
public int postSyncBarrier() {
return 1;
}
public boolean hasQuit() {
synchronized (realObject) {
return quit;
}
}
public static ShadowLooper getShadowMainLooper() {
return shadowOf(Looper.getMainLooper());
}
public static Looper getLooperForThread(Thread thread) {
return isMainThread(thread) ? mainLooper : loopingLoopers.get(thread);
}
public static void pauseLooper(Looper looper) {
shadowOf(looper).pause();
}
public static void unPauseLooper(Looper looper) {
shadowOf(looper).unPause();
}
public static void pauseMainLooper() {
pauseLooper(Looper.getMainLooper());
}
public static void unPauseMainLooper() {
unPauseLooper(Looper.getMainLooper());
}
public static void idleMainLooper() {
shadowOf(Looper.getMainLooper()).idle();
}
public static void idleMainLooper(long interval) {
getShadowMainLooper().idle(interval);
}
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();
}
/**
* Causes {@link Runnable}s that have been scheduled to run immediately to actually run. Does not advance the
* scheduler's clock;
*/
public void idle() {
getScheduler().advanceBy(0);
}
/**
* Causes {@link Runnable}s that have been scheduled to run within the next {@code intervalMillis} milliseconds to
* run while advancing the scheduler's clock.
*
* @param intervalMillis milliseconds to advance
*/
public void idle(long intervalMillis) {
getScheduler().advanceBy(intervalMillis);
}
public void idleConstantly(boolean shouldIdleConstantly) {
getScheduler().idleConstantly(shouldIdleConstantly);
}
/**
* Causes all of the {@link Runnable}s that have been scheduled to run while advancing the scheduler's clock to the
* start time of the last scheduled {@link Runnable}.
*/
public void runToEndOfTasks() {
getScheduler().advanceToLastPostedRunnable();
}
/**
* Causes the next {@link Runnable}(s) that have been scheduled to run while advancing the scheduler's 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 void runToNextTask() {
getScheduler().advanceToNextPostedRunnable();
}
/**
* Causes only one of the next {@link Runnable}s that have been scheduled to run while advancing the scheduler's
* clock to its start time. Only one {@link Runnable} will run even if more than one has ben scheduled to run at the
* same time.
*/
public void runOneTask() {
getScheduler().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 boolean post(Runnable runnable, long delayMillis) {
if (!quit) {
getScheduler().postDelayed(runnable, delayMillis);
return true;
} else {
return false;
}
}
/**
* 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 boolean postAtFrontOfQueue(Runnable runnable) {
if (!quit) {
getScheduler().postAtFrontOfQueue(runnable);
return true;
} else {
return false;
}
}
public void pause() {
getScheduler().pause();
}
public void unPause() {
getScheduler().unPause();
}
public boolean isPaused() {
return getScheduler().isPaused();
}
public boolean setPaused(boolean shouldPause) {
boolean wasPaused = isPaused();
if (shouldPause) {
pause();
} else {
unPause();
}
return wasPaused;
}
public void resetScheduler() {
ShadowMessageQueue sQueue = shadowOf(realObject.getQueue());
if (this == getShadowMainLooper() || RoboSettings.isUseGlobalScheduler()) {
sQueue.setScheduler(RuntimeEnvironment.getMasterScheduler());
} else {
sQueue.setScheduler(new Scheduler());
}
}
/**
* Causes all enqueued tasks to be discarded, and pause state to be reset
*/
public void reset() {
shadowOf(realObject.getQueue()).reset();
resetScheduler();
quit = false;
}
/**
* 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.
*
* @return the {@link org.robolectric.util.Scheduler} that is being used to manage the enqueued tasks.
*/
public Scheduler getScheduler() {
return shadowOf(realObject.getQueue()).getScheduler();
}
public void runPaused(Runnable r) {
boolean wasPaused = setPaused(true);
try {
r.run();
} finally {
if (!wasPaused) unPause();
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy