aboutsummaryrefslogtreecommitdiff
path: root/v1/src/main/java/com/xtremelabs/robolectric/shadows/ShadowLooper.java
blob: 99126e2f3265631ac85d8bbe6ced34c04145db31 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
package com.xtremelabs.robolectric.shadows;

import android.os.Looper;
import com.xtremelabs.robolectric.Robolectric;
import com.xtremelabs.robolectric.internal.Implementation;
import com.xtremelabs.robolectric.internal.Implements;
import com.xtremelabs.robolectric.util.Scheduler;

import static com.xtremelabs.robolectric.Robolectric.shadowOf;

/**
 * Shadow for {@code Looper} that enqueues posted {@link Runnable}s to be run (on this thread) later. {@code Runnable}s
 * that are scheduled to run immediately can be triggered by calling {@link #idle()}
 * todo: provide better support for advancing the clock and running queued tasks
 */

@SuppressWarnings({"UnusedDeclaration"})
@Implements(Looper.class)
public class ShadowLooper {
    private static ThreadLocal<Looper> looperForThread = makeThreadLocalLoopers();
    private Scheduler scheduler = new Scheduler();
    private Thread myThread = Thread.currentThread();

    boolean quit;

    private static synchronized ThreadLocal<Looper> makeThreadLocalLoopers() {
        return new ThreadLocal<Looper>() {
            @Override
            protected Looper initialValue() {
                return Robolectric.Reflection.newInstanceOf(Looper.class);
            }
        };
    }

    public static void resetThreadLoopers() {
        looperForThread = makeThreadLocalLoopers();
    }

    @Implementation
    public static Looper getMainLooper() {
        return Robolectric.getShadowApplication().getMainLooper();
    }

    @Implementation
    public static void loop() {
        final ShadowLooper looper = shadowOf(myLooper());
        if (looper != shadowOf(getMainLooper())) {
            while (!looper.quit) {
                try {
                    synchronized (looper) {
                        looper.wait();
                    }
                } catch (InterruptedException ignore) {
                }
            }
        }
    }

    @Implementation
    public static synchronized Looper myLooper() {
        return looperForThread.get();
    }

    @Implementation
    public void quit() {
        if (this == shadowOf(getMainLooper())) throw new RuntimeException("Main thread not allowed to quit");
        synchronized (this) {
            quit = true;
            scheduler.reset();
            notify();
        }
    }

    @Implementation
    public Thread getThread() {
    	return myThread;
    }
    
    public boolean hasQuit() {
        return quit;
    }

    public static void pauseLooper(Looper looper) {
        shadowOf(looper).pause();
    }

    public static void unPauseLooper(Looper looper) {
        shadowOf(looper).unPause();
    }

    public static void pauseMainLooper() {
        pauseLooper(Looper.getMainLooper());
    }

    public static void unPauseMainLooper() {
        unPauseLooper(Looper.getMainLooper());
    }

    public static void idleMainLooper(long interval) {
        shadowOf(Looper.getMainLooper()).idle(interval);
    }

    /**
     * Causes {@link Runnable}s that have been scheduled to run immediately to actually run. Does not advance the
     * scheduler's clock;
     */
    public void idle() {
        scheduler.advanceBy(0);
    }

    /**
     * Causes {@link Runnable}s that have been scheduled to run within the next {@code intervalMillis} milliseconds to
     * run while advancing the scheduler's clock.
     *
     * @param intervalMillis milliseconds to advance
     */
    public void idle(long intervalMillis) {
        scheduler.advanceBy(intervalMillis);
    }

    /**
     * Causes all of the {@link Runnable}s that have been scheduled to run while advancing the scheduler's clock to the
     * start time of the last scheduled {@link Runnable}.
     */
    public void runToEndOfTasks() {
        scheduler.advanceToLastPostedRunnable();
    }

    /**
     * Causes the next {@link Runnable}(s) that have been scheduled to run while advancing the scheduler's clock to its
     * start time. If more than one {@link Runnable} is scheduled to run at this time then they will all be run.
     */
    public void runToNextTask() {
        scheduler.advanceToNextPostedRunnable();
    }

    /**
     * Causes only one of the next {@link Runnable}s that have been scheduled to run while advancing the scheduler's
     * clock to its start time. Only one {@link Runnable} will run even if more than one has ben scheduled to run at the
     * same time.
     */
    public void runOneTask() {
        scheduler.runOneTask();
    }

    /**
     * Enqueue a task to be run later.
     *
     * @param runnable    the task to be run
     * @param delayMillis how many milliseconds into the (virtual) future to run it
     */
    public boolean post(Runnable runnable, long delayMillis) {
        if (!quit) {
            scheduler.postDelayed(runnable, delayMillis);
            return true;
        } else {
            return false;
        }
    }

    public boolean postAtFrontOfQueue(Runnable runnable) {
        if (!quit) {
            scheduler.postAtFrontOfQueue(runnable);
            return true;
        } else {
            return false;
        }
    }

    public void pause() {
        scheduler.pause();
    }

    public void unPause() {
        scheduler.unPause();
    }

    /**
     * Causes all enqueued tasks to be discarded
     */
    public void reset() {
        scheduler.reset();
    }

    /**
     * Returns the {@link com.xtremelabs.robolectric.util.Scheduler} that is being used to manage the enqueued tasks.
     *
     * @return the {@link com.xtremelabs.robolectric.util.Scheduler} that is being used to manage the enqueued tasks.
     */
    public Scheduler getScheduler() {
        return scheduler;
    }
}