/* * 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 sHeldLoopers = new ArraySet<>(); private final MessageQueue mQueue; private final Looper mLooper; private final LinkedBlockingQueue 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. *

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