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 @android.ravenwood.annotation.RavenwoodKeepWholeClass
32 public class TestLooperManager {
33 
34     private static final ArraySet<Looper> sHeldLoopers = new ArraySet<>();
35 
36     private final MessageQueue mQueue;
37     private final Looper mLooper;
38     private final LinkedBlockingQueue<MessageExecution> mExecuteQueue = new LinkedBlockingQueue<>();
39 
40     private boolean mReleased;
41     private boolean mLooperBlocked;
42 
43     /**
44      * @hide
45      */
TestLooperManager(Looper looper)46     public TestLooperManager(Looper looper) {
47         synchronized (sHeldLoopers) {
48             if (sHeldLoopers.contains(looper)) {
49                 throw new RuntimeException("TestLooperManager already held for this looper");
50             }
51             sHeldLoopers.add(looper);
52         }
53         mLooper = looper;
54         mQueue = mLooper.getQueue();
55         // Post a message that will keep the looper blocked as long as we are dispatching.
56         new Handler(looper).post(new LooperHolder());
57     }
58 
59     /**
60      * Returns the {@link MessageQueue} this object is wrapping.
61      */
getMessageQueue()62     public MessageQueue getMessageQueue() {
63         checkReleased();
64         return mQueue;
65     }
66 
67     /** @removed */
68     @Deprecated
getQueue()69     public MessageQueue getQueue() {
70         return getMessageQueue();
71     }
72 
73     /**
74      * Returns the next message that should be executed by this queue, may block
75      * if no messages are ready.
76      * <p>
77      * Callers should always call {@link #recycle(Message)} on the message when all
78      * interactions with it have completed.
79      */
next()80     public Message next() {
81         // Wait for the looper block to come up, to make sure we don't accidentally get
82         // the message for the block.
83         while (!mLooperBlocked) {
84             synchronized (this) {
85                 try {
86                     wait();
87                 } catch (InterruptedException e) {
88                 }
89             }
90         }
91         checkReleased();
92         return mQueue.next();
93     }
94 
95     /**
96      * Releases the looper to continue standard looping and processing of messages,
97      * no further interactions with TestLooperManager will be allowed after
98      * release() has been called.
99      */
release()100     public void release() {
101         synchronized (sHeldLoopers) {
102             sHeldLoopers.remove(mLooper);
103         }
104         checkReleased();
105         mReleased = true;
106         mExecuteQueue.add(new MessageExecution());
107     }
108 
109     /**
110      * Executes the given message on the Looper thread this wrapper is
111      * attached to.
112      * <p>
113      * Execution will happen on the Looper's thread (whether it is the current thread
114      * or not), but all RuntimeExceptions encountered while executing the message will
115      * be thrown on the calling thread.
116      */
execute(Message message)117     public void execute(Message message) {
118         checkReleased();
119         if (Looper.myLooper() == mLooper) {
120             // This is being called from the thread it should be executed on, we can just dispatch.
121             message.target.dispatchMessage(message);
122         } else {
123             MessageExecution execution = new MessageExecution();
124             execution.m = message;
125             synchronized (execution) {
126                 mExecuteQueue.add(execution);
127                 // Wait for the message to be executed.
128                 try {
129                     execution.wait();
130                 } catch (InterruptedException e) {
131                 }
132                 if (execution.response != null) {
133                     throw new RuntimeException(execution.response);
134                 }
135             }
136         }
137     }
138 
139     /**
140      * Called to indicate that a Message returned by {@link #next()} has been parsed
141      * and should be recycled.
142      */
recycle(Message msg)143     public void recycle(Message msg) {
144         checkReleased();
145         msg.recycleUnchecked();
146     }
147 
148     /**
149      * Returns true if there are any queued messages that match the parameters.
150      *
151      * @param h      the value of {@link Message#getTarget()}
152      * @param what   the value of {@link Message#what}
153      * @param object the value of {@link Message#obj}, null for any
154      */
hasMessages(Handler h, Object object, int what)155     public boolean hasMessages(Handler h, Object object, int what) {
156         checkReleased();
157         return mQueue.hasMessages(h, what, object);
158     }
159 
160     /**
161      * Returns true if there are any queued messages that match the parameters.
162      *
163      * @param h      the value of {@link Message#getTarget()}
164      * @param r      the value of {@link Message#getCallback()}
165      * @param object the value of {@link Message#obj}, null for any
166      */
hasMessages(Handler h, Object object, Runnable r)167     public boolean hasMessages(Handler h, Object object, Runnable r) {
168         checkReleased();
169         return mQueue.hasMessages(h, r, object);
170     }
171 
checkReleased()172     private void checkReleased() {
173         if (mReleased) {
174             throw new RuntimeException("release() has already be called");
175         }
176     }
177 
178     private class LooperHolder implements Runnable {
179         @Override
run()180         public void run() {
181             synchronized (TestLooperManager.this) {
182                 mLooperBlocked = true;
183                 TestLooperManager.this.notify();
184             }
185             while (!mReleased) {
186                 try {
187                     final MessageExecution take = mExecuteQueue.take();
188                     if (take.m != null) {
189                         processMessage(take);
190                     }
191                 } catch (InterruptedException e) {
192                 }
193             }
194             synchronized (TestLooperManager.this) {
195                 mLooperBlocked = false;
196             }
197         }
198 
processMessage(MessageExecution mex)199         private void processMessage(MessageExecution mex) {
200             synchronized (mex) {
201                 try {
202                     mex.m.target.dispatchMessage(mex.m);
203                     mex.response = null;
204                 } catch (Throwable t) {
205                     mex.response = t;
206                 }
207                 mex.notifyAll();
208             }
209         }
210     }
211 
212     private static class MessageExecution {
213         private Message m;
214         private Throwable response;
215     }
216 }
217