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.Build; 22 import android.os.Handler; 23 import android.os.HandlerExecutor; 24 import android.os.Looper; 25 import android.os.Message; 26 import android.os.MessageQueue; 27 import android.os.SystemClock; 28 import android.os.TestLooperManager; 29 import android.util.Log; 30 31 import androidx.test.platform.app.InstrumentationRegistry; 32 33 import java.lang.reflect.Constructor; 34 import java.lang.reflect.Field; 35 import java.lang.reflect.InvocationTargetException; 36 import java.lang.reflect.Method; 37 import java.util.ArrayDeque; 38 import java.util.Queue; 39 import java.util.concurrent.Executor; 40 41 /** 42 * Creates a looper whose message queue can be manipulated This allows testing code that uses a 43 * looper to dispatch messages in a deterministic manner Creating a TestLooper will also install it 44 * as the looper for the current thread 45 * 46 * @deprecated Use {@link android.os.TestLooperManager} or {@link 47 * org.robolectric.shadows.ShadowLooper} instead. 48 * This class is not actively maintained. 49 * Both of the recommended alternatives allow fine control of execution. 50 * The Robolectric class also allows advancing time. 51 */ 52 public class TestLooper { 53 private final Looper mLooper; 54 private final TestLooperManager mTestLooperManager; 55 private final Clock mClock; 56 57 private static final Constructor<Looper> LOOPER_CONSTRUCTOR; 58 private static final Field THREAD_LOCAL_LOOPER_FIELD; 59 private static final Field MESSAGE_QUEUE_MESSAGES_FIELD; 60 private static final Field MESSAGE_NEXT_FIELD; 61 private static final Field MESSAGE_WHEN_FIELD; 62 private static final Method MESSAGE_MARK_IN_USE_METHOD; 63 private static final String TAG = "TestLooper"; 64 65 private AutoDispatchThread mAutoDispatchThread; 66 67 /** 68 * Baklava introduces new {@link TestLooperManager} APIs that we can use instead of reflection. 69 */ isAtLeastBaklava()70 private static boolean isAtLeastBaklava() { 71 return Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA; 72 } 73 74 static { 75 try { 76 LOOPER_CONSTRUCTOR = Looper.class.getDeclaredConstructor(Boolean.TYPE); 77 LOOPER_CONSTRUCTOR.setAccessible(true); 78 THREAD_LOCAL_LOOPER_FIELD = Looper.class.getDeclaredField("sThreadLocal"); 79 THREAD_LOCAL_LOOPER_FIELD.setAccessible(true); 80 81 if (isAtLeastBaklava()) { 82 MESSAGE_QUEUE_MESSAGES_FIELD = null; 83 MESSAGE_NEXT_FIELD = null; 84 MESSAGE_WHEN_FIELD = null; 85 MESSAGE_MARK_IN_USE_METHOD = null; 86 } else { 87 MESSAGE_QUEUE_MESSAGES_FIELD = MessageQueue.class.getDeclaredField("mMessages"); 88 MESSAGE_QUEUE_MESSAGES_FIELD.setAccessible(true); 89 MESSAGE_NEXT_FIELD = Message.class.getDeclaredField("next"); 90 MESSAGE_NEXT_FIELD.setAccessible(true); 91 MESSAGE_WHEN_FIELD = Message.class.getDeclaredField("when"); 92 MESSAGE_WHEN_FIELD.setAccessible(true); 93 MESSAGE_MARK_IN_USE_METHOD = Message.class.getDeclaredMethod("markInUse"); 94 MESSAGE_MARK_IN_USE_METHOD.setAccessible(true); 95 } 96 } catch (NoSuchFieldException | NoSuchMethodException e) { 97 throw new RuntimeException("Failed to initialize TestLooper", e); 98 } 99 } 100 101 /** 102 * Creates a TestLooper and installs it as the looper for the current thread. 103 */ TestLooper()104 public TestLooper() { 105 this(SystemClock::uptimeMillis); 106 } 107 108 /** 109 * Creates a TestLooper with a custom clock and installs it as the looper for the current 110 * thread. 111 * 112 * Messages are dispatched when their {@link Message#when} is before or at {@link 113 * Clock#uptimeMillis()}. 114 * Use a custom clock with care. When using an offsettable clock like {@link 115 * com.android.server.testutils.OffsettableClock} be sure not to double offset messages by 116 * offsetting the clock and calling {@link #moveTimeForward(long)}. Instead, offset the clock 117 * and call {@link #dispatchAll()}. 118 */ TestLooper(Clock clock)119 public TestLooper(Clock clock) { 120 try { 121 mLooper = LOOPER_CONSTRUCTOR.newInstance(false); 122 123 ThreadLocal<Looper> threadLocalLooper = 124 (ThreadLocal<Looper>) THREAD_LOCAL_LOOPER_FIELD.get(null); 125 threadLocalLooper.set(mLooper); 126 } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) { 127 throw new RuntimeException("Reflection error constructing or accessing looper", e); 128 } 129 130 if (isAtLeastBaklava()) { 131 mTestLooperManager = 132 InstrumentationRegistry.getInstrumentation().acquireLooperManager(mLooper); 133 } else { 134 mTestLooperManager = null; 135 } 136 137 mClock = clock; 138 } 139 getLooper()140 public Looper getLooper() { 141 return mLooper; 142 } 143 getNewExecutor()144 public Executor getNewExecutor() { 145 return new HandlerExecutor(new Handler(getLooper())); 146 } 147 getMessageLinkedListLegacy()148 private Message getMessageLinkedListLegacy() { 149 try { 150 MessageQueue queue = mLooper.getQueue(); 151 return (Message) MESSAGE_QUEUE_MESSAGES_FIELD.get(queue); 152 } catch (IllegalAccessException e) { 153 throw new RuntimeException("Access failed in TestLooper: get - MessageQueue.mMessages", 154 e); 155 } 156 } 157 moveTimeForward(long milliSeconds)158 public void moveTimeForward(long milliSeconds) { 159 if (isAtLeastBaklava()) { 160 moveTimeForwardBaklava(milliSeconds); 161 } else { 162 moveTimeForwardLegacy(milliSeconds); 163 } 164 } 165 moveTimeForwardBaklava(long milliSeconds)166 private void moveTimeForwardBaklava(long milliSeconds) { 167 // Drain all Messages from the queue. 168 Queue<Message> messages = new ArrayDeque<>(); 169 while (true) { 170 Message message = mTestLooperManager.poll(); 171 if (message == null) { 172 break; 173 } 174 messages.add(message); 175 } 176 177 // Repost all Messages back to the queue with a new time. 178 while (true) { 179 Message message = messages.poll(); 180 if (message == null) { 181 break; 182 } 183 184 // Ugly trick to reset the Message's "in use" flag. 185 // This is needed because the Message cannot be re-enqueued if it's 186 // marked in use. 187 message.copyFrom(message); 188 189 // Adjust the Message's delivery time. 190 long newWhen = message.getWhen() - milliSeconds; 191 if (newWhen < 0) { 192 newWhen = 0; 193 } 194 195 // Send the Message back to its Handler to be re-enqueued. 196 message.getTarget().sendMessageAtTime(message, newWhen); 197 } 198 } 199 moveTimeForwardLegacy(long milliSeconds)200 private void moveTimeForwardLegacy(long milliSeconds) { 201 try { 202 Message msg = getMessageLinkedListLegacy(); 203 while (msg != null) { 204 long updatedWhen = msg.getWhen() - milliSeconds; 205 if (updatedWhen < 0) { 206 updatedWhen = 0; 207 } 208 MESSAGE_WHEN_FIELD.set(msg, updatedWhen); 209 msg = (Message) MESSAGE_NEXT_FIELD.get(msg); 210 } 211 } catch (IllegalAccessException e) { 212 throw new RuntimeException("Access failed in TestLooper: set - Message.when", e); 213 } 214 } 215 currentTime()216 private long currentTime() { 217 return mClock.uptimeMillis(); 218 } 219 messageQueueNextLegacy()220 private Message messageQueueNextLegacy() { 221 try { 222 long now = currentTime(); 223 224 Message prevMsg = null; 225 Message msg = getMessageLinkedListLegacy(); 226 if (msg != null && msg.getTarget() == null) { 227 // Stalled by a barrier. Find the next asynchronous message in 228 // the queue. 229 do { 230 prevMsg = msg; 231 msg = (Message) MESSAGE_NEXT_FIELD.get(msg); 232 } while (msg != null && !msg.isAsynchronous()); 233 } 234 if (msg != null) { 235 if (now >= msg.getWhen()) { 236 // Got a message. 237 if (prevMsg != null) { 238 MESSAGE_NEXT_FIELD.set(prevMsg, MESSAGE_NEXT_FIELD.get(msg)); 239 } else { 240 MESSAGE_QUEUE_MESSAGES_FIELD.set(mLooper.getQueue(), 241 MESSAGE_NEXT_FIELD.get(msg)); 242 } 243 MESSAGE_NEXT_FIELD.set(msg, null); 244 MESSAGE_MARK_IN_USE_METHOD.invoke(msg); 245 return msg; 246 } 247 } 248 } catch (IllegalAccessException | InvocationTargetException e) { 249 throw new RuntimeException("Access failed in TestLooper", e); 250 } 251 252 return null; 253 } 254 255 /** 256 * @return true if there are pending messages in the message queue 257 */ isIdle()258 public boolean isIdle() { 259 if (isAtLeastBaklava()) { 260 return isIdleBaklava(); 261 } else { 262 return isIdleLegacy(); 263 } 264 } 265 isIdleBaklava()266 private boolean isIdleBaklava() { 267 Long when = mTestLooperManager.peekWhen(); 268 return when != null && currentTime() >= when; 269 } 270 isIdleLegacy()271 private synchronized boolean isIdleLegacy() { 272 Message messageList = getMessageLinkedListLegacy(); 273 return messageList != null && currentTime() >= messageList.getWhen(); 274 } 275 276 /** 277 * @return the next message in the Looper's message queue or null if there is none 278 */ nextMessage()279 public Message nextMessage() { 280 if (isAtLeastBaklava()) { 281 return nextMessageBaklava(); 282 } else { 283 return nextMessageLegacy(); 284 } 285 } 286 nextMessageBaklava()287 private Message nextMessageBaklava() { 288 if (isIdle()) { 289 return mTestLooperManager.poll(); 290 } else { 291 return null; 292 } 293 } 294 nextMessageLegacy()295 private synchronized Message nextMessageLegacy() { 296 if (isIdle()) { 297 return messageQueueNextLegacy(); 298 } else { 299 return null; 300 } 301 } 302 303 /** 304 * Dispatch the next message in the queue 305 * Asserts that there is a message in the queue 306 */ dispatchNext()307 public void dispatchNext() { 308 if (isAtLeastBaklava()) { 309 dispatchNextBaklava(); 310 } else { 311 dispatchNextLegacy(); 312 } 313 } 314 dispatchNextBaklava()315 private void dispatchNextBaklava() { 316 assertTrue(isIdle()); 317 Message msg = mTestLooperManager.poll(); 318 if (msg == null) { 319 return; 320 } 321 msg.getTarget().dispatchMessage(msg); 322 } 323 dispatchNextLegacy()324 private synchronized void dispatchNextLegacy() { 325 assertTrue(isIdle()); 326 Message msg = messageQueueNextLegacy(); 327 if (msg == null) { 328 return; 329 } 330 msg.getTarget().dispatchMessage(msg); 331 } 332 333 /** 334 * Dispatch all messages currently in the queue 335 * Will not fail if there are no messages pending 336 * 337 * @return the number of messages dispatched 338 */ dispatchAll()339 public synchronized int dispatchAll() { 340 int count = 0; 341 while (isIdle()) { 342 dispatchNext(); 343 ++count; 344 } 345 return count; 346 } 347 348 public interface Clock { uptimeMillis()349 long uptimeMillis(); 350 } 351 352 /** 353 * Thread used to dispatch messages when the main thread is blocked waiting for a response. 354 */ 355 private class AutoDispatchThread extends Thread { 356 private static final int MAX_LOOPS = 100; 357 private static final int LOOP_SLEEP_TIME_MS = 10; 358 359 private RuntimeException mAutoDispatchException = null; 360 361 /** 362 * Run method for the auto dispatch thread. 363 * The thread loops a maximum of MAX_LOOPS times with a 10ms sleep between loops. 364 * The thread continues looping and attempting to dispatch all messages until 365 * {@link #stopAutoDispatch()} has been invoked. 366 */ 367 @Override run()368 public void run() { 369 int dispatchCount = 0; 370 for (int i = 0; i < MAX_LOOPS; i++) { 371 try { 372 dispatchCount += dispatchAll(); 373 } catch (RuntimeException e) { 374 mAutoDispatchException = e; 375 return; 376 } 377 Log.d(TAG, "dispatched " + dispatchCount + " messages"); 378 try { 379 Thread.sleep(LOOP_SLEEP_TIME_MS); 380 } catch (InterruptedException e) { 381 if (dispatchCount == 0) { 382 Log.e(TAG, "stopAutoDispatch called before any messages were dispatched."); 383 mAutoDispatchException = new IllegalStateException( 384 "stopAutoDispatch called before any messages were dispatched."); 385 } 386 return; 387 } 388 } 389 if (dispatchCount == 0) { 390 Log.e(TAG, "AutoDispatchThread did not dispatch any messages."); 391 mAutoDispatchException = new IllegalStateException( 392 "TestLooper did not dispatch any messages before exiting."); 393 } 394 } 395 396 /** 397 * Method allowing the TestLooper to pass any exceptions thrown by the thread to be passed 398 * to the main thread. 399 * 400 * @return RuntimeException Exception created by stopping without dispatching a message 401 */ getException()402 public RuntimeException getException() { 403 return mAutoDispatchException; 404 } 405 } 406 407 /** 408 * Create and start a new AutoDispatchThread if one is not already running. 409 */ startAutoDispatch()410 public void startAutoDispatch() { 411 if (mAutoDispatchThread != null) { 412 throw new IllegalStateException( 413 "startAutoDispatch called with the AutoDispatchThread already running."); 414 } 415 mAutoDispatchThread = new AutoDispatchThread(); 416 mAutoDispatchThread.start(); 417 } 418 419 /** 420 * If an AutoDispatchThread is currently running, stop and clean up. 421 */ stopAutoDispatch()422 public void stopAutoDispatch() { 423 if (mAutoDispatchThread != null) { 424 if (mAutoDispatchThread.isAlive()) { 425 mAutoDispatchThread.interrupt(); 426 } 427 try { 428 mAutoDispatchThread.join(); 429 } catch (InterruptedException e) { 430 // Catch exception from join. 431 } 432 433 RuntimeException e = mAutoDispatchThread.getException(); 434 mAutoDispatchThread = null; 435 if (e != null) { 436 throw e; 437 } 438 } else { 439 // stopAutoDispatch was called when startAutoDispatch has not created a new thread. 440 throw new IllegalStateException( 441 "stopAutoDispatch called without startAutoDispatch."); 442 } 443 } 444 445 /** 446 * If an AutoDispatchThread is currently running, stop and clean up. 447 * This method ignores exceptions raised for indicating that no messages were dispatched. 448 */ stopAutoDispatchAndIgnoreExceptions()449 public void stopAutoDispatchAndIgnoreExceptions() { 450 try { 451 stopAutoDispatch(); 452 } catch (IllegalStateException e) { 453 Log.e(TAG, "stopAutoDispatch", e); 454 } 455 456 } 457 } 458