/*
 * 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 Instrumentation.acquireLooperManager. Until release() is called, the Looper thread will not execute any messages except when execute(Message) is called. The test code may use next() to acquire messages that have been queued to this Looper/MessageQueue and then execute to run any that desires.

/** * 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
/** * @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 MessageQueue this object is wrapping.
/** * Returns the {@link MessageQueue} this object is wrapping. */
public MessageQueue getMessageQueue() { checkReleased(); return mQueue; }
@removed
/** @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 recycle(Message) on the message when all interactions with it have completed.

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

/** * 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 next() has been parsed and should be recycled.
/** * 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.
Params:
/** * 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.
Params:
/** * 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; } }