1package com.xtremelabs.robolectric.shadows;
2
3import android.os.Looper;
4import com.xtremelabs.robolectric.Robolectric;
5import com.xtremelabs.robolectric.internal.Implementation;
6import com.xtremelabs.robolectric.internal.Implements;
7import com.xtremelabs.robolectric.util.Scheduler;
8
9import 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)
19public 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
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