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