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