1 /*
2  * Copyright (C) 2017 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5  * except in compliance with the License. You may obtain a copy of the License at
6  *
7  *      http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software distributed under the
10  * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11  * KIND, either express or implied. See the License for the specific language governing
12  * permissions and limitations under the License.
13  */
14 
15 package android.os;
16 
17 import android.util.ArraySet;
18 
19 import java.util.concurrent.LinkedBlockingQueue;
20 
21 /**
22  * Blocks a looper from executing any messages, and allows the holder of this object
23  * to control when and which messages get executed until it is released.
24  * <p>
25  * A TestLooperManager should be acquired using
26  * {@link android.app.Instrumentation#acquireLooperManager}. Until {@link #release()} is called,
27  * the Looper thread will not execute any messages except when {@link #execute(Message)} is called.
28  * The test code may use {@link #next()} to acquire messages that have been queued to this
29  * {@link Looper}/{@link MessageQueue} and then {@link #execute} to run any that desires.
30  */
31 public class TestLooperManager {
32 
33     private static final ArraySet<Looper> sHeldLoopers = new ArraySet<>();
34 
35     private final MessageQueue mQueue;
36     private final Looper mLooper;
37     private final LinkedBlockingQueue<MessageExecution> mExecuteQueue = new LinkedBlockingQueue<>();
38 
39     private boolean mReleased;
40     private boolean mLooperBlocked;
41 
42     /**
43      * @hide
44      */
TestLooperManager(Looper looper)45     public TestLooperManager(Looper looper) {
46         synchronized (sHeldLoopers) {
47             if (sHeldLoopers.contains(looper)) {
48                 throw new RuntimeException("TestLooperManager already held for this looper");
49             }
50             sHeldLoopers.add(looper);
51         }
52         mLooper = looper;
53         mQueue = mLooper.getQueue();
54         // Post a message that will keep the looper blocked as long as we are dispatching.
55         new Handler(looper).post(new LooperHolder());
56     }
57 
58     /**
59      * Returns the {@link MessageQueue} this object is wrapping.
60      */
getMessageQueue()61     public MessageQueue getMessageQueue() {
62         checkReleased();
63         return mQueue;
64     }
65 
66     /** @removed */
67     @Deprecated
getQueue()68     public MessageQueue getQueue() {
69         return getMessageQueue();
70     }
71 
72     /**
73      * Returns the next message that should be executed by this queue, may block
74      * if no messages are ready.
75      * <p>
76      * Callers should always call {@link #recycle(Message)} on the message when all
77      * interactions with it have completed.
78      */
next()79     public Message next() {
80         // Wait for the looper block to come up, to make sure we don't accidentally get
81         // the message for the block.
82         while (!mLooperBlocked) {
83             synchronized (this) {
84                 try {
85                     wait();
86                 } catch (InterruptedException e) {
87                 }
88             }
89         }
90         checkReleased();
91         return mQueue.next();
92     }
93 
94     /**
95      * Releases the looper to continue standard looping and processing of messages,
96      * no further interactions with TestLooperManager will be allowed after
97      * release() has been called.
98      */
release()99     public void release() {
100         synchronized (sHeldLoopers) {
101             sHeldLoopers.remove(mLooper);
102         }
103         checkReleased();
104         mReleased = true;
105         mExecuteQueue.add(new MessageExecution());
106     }
107 
108     /**
109      * Executes the given message on the Looper thread this wrapper is
110      * attached to.
111      * <p>
112      * Execution will happen on the Looper's thread (whether it is the current thread
113      * or not), but all RuntimeExceptions encountered while executing the message will
114      * be thrown on the calling thread.
115      */
execute(Message message)116     public void execute(Message message) {
117         checkReleased();
118         if (Looper.myLooper() == mLooper) {
119             // This is being called from the thread it should be executed on, we can just dispatch.
120             message.target.dispatchMessage(message);
121         } else {
122             MessageExecution execution = new MessageExecution();
123             execution.m = message;
124             synchronized (execution) {
125                 mExecuteQueue.add(execution);
126                 // Wait for the message to be executed.
127                 try {
128                     execution.wait();
129                 } catch (InterruptedException e) {
130                 }
131                 if (execution.response != null) {
132                     throw new RuntimeException(execution.response);
133                 }
134             }
135         }
136     }
137 
138     /**
139      * Called to indicate that a Message returned by {@link #next()} has been parsed
140      * and should be recycled.
141      */
recycle(Message msg)142     public void recycle(Message msg) {
143         checkReleased();
144         msg.recycleUnchecked();
145     }
146 
147     /**
148      * Returns true if there are any queued messages that match the parameters.
149      *
150      * @param h      the value of {@link Message#getTarget()}
151      * @param what   the value of {@link Message#what}
152      * @param object the value of {@link Message#obj}, null for any
153      */
hasMessages(Handler h, Object object, int what)154     public boolean hasMessages(Handler h, Object object, int what) {
155         checkReleased();
156         return mQueue.hasMessages(h, what, object);
157     }
158 
159     /**
160      * Returns true if there are any queued messages that match the parameters.
161      *
162      * @param h      the value of {@link Message#getTarget()}
163      * @param r      the value of {@link Message#getCallback()}
164      * @param object the value of {@link Message#obj}, null for any
165      */
hasMessages(Handler h, Object object, Runnable r)166     public boolean hasMessages(Handler h, Object object, Runnable r) {
167         checkReleased();
168         return mQueue.hasMessages(h, r, object);
169     }
170 
checkReleased()171     private void checkReleased() {
172         if (mReleased) {
173             throw new RuntimeException("release() has already be called");
174         }
175     }
176 
177     private class LooperHolder implements Runnable {
178         @Override
run()179         public void run() {
180             synchronized (TestLooperManager.this) {
181                 mLooperBlocked = true;
182                 TestLooperManager.this.notify();
183             }
184             while (!mReleased) {
185                 try {
186                     final MessageExecution take = mExecuteQueue.take();
187                     if (take.m != null) {
188                         processMessage(take);
189                     }
190                 } catch (InterruptedException e) {
191                 }
192             }
193             synchronized (TestLooperManager.this) {
194                 mLooperBlocked = false;
195             }
196         }
197 
processMessage(MessageExecution mex)198         private void processMessage(MessageExecution mex) {
199             synchronized (mex) {
200                 try {
201                     mex.m.target.dispatchMessage(mex.m);
202                     mex.response = null;
203                 } catch (Throwable t) {
204                     mex.response = t;
205                 }
206                 mex.notifyAll();
207             }
208         }
209     }
210 
211     private static class MessageExecution {
212         private Message m;
213         private Throwable response;
214     }
215 }
216