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.os; 16 17 import android.annotation.FlaggedApi; 18 import android.annotation.Nullable; 19 import android.util.ArraySet; 20 21 import java.util.concurrent.CountDownLatch; 22 import java.util.concurrent.LinkedBlockingQueue; 23 24 /** 25 * Blocks a looper from executing any messages, and allows the holder of this object 26 * to control when and which messages get executed until it is released. 27 * <p> 28 * A TestLooperManager should be acquired using 29 * {@link android.app.Instrumentation#acquireLooperManager}. Until {@link #release()} is called, 30 * the Looper thread will not execute any messages except when {@link #execute(Message)} is called. 31 * The test code may use {@link #next()} to acquire messages that have been queued to this 32 * {@link Looper}/{@link MessageQueue} and then {@link #execute} to run any that desires. 33 */ 34 @android.ravenwood.annotation.RavenwoodKeepWholeClass 35 public class TestLooperManager { 36 37 private static final ArraySet<Looper> sHeldLoopers = new ArraySet<>(); 38 39 private final MessageQueue mQueue; 40 private final Looper mLooper; 41 private final LinkedBlockingQueue<MessageExecution> mExecuteQueue = new LinkedBlockingQueue<>(); 42 private final boolean mLooperIsMyLooper; 43 44 // When this latch is zero, it's guaranteed that the LooperHolder Message 45 // is not in the underlying queue. 46 private final CountDownLatch mLooperHolderLatch = new CountDownLatch(1); 47 48 private boolean mReleased; 49 50 /** 51 * @hide 52 */ TestLooperManager(Looper looper)53 public TestLooperManager(Looper looper) { 54 synchronized (sHeldLoopers) { 55 if (sHeldLoopers.contains(looper)) { 56 throw new RuntimeException("TestLooperManager already held for this looper"); 57 } 58 sHeldLoopers.add(looper); 59 } 60 mLooper = looper; 61 mQueue = mLooper.getQueue(); 62 mLooperIsMyLooper = Looper.myLooper() == looper; 63 if (!mLooperIsMyLooper) { 64 // Post a message that will keep the looper blocked as long as we are dispatching. 65 new Handler(looper).post(new LooperHolder()); 66 } else { 67 mLooperHolderLatch.countDown(); 68 } 69 } 70 71 /** 72 * Returns the {@link MessageQueue} this object is wrapping. 73 */ getMessageQueue()74 public MessageQueue getMessageQueue() { 75 checkReleased(); 76 return mQueue; 77 } 78 79 /** @removed */ 80 @Deprecated getQueue()81 public MessageQueue getQueue() { 82 return getMessageQueue(); 83 } 84 85 /** 86 * Returns the next message that should be executed by this queue, may block 87 * if no messages are ready. 88 * <p> 89 * Callers should always call {@link #recycle(Message)} on the message when all 90 * interactions with it have completed. 91 */ next()92 public Message next() { 93 checkReleased(); 94 waitForLooperHolder(); 95 return mQueue.next(); 96 } 97 98 /** 99 * Retrieves and removes the next message in this queue. 100 * If the queue is empty, returns null. 101 * This method never blocks. 102 * 103 * <p>Callers should always call {@link #recycle(Message)} on the message when all interactions 104 * with it have completed. 105 */ 106 @FlaggedApi(Flags.FLAG_MESSAGE_QUEUE_TESTABILITY) 107 @Nullable poll()108 public Message poll() { 109 checkReleased(); 110 waitForLooperHolder(); 111 return mQueue.pollForTest(); 112 } 113 114 /** 115 * Retrieves, but does not remove, the values of {@link Message#when} of next message in the 116 * queue. 117 * If the queue is empty, returns null. 118 * This method never blocks. 119 */ 120 @FlaggedApi(Flags.FLAG_MESSAGE_QUEUE_TESTABILITY) 121 @SuppressWarnings("AutoBoxing") // box the primitive long, or return null to indicate no value 122 @Nullable peekWhen()123 public Long peekWhen() { 124 checkReleased(); 125 waitForLooperHolder(); 126 return mQueue.peekWhenForTest(); 127 } 128 129 /** 130 * Checks whether the Looper is currently blocked on a sync barrier. 131 */ 132 @FlaggedApi(Flags.FLAG_MESSAGE_QUEUE_TESTABILITY) isBlockedOnSyncBarrier()133 public boolean isBlockedOnSyncBarrier() { 134 checkReleased(); 135 waitForLooperHolder(); 136 return mQueue.isBlockedOnSyncBarrier(); 137 } 138 139 /** 140 * Releases the looper to continue standard looping and processing of messages, no further 141 * interactions with TestLooperManager will be allowed after release() has been called. 142 */ release()143 public void release() { 144 synchronized (sHeldLoopers) { 145 sHeldLoopers.remove(mLooper); 146 } 147 checkReleased(); 148 mReleased = true; 149 mExecuteQueue.add(new MessageExecution()); 150 } 151 152 /** 153 * Executes the given message on the Looper thread this wrapper is 154 * attached to. 155 * <p> 156 * Execution will happen on the Looper's thread (whether it is the current thread 157 * or not), but all RuntimeExceptions encountered while executing the message will 158 * be thrown on the calling thread. 159 */ execute(Message message)160 public void execute(Message message) { 161 checkReleased(); 162 if (mLooper.isCurrentThread()) { 163 // This is being called from the thread it should be executed on, we can just dispatch. 164 message.target.dispatchMessage(message); 165 } else { 166 if (mLooperIsMyLooper) { 167 throw new RuntimeException("Cannot call execute from non Looper thread"); 168 } 169 MessageExecution execution = new MessageExecution(); 170 execution.m = message; 171 synchronized (execution) { 172 mExecuteQueue.add(execution); 173 // Wait for the message to be executed. 174 try { 175 execution.wait(); 176 } catch (InterruptedException e) { 177 Thread.currentThread().interrupt(); 178 } 179 if (execution.response != null) { 180 throw new RuntimeException(execution.response); 181 } 182 } 183 } 184 } 185 186 /** 187 * Called to indicate that a Message returned by {@link #next()} has been parsed 188 * and should be recycled. 189 */ recycle(Message msg)190 public void recycle(Message msg) { 191 checkReleased(); 192 msg.recycleUnchecked(); 193 } 194 195 /** 196 * Returns true if there are any queued messages that match the parameters. 197 * 198 * @param h the value of {@link Message#getTarget()} 199 * @param what the value of {@link Message#what} 200 * @param object the value of {@link Message#obj}, null for any 201 */ hasMessages(Handler h, Object object, int what)202 public boolean hasMessages(Handler h, Object object, int what) { 203 checkReleased(); 204 return mQueue.hasMessages(h, what, object); 205 } 206 207 /** 208 * Returns true if there are any queued messages that match the parameters. 209 * 210 * @param h the value of {@link Message#getTarget()} 211 * @param r the value of {@link Message#getCallback()} 212 * @param object the value of {@link Message#obj}, null for any 213 */ hasMessages(Handler h, Object object, Runnable r)214 public boolean hasMessages(Handler h, Object object, Runnable r) { 215 checkReleased(); 216 return mQueue.hasMessages(h, r, object); 217 } 218 checkReleased()219 private void checkReleased() { 220 if (mReleased) { 221 throw new RuntimeException("release() has already be called"); 222 } 223 } 224 225 /** 226 * Waits until the Looper is blocked by the LooperHolder, if one was posted. 227 * 228 * After this method returns, it's guaranteed that the LooperHolder Message 229 * is not in the underlying queue. 230 */ waitForLooperHolder()231 private void waitForLooperHolder() { 232 try { 233 mLooperHolderLatch.await(); 234 } catch (InterruptedException e) { 235 Thread.currentThread().interrupt(); 236 } 237 } 238 239 private class LooperHolder implements Runnable { 240 @Override run()241 public void run() { 242 mLooperHolderLatch.countDown(); 243 while (!mReleased) { 244 try { 245 final MessageExecution take = mExecuteQueue.take(); 246 if (take.m != null) { 247 processMessage(take); 248 } 249 } catch (InterruptedException e) { 250 Thread.currentThread().interrupt(); 251 } 252 } 253 } 254 processMessage(MessageExecution mex)255 private void processMessage(MessageExecution mex) { 256 synchronized (mex) { 257 try { 258 mex.m.target.dispatchMessage(mex.m); 259 mex.response = null; 260 } catch (Throwable t) { 261 mex.response = t; 262 } 263 mex.notifyAll(); 264 } 265 } 266 } 267 268 private static class MessageExecution { 269 private Message m; 270 private Throwable response; 271 } 272 } 273