217 lines
7.1 KiB
Java
217 lines
7.1 KiB
Java
/*
|
|
* 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.
|
|
*/
|
|
@android.ravenwood.annotation.RavenwoodKeepWholeClass
|
|
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;
|
|
}
|
|
}
|