1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 * except in compliance with the License. You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software distributed under the 10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 11 * KIND, either express or implied. See the License for the specific language governing 12 * permissions and limitations under the License. 13 */ 14 15 package android.testing; 16 17 import android.os.Handler; 18 import android.os.HandlerThread; 19 import android.os.Looper; 20 import android.os.Message; 21 import android.os.MessageQueue; 22 import android.os.TestLooperManager; 23 import android.util.ArrayMap; 24 25 import androidx.test.InstrumentationRegistry; 26 27 import org.junit.runners.model.FrameworkMethod; 28 29 import java.lang.annotation.ElementType; 30 import java.lang.annotation.Retention; 31 import java.lang.annotation.RetentionPolicy; 32 import java.lang.annotation.Target; 33 import java.lang.reflect.Field; 34 import java.util.Map; 35 36 /** 37 * This is a wrapper around {@link TestLooperManager} to make it easier to manage 38 * and provide an easy annotation for use with tests. 39 * 40 * @see TestableLooperTest TestableLooperTest for examples. 41 */ 42 public class TestableLooper { 43 44 /** 45 * Whether to hold onto the main thread through all tests in an attempt to 46 * catch crashes. 47 */ 48 public static final boolean HOLD_MAIN_THREAD = false; 49 private static final Field MESSAGE_QUEUE_MESSAGES_FIELD; 50 private static final Field MESSAGE_NEXT_FIELD; 51 private static final Field MESSAGE_WHEN_FIELD; 52 53 private Looper mLooper; 54 private MessageQueue mQueue; 55 private MessageHandler mMessageHandler; 56 57 private Handler mHandler; 58 private Runnable mEmptyMessage; 59 private TestLooperManager mQueueWrapper; 60 61 static { 62 try { 63 MESSAGE_QUEUE_MESSAGES_FIELD = MessageQueue.class.getDeclaredField("mMessages"); 64 MESSAGE_QUEUE_MESSAGES_FIELD.setAccessible(true); 65 MESSAGE_NEXT_FIELD = Message.class.getDeclaredField("next"); 66 MESSAGE_NEXT_FIELD.setAccessible(true); 67 MESSAGE_WHEN_FIELD = Message.class.getDeclaredField("when"); 68 MESSAGE_WHEN_FIELD.setAccessible(true); 69 } catch (NoSuchFieldException e) { 70 throw new RuntimeException("Failed to initialize TestableLooper", e); 71 } 72 } 73 TestableLooper(Looper l)74 public TestableLooper(Looper l) throws Exception { 75 this(acquireLooperManager(l), l); 76 } 77 TestableLooper(TestLooperManager wrapper, Looper l)78 private TestableLooper(TestLooperManager wrapper, Looper l) { 79 mQueueWrapper = wrapper; 80 setupQueue(l); 81 } 82 TestableLooper(Looper looper, boolean b)83 private TestableLooper(Looper looper, boolean b) { 84 setupQueue(looper); 85 } 86 getLooper()87 public Looper getLooper() { 88 return mLooper; 89 } 90 setupQueue(Looper l)91 private void setupQueue(Looper l) { 92 mLooper = l; 93 mQueue = mLooper.getQueue(); 94 mHandler = new Handler(mLooper); 95 } 96 97 /** 98 * Must be called to release the looper when the test is complete, otherwise 99 * the looper will not be available for any subsequent tests. This is 100 * automatically handled for tests using {@link RunWithLooper}. 101 */ destroy()102 public void destroy() { 103 mQueueWrapper.release(); 104 if (HOLD_MAIN_THREAD && mLooper == Looper.getMainLooper()) { 105 TestableInstrumentation.releaseMain(); 106 } 107 } 108 109 /** 110 * Sets a callback for all messages processed on this TestableLooper. 111 * 112 * @see {@link MessageHandler} 113 */ setMessageHandler(MessageHandler handler)114 public void setMessageHandler(MessageHandler handler) { 115 mMessageHandler = handler; 116 } 117 118 /** 119 * Parse num messages from the message queue. 120 * 121 * @param num Number of messages to parse 122 */ processMessages(int num)123 public int processMessages(int num) { 124 for (int i = 0; i < num; i++) { 125 if (!parseMessageInt()) { 126 return i + 1; 127 } 128 } 129 return num; 130 } 131 132 /** 133 * Process messages in the queue until no more are found. 134 */ processAllMessages()135 public void processAllMessages() { 136 while (processQueuedMessages() != 0) ; 137 } 138 moveTimeForward(long milliSeconds)139 public void moveTimeForward(long milliSeconds) { 140 try { 141 Message msg = getMessageLinkedList(); 142 while (msg != null) { 143 long updatedWhen = msg.getWhen() - milliSeconds; 144 if (updatedWhen < 0) { 145 updatedWhen = 0; 146 } 147 MESSAGE_WHEN_FIELD.set(msg, updatedWhen); 148 msg = (Message) MESSAGE_NEXT_FIELD.get(msg); 149 } 150 } catch (IllegalAccessException e) { 151 throw new RuntimeException("Access failed in TestableLooper: set - Message.when", e); 152 } 153 } 154 getMessageLinkedList()155 private Message getMessageLinkedList() { 156 try { 157 MessageQueue queue = mLooper.getQueue(); 158 return (Message) MESSAGE_QUEUE_MESSAGES_FIELD.get(queue); 159 } catch (IllegalAccessException e) { 160 throw new RuntimeException( 161 "Access failed in TestableLooper: get - MessageQueue.mMessages", 162 e); 163 } 164 } 165 processQueuedMessages()166 private int processQueuedMessages() { 167 int count = 0; 168 mEmptyMessage = () -> { }; 169 mHandler.post(mEmptyMessage); 170 waitForMessage(mQueueWrapper, mHandler, mEmptyMessage); 171 while (parseMessageInt()) count++; 172 return count; 173 } 174 parseMessageInt()175 private boolean parseMessageInt() { 176 try { 177 Message result = mQueueWrapper.next(); 178 if (result != null) { 179 // This is a break message. 180 if (result.getCallback() == mEmptyMessage) { 181 mQueueWrapper.recycle(result); 182 return false; 183 } 184 185 if (mMessageHandler != null) { 186 if (mMessageHandler.onMessageHandled(result)) { 187 mQueueWrapper.execute(result); 188 mQueueWrapper.recycle(result); 189 } else { 190 mQueueWrapper.recycle(result); 191 // Message handler indicated it doesn't want us to continue. 192 return false; 193 } 194 } else { 195 mQueueWrapper.execute(result); 196 mQueueWrapper.recycle(result); 197 } 198 } else { 199 // No messages, don't continue parsing 200 return false; 201 } 202 } catch (Exception e) { 203 throw new RuntimeException(e); 204 } 205 return true; 206 } 207 208 /** 209 * Runs an executable with myLooper set and processes all messages added. 210 */ runWithLooper(RunnableWithException runnable)211 public void runWithLooper(RunnableWithException runnable) throws Exception { 212 new Handler(getLooper()).post(() -> { 213 try { 214 runnable.run(); 215 } catch (Exception e) { 216 throw new RuntimeException(e); 217 } 218 }); 219 processAllMessages(); 220 } 221 222 public interface RunnableWithException { run()223 void run() throws Exception; 224 } 225 226 /** 227 * Annotation that tells the {@link AndroidTestingRunner} to create a TestableLooper and 228 * run this test/class on that thread. The {@link TestableLooper} can be acquired using 229 * {@link #get(Object)}. 230 */ 231 @Retention(RetentionPolicy.RUNTIME) 232 @Target({ElementType.METHOD, ElementType.TYPE}) 233 public @interface RunWithLooper { setAsMainLooper()234 boolean setAsMainLooper() default false; 235 } 236 waitForMessage(TestLooperManager queueWrapper, Handler handler, Runnable execute)237 private static void waitForMessage(TestLooperManager queueWrapper, Handler handler, 238 Runnable execute) { 239 for (int i = 0; i < 10; i++) { 240 if (!queueWrapper.hasMessages(handler, null, execute)) { 241 try { 242 Thread.sleep(1); 243 } catch (InterruptedException e) { 244 } 245 } 246 } 247 if (!queueWrapper.hasMessages(handler, null, execute)) { 248 throw new RuntimeException("Message didn't queue..."); 249 } 250 } 251 acquireLooperManager(Looper l)252 private static TestLooperManager acquireLooperManager(Looper l) { 253 if (HOLD_MAIN_THREAD && l == Looper.getMainLooper()) { 254 TestableInstrumentation.acquireMain(); 255 } 256 return InstrumentationRegistry.getInstrumentation().acquireLooperManager(l); 257 } 258 259 private static final Map<Object, TestableLooper> sLoopers = new ArrayMap<>(); 260 261 /** 262 * For use with {@link RunWithLooper}, used to get the TestableLooper that was 263 * automatically created for this test. 264 */ get(Object test)265 public static TestableLooper get(Object test) { 266 return sLoopers.get(test); 267 } 268 remove(Object test)269 public static void remove(Object test) { 270 sLoopers.remove(test); 271 } 272 273 static class LooperFrameworkMethod extends FrameworkMethod { 274 private HandlerThread mHandlerThread; 275 276 private final TestableLooper mTestableLooper; 277 private final Looper mLooper; 278 private final Handler mHandler; 279 LooperFrameworkMethod(FrameworkMethod base, boolean setAsMain, Object test)280 public LooperFrameworkMethod(FrameworkMethod base, boolean setAsMain, Object test) { 281 super(base.getMethod()); 282 try { 283 mLooper = setAsMain ? Looper.getMainLooper() : createLooper(); 284 mTestableLooper = new TestableLooper(mLooper, false); 285 if (!setAsMain) { 286 mTestableLooper.getLooper().getThread().setName(test.getClass().getName()); 287 } 288 } catch (Exception e) { 289 throw new RuntimeException(e); 290 } 291 sLoopers.put(test, mTestableLooper); 292 mHandler = new Handler(mLooper); 293 } 294 LooperFrameworkMethod(TestableLooper other, FrameworkMethod base)295 public LooperFrameworkMethod(TestableLooper other, FrameworkMethod base) { 296 super(base.getMethod()); 297 mLooper = other.mLooper; 298 mTestableLooper = other; 299 mHandler = Handler.createAsync(mLooper); 300 } 301 get(FrameworkMethod base, boolean setAsMain, Object test)302 public static FrameworkMethod get(FrameworkMethod base, boolean setAsMain, Object test) { 303 if (sLoopers.containsKey(test)) { 304 return new LooperFrameworkMethod(sLoopers.get(test), base); 305 } 306 return new LooperFrameworkMethod(base, setAsMain, test); 307 } 308 309 @Override invokeExplosively(Object target, Object... params)310 public Object invokeExplosively(Object target, Object... params) throws Throwable { 311 if (Looper.myLooper() == mLooper) { 312 // Already on the right thread from another statement, just execute then. 313 return super.invokeExplosively(target, params); 314 } 315 boolean set = mTestableLooper.mQueueWrapper == null; 316 if (set) { 317 mTestableLooper.mQueueWrapper = acquireLooperManager(mLooper); 318 } 319 try { 320 Object[] ret = new Object[1]; 321 // Run the execution on the looper thread. 322 Runnable execute = () -> { 323 try { 324 ret[0] = super.invokeExplosively(target, params); 325 } catch (Throwable throwable) { 326 throw new LooperException(throwable); 327 } 328 }; 329 Message m = Message.obtain(mHandler, execute); 330 331 // Dispatch our message. 332 try { 333 mTestableLooper.mQueueWrapper.execute(m); 334 } catch (LooperException e) { 335 throw e.getSource(); 336 } catch (RuntimeException re) { 337 // If the TestLooperManager has to post, it will wrap what it throws in a 338 // RuntimeException, make sure we grab the actual source. 339 if (re.getCause() instanceof LooperException) { 340 throw ((LooperException) re.getCause()).getSource(); 341 } else { 342 throw re.getCause(); 343 } 344 } finally { 345 m.recycle(); 346 } 347 return ret[0]; 348 } finally { 349 if (set) { 350 mTestableLooper.mQueueWrapper.release(); 351 mTestableLooper.mQueueWrapper = null; 352 if (HOLD_MAIN_THREAD && mLooper == Looper.getMainLooper()) { 353 TestableInstrumentation.releaseMain(); 354 } 355 } 356 } 357 } 358 createLooper()359 private Looper createLooper() { 360 // TODO: Find way to share these. 361 mHandlerThread = new HandlerThread(TestableLooper.class.getSimpleName()); 362 mHandlerThread.start(); 363 return mHandlerThread.getLooper(); 364 } 365 366 @Override finalize()367 protected void finalize() throws Throwable { 368 super.finalize(); 369 if (mHandlerThread != null) { 370 mHandlerThread.quit(); 371 } 372 } 373 374 private static class LooperException extends RuntimeException { 375 private final Throwable mSource; 376 LooperException(Throwable t)377 public LooperException(Throwable t) { 378 mSource = t; 379 } 380 getSource()381 public Throwable getSource() { 382 return mSource; 383 } 384 } 385 } 386 387 /** 388 * Callback to control the execution of messages on the looper, when set with 389 * {@link #setMessageHandler(MessageHandler)} then {@link #onMessageHandled(Message)} 390 * will get called back for every message processed on the {@link TestableLooper}. 391 */ 392 public interface MessageHandler { 393 /** 394 * Return true to have the message executed and delivered to target. 395 * Return false to not execute the message and stop executing messages. 396 */ onMessageHandled(Message m)397 boolean onMessageHandled(Message m); 398 } 399 } 400