• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.os.test;
18 
19 import static org.junit.Assert.assertTrue;
20 
21 import android.os.Handler;
22 import android.os.HandlerExecutor;
23 import android.os.Looper;
24 import android.os.Message;
25 import android.os.MessageQueue;
26 import android.os.SystemClock;
27 import android.util.Log;
28 
29 import java.lang.reflect.Constructor;
30 import java.lang.reflect.Field;
31 import java.lang.reflect.InvocationTargetException;
32 import java.lang.reflect.Method;
33 import java.util.concurrent.Executor;
34 
35 /**
36  * Creates a looper whose message queue can be manipulated
37  * This allows testing code that uses a looper to dispatch messages in a deterministic manner
38  * Creating a TestLooper will also install it as the looper for the current thread
39  */
40 public class TestLooper {
41     protected final Looper mLooper;
42 
43     private static final Constructor<Looper> LOOPER_CONSTRUCTOR;
44     private static final Field THREAD_LOCAL_LOOPER_FIELD;
45     private static final Field MESSAGE_QUEUE_MESSAGES_FIELD;
46     private static final Field MESSAGE_NEXT_FIELD;
47     private static final Field MESSAGE_WHEN_FIELD;
48     private static final Method MESSAGE_MARK_IN_USE_METHOD;
49     private static final String TAG = "TestLooper";
50 
51     private final Clock mClock;
52 
53     private AutoDispatchThread mAutoDispatchThread;
54 
55     static {
56         try {
57             LOOPER_CONSTRUCTOR = Looper.class.getDeclaredConstructor(Boolean.TYPE);
58             LOOPER_CONSTRUCTOR.setAccessible(true);
59             THREAD_LOCAL_LOOPER_FIELD = Looper.class.getDeclaredField("sThreadLocal");
60             THREAD_LOCAL_LOOPER_FIELD.setAccessible(true);
61             MESSAGE_QUEUE_MESSAGES_FIELD = MessageQueue.class.getDeclaredField("mMessages");
62             MESSAGE_QUEUE_MESSAGES_FIELD.setAccessible(true);
63             MESSAGE_NEXT_FIELD = Message.class.getDeclaredField("next");
64             MESSAGE_NEXT_FIELD.setAccessible(true);
65             MESSAGE_WHEN_FIELD = Message.class.getDeclaredField("when");
66             MESSAGE_WHEN_FIELD.setAccessible(true);
67             MESSAGE_MARK_IN_USE_METHOD = Message.class.getDeclaredMethod("markInUse");
68             MESSAGE_MARK_IN_USE_METHOD.setAccessible(true);
69         } catch (NoSuchFieldException | NoSuchMethodException e) {
70             throw new RuntimeException("Failed to initialize TestLooper", e);
71         }
72     }
73 
74     /**
75      * Creates a TestLooper and installs it as the looper for the current thread.
76      */
TestLooper()77     public TestLooper() {
78         this(SystemClock::uptimeMillis);
79     }
80 
81     /**
82      * Creates a TestLooper with a custom clock and installs it as the looper for the current
83      * thread.
84      *
85      * Messages are dispatched when their {@link Message#when} is before or at {@link
86      * Clock#uptimeMillis()}.
87      * Use a custom clock with care. When using an offsettable clock like {@link
88      * com.android.server.testutils.OffsettableClock} be sure not to double offset messages by
89      * offsetting the clock and calling {@link #moveTimeForward(long)}. Instead, offset the clock
90      * and call {@link #dispatchAll()}.
91      */
TestLooper(Clock clock)92     public TestLooper(Clock clock) {
93         try {
94             mLooper = LOOPER_CONSTRUCTOR.newInstance(false);
95 
96             ThreadLocal<Looper> threadLocalLooper = (ThreadLocal<Looper>) THREAD_LOCAL_LOOPER_FIELD
97                     .get(null);
98             threadLocalLooper.set(mLooper);
99         } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
100             throw new RuntimeException("Reflection error constructing or accessing looper", e);
101         }
102 
103         mClock = clock;
104     }
105 
getLooper()106     public Looper getLooper() {
107         return mLooper;
108     }
109 
getNewExecutor()110     public Executor getNewExecutor() {
111         return new HandlerExecutor(new Handler(getLooper()));
112     }
113 
getMessageLinkedList()114     private Message getMessageLinkedList() {
115         try {
116             MessageQueue queue = mLooper.getQueue();
117             return (Message) MESSAGE_QUEUE_MESSAGES_FIELD.get(queue);
118         } catch (IllegalAccessException e) {
119             throw new RuntimeException("Access failed in TestLooper: get - MessageQueue.mMessages",
120                     e);
121         }
122     }
123 
moveTimeForward(long milliSeconds)124     public void moveTimeForward(long milliSeconds) {
125         try {
126             Message msg = getMessageLinkedList();
127             while (msg != null) {
128                 long updatedWhen = msg.getWhen() - milliSeconds;
129                 if (updatedWhen < 0) {
130                     updatedWhen = 0;
131                 }
132                 MESSAGE_WHEN_FIELD.set(msg, updatedWhen);
133                 msg = (Message) MESSAGE_NEXT_FIELD.get(msg);
134             }
135         } catch (IllegalAccessException e) {
136             throw new RuntimeException("Access failed in TestLooper: set - Message.when", e);
137         }
138     }
139 
currentTime()140     private long currentTime() {
141         return mClock.uptimeMillis();
142     }
143 
messageQueueNext()144     private Message messageQueueNext() {
145         try {
146             long now = currentTime();
147 
148             Message prevMsg = null;
149             Message msg = getMessageLinkedList();
150             if (msg != null && msg.getTarget() == null) {
151                 // Stalled by a barrier. Find the next asynchronous message in
152                 // the queue.
153                 do {
154                     prevMsg = msg;
155                     msg = (Message) MESSAGE_NEXT_FIELD.get(msg);
156                 } while (msg != null && !msg.isAsynchronous());
157             }
158             if (msg != null) {
159                 if (now >= msg.getWhen()) {
160                     // Got a message.
161                     if (prevMsg != null) {
162                         MESSAGE_NEXT_FIELD.set(prevMsg, MESSAGE_NEXT_FIELD.get(msg));
163                     } else {
164                         MESSAGE_QUEUE_MESSAGES_FIELD.set(mLooper.getQueue(),
165                                 MESSAGE_NEXT_FIELD.get(msg));
166                     }
167                     MESSAGE_NEXT_FIELD.set(msg, null);
168                     MESSAGE_MARK_IN_USE_METHOD.invoke(msg);
169                     return msg;
170                 }
171             }
172         } catch (IllegalAccessException | InvocationTargetException e) {
173             throw new RuntimeException("Access failed in TestLooper", e);
174         }
175 
176         return null;
177     }
178 
179     /**
180      * @return true if there are pending messages in the message queue
181      */
isIdle()182     public synchronized boolean isIdle() {
183         Message messageList = getMessageLinkedList();
184 
185         return messageList != null && currentTime() >= messageList.getWhen();
186     }
187 
188     /**
189      * @return the next message in the Looper's message queue or null if there is none
190      */
nextMessage()191     public synchronized Message nextMessage() {
192         if (isIdle()) {
193             return messageQueueNext();
194         } else {
195             return null;
196         }
197     }
198 
199     /**
200      * Dispatch the next message in the queue
201      * Asserts that there is a message in the queue
202      */
dispatchNext()203     public synchronized void dispatchNext() {
204         assertTrue(isIdle());
205         Message msg = messageQueueNext();
206         if (msg == null) {
207             return;
208         }
209         msg.getTarget().dispatchMessage(msg);
210     }
211 
212     /**
213      * Dispatch all messages currently in the queue
214      * Will not fail if there are no messages pending
215      *
216      * @return the number of messages dispatched
217      */
dispatchAll()218     public synchronized int dispatchAll() {
219         int count = 0;
220         while (isIdle()) {
221             dispatchNext();
222             ++count;
223         }
224         return count;
225     }
226 
227     public interface Clock {
uptimeMillis()228         long uptimeMillis();
229     }
230 
231     /**
232      * Thread used to dispatch messages when the main thread is blocked waiting for a response.
233      */
234     private class AutoDispatchThread extends Thread {
235         private static final int MAX_LOOPS = 100;
236         private static final int LOOP_SLEEP_TIME_MS = 10;
237 
238         private RuntimeException mAutoDispatchException = null;
239 
240         /**
241          * Run method for the auto dispatch thread.
242          * The thread loops a maximum of MAX_LOOPS times with a 10ms sleep between loops.
243          * The thread continues looping and attempting to dispatch all messages until
244          * {@link #stopAutoDispatch()} has been invoked.
245          */
246         @Override
run()247         public void run() {
248             int dispatchCount = 0;
249             for (int i = 0; i < MAX_LOOPS; i++) {
250                 try {
251                     dispatchCount += dispatchAll();
252                 } catch (RuntimeException e) {
253                     mAutoDispatchException = e;
254                     return;
255                 }
256                 Log.d(TAG, "dispatched " + dispatchCount + " messages");
257                 try {
258                     Thread.sleep(LOOP_SLEEP_TIME_MS);
259                 } catch (InterruptedException e) {
260                     if (dispatchCount == 0) {
261                         Log.e(TAG, "stopAutoDispatch called before any messages were dispatched.");
262                         mAutoDispatchException = new IllegalStateException(
263                                 "stopAutoDispatch called before any messages were dispatched.");
264                     }
265                     return;
266                 }
267             }
268             if (dispatchCount == 0) {
269                 Log.e(TAG, "AutoDispatchThread did not dispatch any messages.");
270                 mAutoDispatchException = new IllegalStateException(
271                         "TestLooper did not dispatch any messages before exiting.");
272             }
273         }
274 
275         /**
276          * Method allowing the TestLooper to pass any exceptions thrown by the thread to be passed
277          * to the main thread.
278          *
279          * @return RuntimeException Exception created by stopping without dispatching a message
280          */
getException()281         public RuntimeException getException() {
282             return mAutoDispatchException;
283         }
284     }
285 
286     /**
287      * Create and start a new AutoDispatchThread if one is not already running.
288      */
startAutoDispatch()289     public void startAutoDispatch() {
290         if (mAutoDispatchThread != null) {
291             throw new IllegalStateException(
292                     "startAutoDispatch called with the AutoDispatchThread already running.");
293         }
294         mAutoDispatchThread = new AutoDispatchThread();
295         mAutoDispatchThread.start();
296     }
297 
298     /**
299      * If an AutoDispatchThread is currently running, stop and clean up.
300      */
stopAutoDispatch()301     public void stopAutoDispatch() {
302         if (mAutoDispatchThread != null) {
303             if (mAutoDispatchThread.isAlive()) {
304                 mAutoDispatchThread.interrupt();
305             }
306             try {
307                 mAutoDispatchThread.join();
308             } catch (InterruptedException e) {
309                 // Catch exception from join.
310             }
311 
312             RuntimeException e = mAutoDispatchThread.getException();
313             mAutoDispatchThread = null;
314             if (e != null) {
315                 throw e;
316             }
317         } else {
318             // stopAutoDispatch was called when startAutoDispatch has not created a new thread.
319             throw new IllegalStateException(
320                     "stopAutoDispatch called without startAutoDispatch.");
321         }
322     }
323 
324     /**
325      * If an AutoDispatchThread is currently running, stop and clean up.
326      * This method ignores exceptions raised for indicating that no messages were dispatched.
327      */
stopAutoDispatchAndIgnoreExceptions()328     public void stopAutoDispatchAndIgnoreExceptions() {
329         try {
330             stopAutoDispatch();
331         } catch (IllegalStateException e) {
332             Log.e(TAG, "stopAutoDispatch", e);
333         }
334 
335     }
336 }
337