/* * 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. *
* 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
* 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.
*
* 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;
}
}