1 package com.xtremelabs.robolectric.shadows; 2 3 import android.os.Looper; 4 import com.xtremelabs.robolectric.Robolectric; 5 import com.xtremelabs.robolectric.internal.Implementation; 6 import com.xtremelabs.robolectric.internal.Implements; 7 import com.xtremelabs.robolectric.util.Scheduler; 8 9 import static com.xtremelabs.robolectric.Robolectric.shadowOf; 10 11 /** 12 * Shadow for {@code Looper} that enqueues posted {@link Runnable}s to be run (on this thread) later. {@code Runnable}s 13 * that are scheduled to run immediately can be triggered by calling {@link #idle()} 14 * todo: provide better support for advancing the clock and running queued tasks 15 */ 16 17 @SuppressWarnings({"UnusedDeclaration"}) 18 @Implements(Looper.class) 19 public class ShadowLooper { 20 private static ThreadLocal<Looper> looperForThread = makeThreadLocalLoopers(); 21 private Scheduler scheduler = new Scheduler(); 22 private Thread myThread = Thread.currentThread(); 23 24 boolean quit; 25 makeThreadLocalLoopers()26 private static synchronized ThreadLocal<Looper> makeThreadLocalLoopers() { 27 return new ThreadLocal<Looper>() { 28 @Override 29 protected Looper initialValue() { 30 return Robolectric.Reflection.newInstanceOf(Looper.class); 31 } 32 }; 33 } 34 35 public static void resetThreadLoopers() { 36 looperForThread = makeThreadLocalLoopers(); 37 } 38 39 @Implementation 40 public static Looper getMainLooper() { 41 return Robolectric.getShadowApplication().getMainLooper(); 42 } 43 44 @Implementation 45 public static void loop() { 46 final ShadowLooper looper = shadowOf(myLooper()); 47 if (looper != shadowOf(getMainLooper())) { 48 while (!looper.quit) { 49 try { 50 synchronized (looper) { 51 looper.wait(); 52 } 53 } catch (InterruptedException ignore) { 54 } 55 } 56 } 57 } 58 59 @Implementation 60 public static synchronized Looper myLooper() { 61 return looperForThread.get(); 62 } 63 64 @Implementation 65 public void quit() { 66 if (this == shadowOf(getMainLooper())) throw new RuntimeException("Main thread not allowed to quit"); 67 synchronized (this) { 68 quit = true; 69 scheduler.reset(); 70 notify(); 71 } 72 } 73 74 @Implementation 75 public Thread getThread() { 76 return myThread; 77 } 78 79 public boolean hasQuit() { 80 return quit; 81 } 82 83 public static void pauseLooper(Looper looper) { 84 shadowOf(looper).pause(); 85 } 86 87 public static void unPauseLooper(Looper looper) { 88 shadowOf(looper).unPause(); 89 } 90 91 public static void pauseMainLooper() { 92 pauseLooper(Looper.getMainLooper()); 93 } 94 95 public static void unPauseMainLooper() { 96 unPauseLooper(Looper.getMainLooper()); 97 } 98 99 public static void idleMainLooper(long interval) { 100 shadowOf(Looper.getMainLooper()).idle(interval); 101 } 102 103 /** 104 * Causes {@link Runnable}s that have been scheduled to run immediately to actually run. Does not advance the 105 * scheduler's clock; 106 */ 107 public void idle() { 108 scheduler.advanceBy(0); 109 } 110 111 /** 112 * Causes {@link Runnable}s that have been scheduled to run within the next {@code intervalMillis} milliseconds to 113 * run while advancing the scheduler's clock. 114 * 115 * @param intervalMillis milliseconds to advance 116 */ 117 public void idle(long intervalMillis) { 118 scheduler.advanceBy(intervalMillis); 119 } 120 121 /** 122 * Causes all of the {@link Runnable}s that have been scheduled to run while advancing the scheduler's clock to the 123 * start time of the last scheduled {@link Runnable}. 124 */ 125 public void runToEndOfTasks() { 126 scheduler.advanceToLastPostedRunnable(); 127 } 128 129 /** 130 * Causes the next {@link Runnable}(s) that have been scheduled to run while advancing the scheduler's clock to its 131 * start time. If more than one {@link Runnable} is scheduled to run at this time then they will all be run. 132 */ 133 public void runToNextTask() { 134 scheduler.advanceToNextPostedRunnable(); 135 } 136 137 /** 138 * Causes only one of the next {@link Runnable}s that have been scheduled to run while advancing the scheduler's 139 * clock to its start time. Only one {@link Runnable} will run even if more than one has ben scheduled to run at the 140 * same time. 141 */ 142 public void runOneTask() { 143 scheduler.runOneTask(); 144 } 145 146 /** 147 * Enqueue a task to be run later. 148 * 149 * @param runnable the task to be run 150 * @param delayMillis how many milliseconds into the (virtual) future to run it 151 */ 152 public boolean post(Runnable runnable, long delayMillis) { 153 if (!quit) { 154 scheduler.postDelayed(runnable, delayMillis); 155 return true; 156 } else { 157 return false; 158 } 159 } 160 161 public boolean postAtFrontOfQueue(Runnable runnable) { 162 if (!quit) { 163 scheduler.postAtFrontOfQueue(runnable); 164 return true; 165 } else { 166 return false; 167 } 168 } 169 170 public void pause() { 171 scheduler.pause(); 172 } 173 174 public void unPause() { 175 scheduler.unPause(); 176 } 177 178 /** 179 * Causes all enqueued tasks to be discarded 180 */ 181 public void reset() { 182 scheduler.reset(); 183 } 184 185 /** 186 * Returns the {@link com.xtremelabs.robolectric.util.Scheduler} that is being used to manage the enqueued tasks. 187 * 188 * @return the {@link com.xtremelabs.robolectric.util.Scheduler} that is being used to manage the enqueued tasks. 189 */ 190 public Scheduler getScheduler() { 191 return scheduler; 192 } 193 } 194