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