blob: 341e0b893cd4ee98f750e6fc797369a3e6ddc00e [file] [log] [blame]
package org.robolectric.shadows;
import android.os.Looper;
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.ReflectionHelpers;
import org.robolectric.util.Scheduler;
import org.robolectric.util.SoftThreadLocal;
import static org.robolectric.Shadows.shadowOf;
/**
* Shadow for {@code 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()}
* todo: provide better support for advancing the clock and running queued tasks
*/
@SuppressWarnings({"UnusedDeclaration"})
@Implements(Looper.class)
public class ShadowLooper {
private static final Thread MAIN_THREAD = Thread.currentThread();
private static SoftThreadLocal<Looper> looperForThread = makeThreadLocalLoopers();
private Scheduler scheduler = new Scheduler();
private Thread myThread = Thread.currentThread();
private @RealObject Looper realObject;
boolean quit;
private static SoftThreadLocal<Looper> makeThreadLocalLoopers() {
return new SoftThreadLocal<Looper>() {
@Override protected Looper create() {
return createLooper();
}
};
}
private static Looper createLooper() {
return ReflectionHelpers.callConstructorReflectively(Looper.class);
}
@Resetter
public static synchronized void resetThreadLoopers() {
// Blech. We need to share the main looper because somebody might refer to it in a static
// field. We also need to keep it in a soft reference so we don't max out permgen.
if (Thread.currentThread() != MAIN_THREAD) {
throw new RuntimeException("you should only be calling this from the main thread!");
}
Looper mainLooper = looperForThread.get();
looperForThread = makeThreadLocalLoopers();
looperForThread.set(mainLooper);
shadowOf(mainLooper).reset();
}
@Implementation
public static Looper getMainLooper() {
ShadowApplication shadowApplication = ShadowApplication.getInstance();
if ((shadowApplication == null) && (Thread.currentThread() == MAIN_THREAD)) {
Looper mainLooper = looperForThread.get();
return mainLooper;
} else {
// might still throw NullPointerException
// better than returning null because this fails early.
return shadowApplication.getMainLooper();
}
}
@Implementation
public static void loop() {
shadowOf(myLooper()).doLoop();
}
@Implementation
public static synchronized Looper myLooper() {
return looperForThread.get();
}
public static Scheduler getUiThreadScheduler() {
return shadowOf(Looper.getMainLooper()).getScheduler();
}
@HiddenApi
public void __constructor__() {
}
private void doLoop() {
if (this != shadowOf(getMainLooper())) {
synchronized (realObject) {
while (!quit) {
try {
realObject.wait();
} catch (InterruptedException ignore) {
}
}
}
}
}
@Implementation
public void quit() {
if (this == shadowOf(getMainLooper())) throw new RuntimeException("Main thread not allowed to quit");
synchronized (realObject) {
quit = true;
scheduler.reset();
realObject.notifyAll();
}
}
@Implementation
public Thread getThread() {
return myThread;
}
@HiddenApi @Implementation
public int postSyncBarrier() {
return 1;
}
@HiddenApi @Implementation
public void removeSyncBarrier(int token) {
}
public boolean hasQuit() {
synchronized (realObject) {
return quit;
}
}
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(long interval) {
shadowOf(Looper.getMainLooper()).idle(interval);
}
public static void idleMainLooperConstantly(boolean shouldIdleConstantly) {
shadowOf(Looper.getMainLooper()).idleConstantly(shouldIdleConstantly);
}
/**
* 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)}.
* <p/>
* <p/>
* Note: calling this method does not pause or un-pause the scheduler.
*/
public static void runUiThreadTasks() {
getUiThreadScheduler().advanceBy(0);
}
public static void runUiThreadTasksIncludingDelayedTasks() {
getUiThreadScheduler().advanceToLastPostedRunnable();
}
/**
* Causes {@link Runnable}s that have been scheduled to run immediately to actually run. Does not advance the
* scheduler's clock;
*/
public void idle() {
scheduler.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) {
scheduler.advanceBy(intervalMillis);
}
public void idleConstantly(boolean shouldIdleConstantly) {
scheduler.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() {
scheduler.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() {
scheduler.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() {
scheduler.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
*/
public boolean post(Runnable runnable, long delayMillis) {
if (!quit) {
scheduler.postDelayed(runnable, delayMillis);
return true;
} else {
return false;
}
}
public boolean postAtFrontOfQueue(Runnable runnable) {
if (!quit) {
scheduler.postAtFrontOfQueue(runnable);
return true;
} else {
return false;
}
}
public void pause() {
scheduler.pause();
}
public void unPause() {
scheduler.unPause();
}
public boolean isPaused() {
return scheduler.isPaused();
}
public boolean setPaused(boolean shouldPause) {
boolean wasPaused = isPaused();
if (shouldPause) {
pause();
} else {
unPause();
}
return wasPaused;
}
/**
* Causes all enqueued tasks to be discarded, and pause state to be reset
*/
public void reset() {
scheduler = new Scheduler();
quit = false;
}
/**
* Returns the {@link org.robolectric.util.Scheduler} that is being used to manage the enqueued tasks.
*
* @return the {@link org.robolectric.util.Scheduler} that is being used to manage the enqueued tasks.
*/
public Scheduler getScheduler() {
return scheduler;
}
public void runPaused(Runnable r) {
boolean wasPaused = setPaused(true);
try {
r.run();
} finally {
if (!wasPaused) unPause();
}
}
}