package org.robolectric.shadows;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.Robolectric;
import org.robolectric.TestRunners;
import org.robolectric.util.ReflectionHelpers;
import org.robolectric.util.Scheduler;
import org.robolectric.util.TestRunnable;
import java.util.ArrayList;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.robolectric.Shadows.shadowOf;
import static org.robolectric.util.ReflectionHelpers.ClassParameter.from;
@RunWith(TestRunners.MultiApiSelfTest.class)
public class ShadowHandlerTest {
private List<String> transcript;
TestRunnable scratchRunnable = new TestRunnable();
private Handler.Callback callback = new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
hasHandlerCallbackHandledMessage = true;
return false;
}
};
private Boolean hasHandlerCallbackHandledMessage = false;
@Before
public void setUp() throws Exception {
transcript = new ArrayList<>();
}
@Test
public void testInsertsRunnablesBasedOnLooper() throws Exception {
Looper looper = newLooper(false);
Handler handler1 = new Handler(looper);
handler1.post(new Say("first thing"));
Handler handler2 = new Handler(looper);
handler2.post(new Say("second thing"));
shadowOf(looper).idle();
assertThat(transcript).containsExactly("first thing", "second thing");
}
@Test
public void testDefaultConstructorUsesDefaultLooper() throws Exception {
Handler handler1 = new Handler();
handler1.post(new Say("first thing"));
Handler handler2 = new Handler(Looper.myLooper());
handler2.post(new Say("second thing"));
shadowOf(Looper.myLooper()).idle();
assertThat(transcript).containsExactly("first thing", "second thing");
}
private static Looper newLooper(boolean canQuit) {
return ReflectionHelpers.callConstructor(Looper.class, from(boolean.class, canQuit));
}
@Test
public void testDifferentLoopersGetDifferentQueues() throws Exception {
Looper looper1 = newLooper(true);
ShadowLooper.pauseLooper(looper1);
Looper looper2 = newLooper(true);
ShadowLooper.pauseLooper(looper2);
// Make sure looper has a different scheduler to the first
shadowOf(looper2.getQueue()).setScheduler(new Scheduler());
Handler handler1 = new Handler(looper1);
handler1.post(new Say("first thing"));
Handler handler2 = new Handler(looper2);
handler2.post(new Say("second thing"));
shadowOf(looper2).idle();
assertThat(transcript).containsExactly("second thing");
}
@Test
public void shouldCallProvidedHandlerCallback() {
Handler handler = new Handler(callback);
handler.sendMessage(new Message());
assertTrue(hasHandlerCallbackHandledMessage);
}
@Test
public void testPostAndIdleMainLooper() throws Exception {
new Handler().post(scratchRunnable);
ShadowLooper.idleMainLooper();
assertThat(scratchRunnable.wasRun).isTrue();
}
@Test
public void postDelayedThenIdleMainLooper_shouldNotRunRunnable() throws Exception {
new Handler().postDelayed(scratchRunnable, 1);
ShadowLooper.idleMainLooper();
assertThat(scratchRunnable.wasRun).isFalse();
}
@Test
public void testPostDelayedThenRunMainLooperOneTask() throws Exception {
new Handler().postDelayed(scratchRunnable, 1);
ShadowLooper.runMainLooperOneTask();
assertThat(scratchRunnable.wasRun).isTrue();
}
@Test
public void testRemoveCallbacks() throws Exception {
Handler handler = new Handler();
ShadowLooper shadowLooper = shadowOf(handler.getLooper());
shadowLooper.pause();
handler.post(scratchRunnable);
handler.removeCallbacks(scratchRunnable);
shadowLooper.unPause();
assertThat(scratchRunnable.wasRun).isFalse();
}
@Test
public void testPostDelayedThenRunMainLooperToNextTask_shouldRunOneTask() throws Exception {
new Handler().postDelayed(scratchRunnable, 1);
ShadowLooper.runMainLooperToNextTask();
assertThat(scratchRunnable.wasRun).isTrue();
}
@Test
public void testPostDelayedTwiceThenRunMainLooperToNextTask_shouldRunMultipleTasks() throws Exception {
TestRunnable task1 = new TestRunnable();
TestRunnable task2 = new TestRunnable();
new Handler().postDelayed(task1, 1);
new Handler().postDelayed(task2, 1);
ShadowLooper.runMainLooperToNextTask();
assertThat(task1.wasRun).isTrue();
assertThat(task2.wasRun).isTrue();
}
@Test
public void testPostDelayedTwiceThenRunMainLooperOneTask_shouldRunOnlyOneTask() throws Exception {
TestRunnable task1 = new TestRunnable();
TestRunnable task2 = new TestRunnable();
new Handler().postDelayed(task1, 1);
new Handler().postDelayed(task2, 1);
ShadowLooper.runMainLooperOneTask();
assertThat(task1.wasRun).isTrue();
assertThat(task2.wasRun).isFalse();
}
@Test
public void testPostDelayedMultipleThenRunMainLooperOneTask_shouldRunMultipleTask() throws Exception {
TestRunnable task1 = new TestRunnable();
TestRunnable task2 = new TestRunnable();
TestRunnable task3 = new TestRunnable();
new Handler().postDelayed(task1, 1);
new Handler().postDelayed(task2, 10);
new Handler().postDelayed(task3, 100);
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
assertThat(task1.wasRun).isTrue();
assertThat(task2.wasRun).isTrue();
assertThat(task3.wasRun).isTrue();
}
@Test
public void testPostAtFrontOfQueueThenRunMainLooperOneTaskAtATime_shouldRunFrontOfQueueTaskFirst() throws Exception {
TestRunnable task1 = new TestRunnable();
TestRunnable task2 = new TestRunnable();
ShadowLooper.pauseMainLooper();
new Handler().post(task1);
boolean result = new Handler().postAtFrontOfQueue(task2);
assertTrue(result);
ShadowLooper.runMainLooperOneTask();
assertThat(task2.wasRun).isTrue();
assertThat(task1.wasRun).isFalse();
ShadowLooper.runMainLooperOneTask();
assertThat(task1.wasRun).isTrue();
}
@Test
public void testNestedPost_shouldRunLast() throws Exception {
ShadowLooper.pauseMainLooper();
final List<Integer> order = new ArrayList<>();
final Handler h = new Handler();
h.post(new Runnable() {
@Override
public void run() {
order.add(1);
h.post(new Runnable() {
@Override
public void run() {
order.add(3);
}
});
}
});
h.post(new Runnable() {
@Override
public void run() {
order.add(2);
}
});
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
assertThat(order).containsExactly(1, 2, 3);
}
@Test
public void testSendMessageAtFrontOfQueueThenRunMainLooperOneMsgAtATime_shouldRunFrontOfQueueMsgFirst() throws Exception {
Handler handler = new Handler();
ShadowLooper.pauseMainLooper();
// Post two messages to handler. Handle first message and confirm that msg posted
// to front is removed.
handler.obtainMessage(123).sendToTarget();
Message frontMsg = handler.obtainMessage(345);
boolean result = handler.sendMessageAtFrontOfQueue(frontMsg);
assertTrue(result);
assertTrue(handler.hasMessages(123));
assertTrue(handler.hasMessages(345));
ShadowLooper.runMainLooperOneTask();
assertTrue(handler.hasMessages(123));
assertFalse(handler.hasMessages(345));
ShadowLooper.runMainLooperOneTask();
assertFalse(handler.hasMessages(123));
assertFalse(handler.hasMessages(345));
}
@Test
public void sendEmptyMessage_addMessageToQueue() {
ShadowLooper.pauseMainLooper();
Handler handler = new Handler();
assertThat(handler.hasMessages(123)).isFalse();
handler.sendEmptyMessage(123);
assertThat(handler.hasMessages(456)).isFalse();
assertThat(handler.hasMessages(123)).isTrue();
ShadowLooper.idleMainLooper(0);
assertThat(handler.hasMessages(123)).isFalse();
}
@Test
public void sendEmptyMessageDelayed_sendsMessageAtCorrectTime() {
ShadowLooper.pauseMainLooper();
Handler handler = new Handler();
handler.sendEmptyMessageDelayed(123, 500);
assertThat(handler.hasMessages(123)).isTrue();
ShadowLooper.idleMainLooper(100);
assertThat(handler.hasMessages(123)).isTrue();
ShadowLooper.idleMainLooper(400);
assertThat(handler.hasMessages(123)).isFalse();
}
@Test
public void sendMessageAtTime_sendsMessageAtCorrectTime() {
ShadowLooper.pauseMainLooper();
Handler handler = new Handler();
Message message = handler.obtainMessage(123);
handler.sendMessageAtTime(message, 500);
assertThat(handler.hasMessages(123)).isTrue();
ShadowLooper.idleMainLooper(100);
assertThat(handler.hasMessages(123)).isTrue();
ShadowLooper.idleMainLooper(400);
assertThat(handler.hasMessages(123)).isFalse();
}
@Test
public void removeMessages_takesMessageOutOfQueue() {
ShadowLooper.pauseMainLooper();
Handler handler = new Handler();
handler.sendEmptyMessageDelayed(123, 500);
handler.removeMessages(123);
assertThat(handler.hasMessages(123)).isFalse();
}
@Test
public void removeMessage_withSpecifiedObject() throws Exception {
ShadowLooper.pauseMainLooper();
Handler handler = new Handler();
Message.obtain(handler, 123, "foo").sendToTarget();
Message.obtain(handler, 123, "bar").sendToTarget();
assertThat(handler.hasMessages(123)).isTrue();
assertThat(handler.hasMessages(123, "foo")).isTrue();
assertThat(handler.hasMessages(123, "bar")).isTrue();
assertThat(handler.hasMessages(123, "baz")).isFalse();
handler.removeMessages(123, "foo");
assertThat(handler.hasMessages(123)).isTrue();
handler.removeMessages(123, "bar");
assertThat(handler.hasMessages(123)).isFalse();
}
@Test
public void testHasMessagesWithWhatAndObject() {
ShadowLooper.pauseMainLooper();
Object testObject = new Object();
Handler handler = new Handler();
Message message = handler.obtainMessage(123, testObject);
assertFalse(handler.hasMessages(123, testObject));
handler.sendMessage(message);
assertTrue(handler.hasMessages(123, testObject));
}
@Test
public void testSendToTarget() {
ShadowLooper.pauseMainLooper();
Object testObject = new Object();
Handler handler = new Handler();
Message message = handler.obtainMessage(123, testObject);
assertThat(handler).isEqualTo(message.getTarget());
message.sendToTarget();
assertTrue(handler.hasMessages(123, testObject));
}
@Test
public void removeMessages_removesFromLooperQueueAsWell() {
final boolean[] wasRun = new boolean[1];
ShadowLooper.pauseMainLooper();
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
wasRun[0] = true;
}
};
handler.sendEmptyMessageDelayed(123, 500);
handler.removeMessages(123);
ShadowLooper.unPauseMainLooper();
assertThat(wasRun[0]).isFalse();
}
@Test
public void scheduler_wontDispatchRemovedMessage_evenIfMessageReused() {
final ArrayList<Long> runAt = new ArrayList<>();
ShadowLooper.pauseMainLooper();
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
runAt.add(shadowOf(Looper.myLooper()).getScheduler().getCurrentTime());
}
};
final long startTime = Robolectric.getForegroundThreadScheduler().getCurrentTime();
Message msg = handler.obtainMessage(123);
handler.sendMessageDelayed(msg, 200);
handler.removeMessages(123);
Message newMsg = handler.obtainMessage(123);
assertThat(newMsg).as("new message").isSameAs(msg);
handler.sendMessageDelayed(newMsg, 400);
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
// Original implementation had a bug which caused reused messages to still
// be invoked at their original post time.
assertThat(runAt).as("handledAt").containsExactly(startTime + 400L);
}
@Test
public void shouldRemoveAllCallbacksAndMessages() throws Exception {
final boolean[] wasRun = new boolean[1];
ShadowLooper.pauseMainLooper();
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
wasRun[0] = true;
}
};
handler.sendEmptyMessage(0);
handler.post(scratchRunnable);
handler.removeCallbacksAndMessages(null);
ShadowLooper.runUiThreadTasksIncludingDelayedTasks();
assertThat(wasRun[0]).as("Message").isFalse();
assertThat(scratchRunnable.wasRun).as("Callback").isFalse();
}
@Test
public void shouldRemoveSingleMessage() throws Exception {
final List<Object> objects = new ArrayList<>();
ShadowLooper.pauseMainLooper();
Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
objects.add(msg.obj);
}
};
Object firstObj = new Object();
handler.sendMessage(handler.obtainMessage(0, firstObj));
Object secondObj = new Object();
handler.sendMessage(handler.obtainMessage(0, secondObj));
handler.removeCallbacksAndMessages(secondObj);
ShadowLooper.unPauseMainLooper();
assertThat(objects).containsExactly(firstObj);
}
@Test
public void shouldRemoveTaggedCallback() throws Exception {
ShadowLooper.pauseMainLooper();
Handler handler = new Handler();
final int[] count = new int[1];
Runnable r = new Runnable() {
@Override
public void run() {
count[0]++;
}
};
String tag1 = "tag1", tag2 = "tag2";
handler.postAtTime(r, tag1, 100);
handler.postAtTime(r, tag2, 105);
handler.removeCallbacks(r, tag2);
ShadowLooper.unPauseMainLooper();
assertThat(count[0]).as("run count").isEqualTo(1);
// This assertion proves that it was the first runnable that ran,
// which proves that the correctly tagged runnable was removed.
assertThat(shadowOf(handler.getLooper()).getScheduler().getCurrentTime()).as("currentTime").isEqualTo(100);
}
@Test
public void shouldObtainMessage() throws Exception {
Message m0 = new Handler().obtainMessage();
assertThat(m0.what).isEqualTo(0);
assertThat(m0.obj).isNull();
Message m1 = new Handler().obtainMessage(1);
assertThat(m1.what).isEqualTo(1);
assertThat(m1.obj).isNull();
Message m2 = new Handler().obtainMessage(1, "foo");
assertThat(m2.what).isEqualTo(1);
assertThat(m2.obj).isEqualTo((Object) "foo");
Message m3 = new Handler().obtainMessage(1, 2, 3);
assertThat(m3.what).isEqualTo(1);
assertThat(m3.arg1).isEqualTo(2);
assertThat(m3.arg2).isEqualTo(3);
assertThat(m3.obj).isNull();
Message m4 = new Handler().obtainMessage(1, 2, 3, "foo");
assertThat(m4.what).isEqualTo(1);
assertThat(m4.arg1).isEqualTo(2);
assertThat(m4.arg2).isEqualTo(3);
assertThat(m4.obj).isEqualTo((Object) "foo");
}
@Test
public void shouldSetWhenOnMessage() throws Exception {
final List<Long> whens = new ArrayList<>();
Handler h = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
whens.add(msg.getWhen());
return false;
}
});
final long startTime = Robolectric.getForegroundThreadScheduler().getCurrentTime();
h.sendEmptyMessage(0);
h.sendEmptyMessageDelayed(0, 4000l);
Robolectric.getForegroundThreadScheduler().advanceToLastPostedRunnable();
h.sendEmptyMessageDelayed(0, 12000l);
Robolectric.getForegroundThreadScheduler().advanceToLastPostedRunnable();
assertThat(whens).as("whens").containsExactly(startTime, startTime + 4000, startTime + 16000);
}
@Test
public void shouldRemoveMessageFromQueueBeforeDispatching() throws Exception {
Handler h = new Handler(Looper.myLooper()) {
@Override
public void handleMessage(Message msg) {
assertFalse(hasMessages(0));
}
};
h.sendEmptyMessage(0);
h.sendMessageAtFrontOfQueue(h.obtainMessage());
}
private class Say implements Runnable {
private String event;
public Say(String event) {
this.event = event;
}
@Override
public void run() {
transcript.add(event);
}
}
}