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