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