blob: 8f4fb53348f1af6ece01df4c2c7031a05e47b439 [file] [log] [blame]
package org.robolectric.shadows;
import static android.os.Build.VERSION_CODES.KITKAT_WATCH;
import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static org.robolectric.RuntimeEnvironment.getApiLevel;
import static org.robolectric.Shadows.shadowOf;
import static org.robolectric.shadow.api.Shadow.directlyOn;
import static org.robolectric.util.ReflectionHelpers.ClassParameter.from;
import static org.robolectric.util.ReflectionHelpers.callInstanceMethod;
import static org.robolectric.util.ReflectionHelpers.getField;
import static org.robolectric.util.ReflectionHelpers.setField;
import android.os.Handler;
import android.os.Message;
import android.os.MessageQueue;
import org.robolectric.annotation.HiddenApi;
import org.robolectric.annotation.Implementation;
import org.robolectric.annotation.Implements;
import org.robolectric.annotation.RealObject;
import org.robolectric.util.Scheduler;
/**
* Robolectric puts {@link android.os.Message}s into the scheduler queue instead of sending
* them to be handled on a separate thread. {@link android.os.Message}s that are scheduled to
* be dispatched can be triggered by calling {@link ShadowLooper#idleMainLooper}.
*
* @see ShadowLooper
*/
@Implements(MessageQueue.class)
public class ShadowMessageQueue {
@RealObject
private MessageQueue realQueue;
private Scheduler scheduler;
// Stub out the native peer - scheduling
// is handled by the Scheduler class which is user-driven
// rather than automatic.
@HiddenApi
@Implementation
public static Number nativeInit() {
return 1;
}
@HiddenApi
@Implementation(maxSdk = KITKAT_WATCH)
public static void nativeDestroy(int ptr) {
nativeDestroy((long) ptr);
}
@Implementation(minSdk = LOLLIPOP)
public static void nativeDestroy(long ptr) {
}
@HiddenApi
@Implementation(maxSdk = KITKAT_WATCH)
public static void nativePollOnce(int ptr, int timeoutMillis) {
nativePollOnce((long) ptr, timeoutMillis);
}
@Implementation(minSdk = LOLLIPOP)
public static void nativePollOnce(long ptr, int timeoutMillis) {
throw new AssertionError("Should not be called");
}
@HiddenApi
@Implementation(maxSdk = KITKAT_WATCH)
public static void nativeWake(int ptr) {
nativeWake((long) ptr);
}
@Implementation(minSdk = LOLLIPOP)
public static void nativeWake(long ptr) {
throw new AssertionError("Should not be called");
}
@HiddenApi
@Implementation(maxSdk = KITKAT_WATCH)
public static boolean nativeIsIdling(int ptr) {
return nativeIsIdling((long) ptr);
}
@Implementation(minSdk = LOLLIPOP)
public static boolean nativeIsIdling(long ptr) {
return false;
}
public Scheduler getScheduler() {
return scheduler;
}
public void setScheduler(Scheduler scheduler) {
this.scheduler = scheduler;
}
public Message getHead() {
return getField(realQueue, "mMessages");
}
public void setHead(Message msg) {
setField(realQueue, "mMessages", msg);
}
public void reset() {
setHead(null);
}
@Implementation
@SuppressWarnings("SynchronizeOnNonFinalField")
public boolean enqueueMessage(final Message msg, long when) {
final boolean retval = directlyOn(realQueue, MessageQueue.class, "enqueueMessage", from(Message.class, msg), from(long.class, when));
if (retval) {
final Runnable callback = new Runnable() {
@Override
public void run() {
synchronized (realQueue) {
Message m = getHead();
if (m == null) {
return;
}
Message n = shadowOf(m).getNext();
if (m == msg) {
setHead(n);
} else {
while (n != null) {
if (n == msg) {
n = shadowOf(n).getNext();
shadowOf(m).setNext(n);
break;
}
m = n;
n = shadowOf(m).getNext();
}
}
}
dispatchMessage(msg);
}
};
shadowOf(msg).setScheduledRunnable(callback);
if (when == 0) {
scheduler.postAtFrontOfQueue(callback);
} else {
scheduler.postDelayed(callback, when - scheduler.getCurrentTime());
}
}
return retval;
}
@HiddenApi
@Implementation
public void removeSyncBarrier(int token) {
}
private static void dispatchMessage(Message msg) {
final Handler target = msg.getTarget();
shadowOf(msg).setNext(null);
// If target is null it means the message has been removed
// from the queue prior to being dispatched by the scheduler.
if (target != null) {
callInstanceMethod(msg, "markInUse");
target.dispatchMessage(msg);
if (getApiLevel() >= LOLLIPOP) {
callInstanceMethod(msg, "recycleUnchecked");
} else {
callInstanceMethod(msg, "recycle");
}
}
}
}