org.robolectric.shadows.ShadowLegacyLooper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of shadows-framework Show documentation
Show all versions of shadows-framework Show documentation
An alternative Android testing framework.
package org.robolectric.shadows;
import static android.os.Build.VERSION_CODES.JELLY_BEAN_MR2;
import static org.robolectric.RuntimeEnvironment.isMainThread;
import static org.robolectric.shadow.api.Shadow.invokeConstructor;
import static org.robolectric.util.ReflectionHelpers.ClassParameter.from;
import android.os.Looper;
import android.os.MessageQueue;
import java.time.Duration;
import java.util.Collections;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.concurrent.TimeUnit;
import org.robolectric.RoboSettings;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.LooperMode;
import org.robolectric.annotation.RealObject;
import org.robolectric.annotation.Resetter;
import org.robolectric.shadow.api.Shadow;
import org.robolectric.util.Scheduler;
/**
* The shadow Looper implementation for {@link LooperMode.Mode.LEGACY}.
*
* Robolectric 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(value = Looper.class, isInAndroidSdk = false)
@SuppressWarnings("SynchronizeOnNonFinalField")
public class ShadowLegacyLooper extends 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() {
if (looperMode() == LooperMode.Mode.PAUSED) {
// ignore if realistic looper
return;
}
// 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();
} else {
// Reset the schedulers of all loopers. This prevents un-run tasks queued up in static
// background handlers from leaking to subsequent tests.
shadowOf(looper).getScheduler().reset();
shadowOf(looper.getQueue()).reset();
}
}
}
}
// Because resetStaticState() is called by AndroidTestEnvironment on startup before
// prepareMainLooper() is called, this might be null on that occasion.
if (mainLooper != null) {
shadowOf(mainLooper).reset();
}
}
@Implementation
protected void __constructor__(boolean quitAllowed) {
invokeConstructor(Looper.class, realObject, from(boolean.class, quitAllowed));
if (isMainThread()) {
mainLooper = realObject;
} else {
loopingLoopers.put(Thread.currentThread(), realObject);
}
resetScheduler();
}
@Implementation
protected static Looper getMainLooper() {
return mainLooper;
}
@Implementation
protected static Looper myLooper() {
return getLooperForThread(Thread.currentThread());
}
@Implementation
protected static void loop() {
shadowOf(Looper.myLooper()).doLoop();
}
private void doLoop() {
if (realObject != Looper.getMainLooper()) {
synchronized (realObject) {
while (!quit) {
try {
realObject.wait();
} catch (InterruptedException ignore) {
}
}
}
}
}
@Implementation
protected void quit() {
if (realObject == Looper.getMainLooper()) throw new RuntimeException("Main thread not allowed to quit");
quitUnchecked();
}
@Implementation(minSdk = JELLY_BEAN_MR2)
protected void quitSafely() {
quit();
}
@Override
public void quitUnchecked() {
synchronized (realObject) {
quit = true;
realObject.notifyAll();
getScheduler().reset();
shadowOf(realObject.getQueue()).reset();
}
}
@Override
public boolean hasQuit() {
synchronized (realObject) {
return quit;
}
}
public static Looper getLooperForThread(Thread thread) {
return isMainThread(thread) ? mainLooper : loopingLoopers.get(thread);
}
@Override
public void idle() {
idle(0, TimeUnit.MILLISECONDS);
}
@Override
public void idleFor(long time, TimeUnit timeUnit) {
getScheduler().advanceBy(time, timeUnit);
}
@Override
public boolean isIdle() {
return !getScheduler().areAnyRunnable();
}
@Override
public void idleIfPaused() {
// ignore
}
/**
* 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 #idle(long, TimeUnit)}.
*/
@Override
@Deprecated
public void idle(long intervalMillis) {
idle(intervalMillis, TimeUnit.MILLISECONDS);
}
@Override
public void idle(long amount, TimeUnit unit) {
idleFor(amount, unit);
}
@Override
public void idleConstantly(boolean shouldIdleConstantly) {
getScheduler().idleConstantly(shouldIdleConstantly);
}
@Override
public void runToEndOfTasks() {
getScheduler().advanceToLastPostedRunnable();
}
@Override
public void runToNextTask() {
getScheduler().advanceToNextPostedRunnable();
}
@Override
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.
*/
@Override
@Deprecated
public boolean post(Runnable runnable, long delayMillis) {
if (!quit) {
getScheduler().postDelayed(runnable, delayMillis, TimeUnit.MILLISECONDS);
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.
*/
@Override
@Deprecated
public boolean postAtFrontOfQueue(Runnable runnable) {
if (!quit) {
getScheduler().postAtFrontOfQueue(runnable);
return true;
} else {
return false;
}
}
@Override
public void pause() {
getScheduler().pause();
}
@Override
public Duration getNextScheduledTaskTime() {
return getScheduler().getNextScheduledTaskTime();
}
@Override
public Duration getLastScheduledTaskTime() {
return getScheduler().getLastScheduledTaskTime();
}
@Override
public void unPause() {
getScheduler().unPause();
}
@Override
public boolean isPaused() {
return getScheduler().isPaused();
}
@Override
public boolean setPaused(boolean shouldPause) {
boolean wasPaused = isPaused();
if (shouldPause) {
pause();
} else {
unPause();
}
return wasPaused;
}
@Override
public void resetScheduler() {
ShadowMessageQueue shadowMessageQueue = shadowOf(realObject.getQueue());
if (realObject == Looper.getMainLooper() || RoboSettings.isUseGlobalScheduler()) {
shadowMessageQueue.setScheduler(RuntimeEnvironment.getMasterScheduler());
} else {
shadowMessageQueue.setScheduler(new Scheduler());
}
}
/**
* Causes all enqueued tasks to be discarded, and pause state to be reset
*/
@Override
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.
*/
@Override
public Scheduler getScheduler() {
return shadowOf(realObject.getQueue()).getScheduler();
}
@Override
public void runPaused(Runnable r) {
boolean wasPaused = setPaused(true);
try {
r.run();
} finally {
if (!wasPaused) unPause();
}
}
private static ShadowLegacyLooper shadowOf(Looper looper) {
return Shadow.extract(looper);
}
private static ShadowMessageQueue shadowOf(MessageQueue mq) {
return Shadow.extract(mq);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy