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.util.ArraySet; 18 19 import java.util.concurrent.LinkedBlockingQueue; 20 21 /** 22 * Blocks a looper from executing any messages, and allows the holder of this object 23 * to control when and which messages get executed until it is released. 24 * <p> 25 * A TestLooperManager should be acquired using 26 * {@link android.app.Instrumentation#acquireLooperManager}. Until {@link #release()} is called, 27 * the Looper thread will not execute any messages except when {@link #execute(Message)} is called. 28 * The test code may use {@link #next()} to acquire messages that have been queued to this 29 * {@link Looper}/{@link MessageQueue} and then {@link #execute} to run any that desires. 30 */ 31 @android.ravenwood.annotation.RavenwoodKeepWholeClass 32 public class TestLooperManager { 33 34 private static final ArraySet<Looper> sHeldLoopers = new ArraySet<>(); 35 36 private final MessageQueue mQueue; 37 private final Looper mLooper; 38 private final LinkedBlockingQueue<MessageExecution> mExecuteQueue = new LinkedBlockingQueue<>(); 39 40 private boolean mReleased; 41 private boolean mLooperBlocked; 42 43 /** 44 * @hide 45 */ TestLooperManager(Looper looper)46 public TestLooperManager(Looper looper) { 47 synchronized (sHeldLoopers) { 48 if (sHeldLoopers.contains(looper)) { 49 throw new RuntimeException("TestLooperManager already held for this looper"); 50 } 51 sHeldLoopers.add(looper); 52 } 53 mLooper = looper; 54 mQueue = mLooper.getQueue(); 55 // Post a message that will keep the looper blocked as long as we are dispatching. 56 new Handler(looper).post(new LooperHolder()); 57 } 58 59 /** 60 * Returns the {@link MessageQueue} this object is wrapping. 61 */ getMessageQueue()62 public MessageQueue getMessageQueue() { 63 checkReleased(); 64 return mQueue; 65 } 66 67 /** @removed */ 68 @Deprecated getQueue()69 public MessageQueue getQueue() { 70 return getMessageQueue(); 71 } 72 73 /** 74 * Returns the next message that should be executed by this queue, may block 75 * if no messages are ready. 76 * <p> 77 * Callers should always call {@link #recycle(Message)} on the message when all 78 * interactions with it have completed. 79 */ next()80 public Message next() { 81 // Wait for the looper block to come up, to make sure we don't accidentally get 82 // the message for the block. 83 while (!mLooperBlocked) { 84 synchronized (this) { 85 try { 86 wait(); 87 } catch (InterruptedException e) { 88 } 89 } 90 } 91 checkReleased(); 92 return mQueue.next(); 93 } 94 95 /** 96 * Releases the looper to continue standard looping and processing of messages, 97 * no further interactions with TestLooperManager will be allowed after 98 * release() has been called. 99 */ release()100 public void release() { 101 synchronized (sHeldLoopers) { 102 sHeldLoopers.remove(mLooper); 103 } 104 checkReleased(); 105 mReleased = true; 106 mExecuteQueue.add(new MessageExecution()); 107 } 108 109 /** 110 * Executes the given message on the Looper thread this wrapper is 111 * attached to. 112 * <p> 113 * Execution will happen on the Looper's thread (whether it is the current thread 114 * or not), but all RuntimeExceptions encountered while executing the message will 115 * be thrown on the calling thread. 116 */ execute(Message message)117 public void execute(Message message) { 118 checkReleased(); 119 if (Looper.myLooper() == mLooper) { 120 // This is being called from the thread it should be executed on, we can just dispatch. 121 message.target.dispatchMessage(message); 122 } else { 123 MessageExecution execution = new MessageExecution(); 124 execution.m = message; 125 synchronized (execution) { 126 mExecuteQueue.add(execution); 127 // Wait for the message to be executed. 128 try { 129 execution.wait(); 130 } catch (InterruptedException e) { 131 } 132 if (execution.response != null) { 133 throw new RuntimeException(execution.response); 134 } 135 } 136 } 137 } 138 139 /** 140 * Called to indicate that a Message returned by {@link #next()} has been parsed 141 * and should be recycled. 142 */ recycle(Message msg)143 public void recycle(Message msg) { 144 checkReleased(); 145 msg.recycleUnchecked(); 146 } 147 148 /** 149 * Returns true if there are any queued messages that match the parameters. 150 * 151 * @param h the value of {@link Message#getTarget()} 152 * @param what the value of {@link Message#what} 153 * @param object the value of {@link Message#obj}, null for any 154 */ hasMessages(Handler h, Object object, int what)155 public boolean hasMessages(Handler h, Object object, int what) { 156 checkReleased(); 157 return mQueue.hasMessages(h, what, object); 158 } 159 160 /** 161 * Returns true if there are any queued messages that match the parameters. 162 * 163 * @param h the value of {@link Message#getTarget()} 164 * @param r the value of {@link Message#getCallback()} 165 * @param object the value of {@link Message#obj}, null for any 166 */ hasMessages(Handler h, Object object, Runnable r)167 public boolean hasMessages(Handler h, Object object, Runnable r) { 168 checkReleased(); 169 return mQueue.hasMessages(h, r, object); 170 } 171 checkReleased()172 private void checkReleased() { 173 if (mReleased) { 174 throw new RuntimeException("release() has already be called"); 175 } 176 } 177 178 private class LooperHolder implements Runnable { 179 @Override run()180 public void run() { 181 synchronized (TestLooperManager.this) { 182 mLooperBlocked = true; 183 TestLooperManager.this.notify(); 184 } 185 while (!mReleased) { 186 try { 187 final MessageExecution take = mExecuteQueue.take(); 188 if (take.m != null) { 189 processMessage(take); 190 } 191 } catch (InterruptedException e) { 192 } 193 } 194 synchronized (TestLooperManager.this) { 195 mLooperBlocked = false; 196 } 197 } 198 processMessage(MessageExecution mex)199 private void processMessage(MessageExecution mex) { 200 synchronized (mex) { 201 try { 202 mex.m.target.dispatchMessage(mex.m); 203 mex.response = null; 204 } catch (Throwable t) { 205 mex.response = t; 206 } 207 mex.notifyAll(); 208 } 209 } 210 } 211 212 private static class MessageExecution { 213 private Message m; 214 private Throwable response; 215 } 216 } 217