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