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 AutoDispatchThread mAutoDispatchThread; 52 53 static { 54 try { 55 LOOPER_CONSTRUCTOR = Looper.class.getDeclaredConstructor(Boolean.TYPE); 56 LOOPER_CONSTRUCTOR.setAccessible(true); 57 THREAD_LOCAL_LOOPER_FIELD = Looper.class.getDeclaredField("sThreadLocal"); 58 THREAD_LOCAL_LOOPER_FIELD.setAccessible(true); 59 MESSAGE_QUEUE_MESSAGES_FIELD = MessageQueue.class.getDeclaredField("mMessages"); 60 MESSAGE_QUEUE_MESSAGES_FIELD.setAccessible(true); 61 MESSAGE_NEXT_FIELD = Message.class.getDeclaredField("next"); 62 MESSAGE_NEXT_FIELD.setAccessible(true); 63 MESSAGE_WHEN_FIELD = Message.class.getDeclaredField("when"); 64 MESSAGE_WHEN_FIELD.setAccessible(true); 65 MESSAGE_MARK_IN_USE_METHOD = Message.class.getDeclaredMethod("markInUse"); 66 MESSAGE_MARK_IN_USE_METHOD.setAccessible(true); 67 } catch (NoSuchFieldException | NoSuchMethodException e) { 68 throw new RuntimeException("Failed to initialize TestLooper", e); 69 } 70 } 71 72 TestLooper()73 public TestLooper() { 74 try { 75 mLooper = LOOPER_CONSTRUCTOR.newInstance(false); 76 77 ThreadLocal<Looper> threadLocalLooper = (ThreadLocal<Looper>) THREAD_LOCAL_LOOPER_FIELD 78 .get(null); 79 threadLocalLooper.set(mLooper); 80 } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) { 81 throw new RuntimeException("Reflection error constructing or accessing looper", e); 82 } 83 } 84 getLooper()85 public Looper getLooper() { 86 return mLooper; 87 } 88 getNewExecutor()89 public Executor getNewExecutor() { 90 return new HandlerExecutor(new Handler(getLooper())); 91 } 92 getMessageLinkedList()93 private Message getMessageLinkedList() { 94 try { 95 MessageQueue queue = mLooper.getQueue(); 96 return (Message) MESSAGE_QUEUE_MESSAGES_FIELD.get(queue); 97 } catch (IllegalAccessException e) { 98 throw new RuntimeException("Access failed in TestLooper: get - MessageQueue.mMessages", 99 e); 100 } 101 } 102 moveTimeForward(long milliSeconds)103 public void moveTimeForward(long milliSeconds) { 104 try { 105 Message msg = getMessageLinkedList(); 106 while (msg != null) { 107 long updatedWhen = msg.getWhen() - milliSeconds; 108 if (updatedWhen < 0) { 109 updatedWhen = 0; 110 } 111 MESSAGE_WHEN_FIELD.set(msg, updatedWhen); 112 msg = (Message) MESSAGE_NEXT_FIELD.get(msg); 113 } 114 } catch (IllegalAccessException e) { 115 throw new RuntimeException("Access failed in TestLooper: set - Message.when", e); 116 } 117 } 118 messageQueueNext()119 private Message messageQueueNext() { 120 try { 121 long now = SystemClock.uptimeMillis(); 122 123 Message prevMsg = null; 124 Message msg = getMessageLinkedList(); 125 if (msg != null && msg.getTarget() == null) { 126 // Stalled by a barrier. Find the next asynchronous message in 127 // the queue. 128 do { 129 prevMsg = msg; 130 msg = (Message) MESSAGE_NEXT_FIELD.get(msg); 131 } while (msg != null && !msg.isAsynchronous()); 132 } 133 if (msg != null) { 134 if (now >= msg.getWhen()) { 135 // Got a message. 136 if (prevMsg != null) { 137 MESSAGE_NEXT_FIELD.set(prevMsg, MESSAGE_NEXT_FIELD.get(msg)); 138 } else { 139 MESSAGE_QUEUE_MESSAGES_FIELD.set(mLooper.getQueue(), 140 MESSAGE_NEXT_FIELD.get(msg)); 141 } 142 MESSAGE_NEXT_FIELD.set(msg, null); 143 MESSAGE_MARK_IN_USE_METHOD.invoke(msg); 144 return msg; 145 } 146 } 147 } catch (IllegalAccessException | InvocationTargetException e) { 148 throw new RuntimeException("Access failed in TestLooper", e); 149 } 150 151 return null; 152 } 153 154 /** 155 * @return true if there are pending messages in the message queue 156 */ isIdle()157 public synchronized boolean isIdle() { 158 Message messageList = getMessageLinkedList(); 159 160 return messageList != null && SystemClock.uptimeMillis() >= messageList.getWhen(); 161 } 162 163 /** 164 * @return the next message in the Looper's message queue or null if there is none 165 */ nextMessage()166 public synchronized Message nextMessage() { 167 if (isIdle()) { 168 return messageQueueNext(); 169 } else { 170 return null; 171 } 172 } 173 174 /** 175 * Dispatch the next message in the queue 176 * Asserts that there is a message in the queue 177 */ dispatchNext()178 public synchronized void dispatchNext() { 179 assertTrue(isIdle()); 180 Message msg = messageQueueNext(); 181 if (msg == null) { 182 return; 183 } 184 msg.getTarget().dispatchMessage(msg); 185 } 186 187 /** 188 * Dispatch all messages currently in the queue 189 * Will not fail if there are no messages pending 190 * @return the number of messages dispatched 191 */ dispatchAll()192 public synchronized int dispatchAll() { 193 int count = 0; 194 while (isIdle()) { 195 dispatchNext(); 196 ++count; 197 } 198 return count; 199 } 200 201 /** 202 * Thread used to dispatch messages when the main thread is blocked waiting for a response. 203 */ 204 private class AutoDispatchThread extends Thread { 205 private static final int MAX_LOOPS = 100; 206 private static final int LOOP_SLEEP_TIME_MS = 10; 207 208 private RuntimeException mAutoDispatchException = null; 209 210 /** 211 * Run method for the auto dispatch thread. 212 * The thread loops a maximum of MAX_LOOPS times with a 10ms sleep between loops. 213 * The thread continues looping and attempting to dispatch all messages until at 214 * least one message has been dispatched. 215 */ 216 @Override run()217 public void run() { 218 int dispatchCount = 0; 219 for (int i = 0; i < MAX_LOOPS; i++) { 220 try { 221 dispatchCount = dispatchAll(); 222 } catch (RuntimeException e) { 223 mAutoDispatchException = e; 224 } 225 Log.d(TAG, "dispatched " + dispatchCount + " messages"); 226 if (dispatchCount > 0) { 227 return; 228 } 229 try { 230 Thread.sleep(LOOP_SLEEP_TIME_MS); 231 } catch (InterruptedException e) { 232 mAutoDispatchException = new IllegalStateException( 233 "stopAutoDispatch called before any messages were dispatched."); 234 return; 235 } 236 } 237 Log.e(TAG, "AutoDispatchThread did not dispatch any messages."); 238 mAutoDispatchException = new IllegalStateException( 239 "TestLooper did not dispatch any messages before exiting."); 240 } 241 242 /** 243 * Method allowing the TestLooper to pass any exceptions thrown by the thread to be passed 244 * to the main thread. 245 * 246 * @return RuntimeException Exception created by stopping without dispatching a message 247 */ getException()248 public RuntimeException getException() { 249 return mAutoDispatchException; 250 } 251 } 252 253 /** 254 * Create and start a new AutoDispatchThread if one is not already running. 255 */ startAutoDispatch()256 public void startAutoDispatch() { 257 if (mAutoDispatchThread != null) { 258 throw new IllegalStateException( 259 "startAutoDispatch called with the AutoDispatchThread already running."); 260 } 261 mAutoDispatchThread = new AutoDispatchThread(); 262 mAutoDispatchThread.start(); 263 } 264 265 /** 266 * If an AutoDispatchThread is currently running, stop and clean up. 267 */ stopAutoDispatch()268 public void stopAutoDispatch() { 269 if (mAutoDispatchThread != null) { 270 if (mAutoDispatchThread.isAlive()) { 271 mAutoDispatchThread.interrupt(); 272 } 273 try { 274 mAutoDispatchThread.join(); 275 } catch (InterruptedException e) { 276 // Catch exception from join. 277 } 278 279 RuntimeException e = mAutoDispatchThread.getException(); 280 mAutoDispatchThread = null; 281 if (e != null) { 282 throw e; 283 } 284 } else { 285 // stopAutoDispatch was called when startAutoDispatch has not created a new thread. 286 throw new IllegalStateException( 287 "stopAutoDispatch called without startAutoDispatch."); 288 } 289 } 290 } 291