• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.annotation.FlaggedApi;
18 import android.annotation.Nullable;
19 import android.util.ArraySet;
20 
21 import java.util.concurrent.CountDownLatch;
22 import java.util.concurrent.LinkedBlockingQueue;
23 
24 /**
25  * Blocks a looper from executing any messages, and allows the holder of this object
26  * to control when and which messages get executed until it is released.
27  * <p>
28  * A TestLooperManager should be acquired using
29  * {@link android.app.Instrumentation#acquireLooperManager}. Until {@link #release()} is called,
30  * the Looper thread will not execute any messages except when {@link #execute(Message)} is called.
31  * The test code may use {@link #next()} to acquire messages that have been queued to this
32  * {@link Looper}/{@link MessageQueue} and then {@link #execute} to run any that desires.
33  */
34 @android.ravenwood.annotation.RavenwoodKeepWholeClass
35 public class TestLooperManager {
36 
37     private static final ArraySet<Looper> sHeldLoopers = new ArraySet<>();
38 
39     private final MessageQueue mQueue;
40     private final Looper mLooper;
41     private final LinkedBlockingQueue<MessageExecution> mExecuteQueue = new LinkedBlockingQueue<>();
42     private final boolean mLooperIsMyLooper;
43 
44     // When this latch is zero, it's guaranteed that the LooperHolder Message
45     // is not in the underlying queue.
46     private final CountDownLatch mLooperHolderLatch = new CountDownLatch(1);
47 
48     private boolean mReleased;
49 
50     /**
51      * @hide
52      */
TestLooperManager(Looper looper)53     public TestLooperManager(Looper looper) {
54         synchronized (sHeldLoopers) {
55             if (sHeldLoopers.contains(looper)) {
56                 throw new RuntimeException("TestLooperManager already held for this looper");
57             }
58             sHeldLoopers.add(looper);
59         }
60         mLooper = looper;
61         mQueue = mLooper.getQueue();
62         mLooperIsMyLooper = Looper.myLooper() == looper;
63         if (!mLooperIsMyLooper) {
64             // Post a message that will keep the looper blocked as long as we are dispatching.
65             new Handler(looper).post(new LooperHolder());
66         } else {
67             mLooperHolderLatch.countDown();
68         }
69     }
70 
71     /**
72      * Returns the {@link MessageQueue} this object is wrapping.
73      */
getMessageQueue()74     public MessageQueue getMessageQueue() {
75         checkReleased();
76         return mQueue;
77     }
78 
79     /** @removed */
80     @Deprecated
getQueue()81     public MessageQueue getQueue() {
82         return getMessageQueue();
83     }
84 
85     /**
86      * Returns the next message that should be executed by this queue, may block
87      * if no messages are ready.
88      * <p>
89      * Callers should always call {@link #recycle(Message)} on the message when all
90      * interactions with it have completed.
91      */
next()92     public Message next() {
93         checkReleased();
94         waitForLooperHolder();
95         return mQueue.next();
96     }
97 
98     /**
99      * Retrieves and removes the next message in this queue.
100      * If the queue is empty, returns null.
101      * This method never blocks.
102      *
103      * <p>Callers should always call {@link #recycle(Message)} on the message when all interactions
104      * with it have completed.
105      */
106     @FlaggedApi(Flags.FLAG_MESSAGE_QUEUE_TESTABILITY)
107     @Nullable
poll()108     public Message poll() {
109         checkReleased();
110         waitForLooperHolder();
111         return mQueue.pollForTest();
112     }
113 
114     /**
115      * Retrieves, but does not remove, the values of {@link Message#when} of next message in the
116      * queue.
117      * If the queue is empty, returns null.
118      * This method never blocks.
119      */
120     @FlaggedApi(Flags.FLAG_MESSAGE_QUEUE_TESTABILITY)
121     @SuppressWarnings("AutoBoxing")  // box the primitive long, or return null to indicate no value
122     @Nullable
peekWhen()123     public Long peekWhen() {
124         checkReleased();
125         waitForLooperHolder();
126         return mQueue.peekWhenForTest();
127     }
128 
129     /**
130      * Checks whether the Looper is currently blocked on a sync barrier.
131      */
132     @FlaggedApi(Flags.FLAG_MESSAGE_QUEUE_TESTABILITY)
isBlockedOnSyncBarrier()133     public boolean isBlockedOnSyncBarrier() {
134         checkReleased();
135         waitForLooperHolder();
136         return mQueue.isBlockedOnSyncBarrier();
137     }
138 
139     /**
140      * Releases the looper to continue standard looping and processing of messages, no further
141      * interactions with TestLooperManager will be allowed after release() has been called.
142      */
release()143     public void release() {
144         synchronized (sHeldLoopers) {
145             sHeldLoopers.remove(mLooper);
146         }
147         checkReleased();
148         mReleased = true;
149         mExecuteQueue.add(new MessageExecution());
150     }
151 
152     /**
153      * Executes the given message on the Looper thread this wrapper is
154      * attached to.
155      * <p>
156      * Execution will happen on the Looper's thread (whether it is the current thread
157      * or not), but all RuntimeExceptions encountered while executing the message will
158      * be thrown on the calling thread.
159      */
execute(Message message)160     public void execute(Message message) {
161         checkReleased();
162         if (mLooper.isCurrentThread()) {
163             // This is being called from the thread it should be executed on, we can just dispatch.
164             message.target.dispatchMessage(message);
165         } else {
166             if (mLooperIsMyLooper) {
167                 throw new RuntimeException("Cannot call execute from non Looper thread");
168             }
169             MessageExecution execution = new MessageExecution();
170             execution.m = message;
171             synchronized (execution) {
172                 mExecuteQueue.add(execution);
173                 // Wait for the message to be executed.
174                 try {
175                     execution.wait();
176                 } catch (InterruptedException e) {
177                     Thread.currentThread().interrupt();
178                 }
179                 if (execution.response != null) {
180                     throw new RuntimeException(execution.response);
181                 }
182             }
183         }
184     }
185 
186     /**
187      * Called to indicate that a Message returned by {@link #next()} has been parsed
188      * and should be recycled.
189      */
recycle(Message msg)190     public void recycle(Message msg) {
191         checkReleased();
192         msg.recycleUnchecked();
193     }
194 
195     /**
196      * Returns true if there are any queued messages that match the parameters.
197      *
198      * @param h      the value of {@link Message#getTarget()}
199      * @param what   the value of {@link Message#what}
200      * @param object the value of {@link Message#obj}, null for any
201      */
hasMessages(Handler h, Object object, int what)202     public boolean hasMessages(Handler h, Object object, int what) {
203         checkReleased();
204         return mQueue.hasMessages(h, what, object);
205     }
206 
207     /**
208      * Returns true if there are any queued messages that match the parameters.
209      *
210      * @param h      the value of {@link Message#getTarget()}
211      * @param r      the value of {@link Message#getCallback()}
212      * @param object the value of {@link Message#obj}, null for any
213      */
hasMessages(Handler h, Object object, Runnable r)214     public boolean hasMessages(Handler h, Object object, Runnable r) {
215         checkReleased();
216         return mQueue.hasMessages(h, r, object);
217     }
218 
checkReleased()219     private void checkReleased() {
220         if (mReleased) {
221             throw new RuntimeException("release() has already be called");
222         }
223     }
224 
225     /**
226      * Waits until the Looper is blocked by the LooperHolder, if one was posted.
227      *
228      * After this method returns, it's guaranteed that the LooperHolder Message
229      * is not in the underlying queue.
230      */
waitForLooperHolder()231     private void waitForLooperHolder() {
232         try {
233             mLooperHolderLatch.await();
234         } catch (InterruptedException e) {
235             Thread.currentThread().interrupt();
236         }
237     }
238 
239     private class LooperHolder implements Runnable {
240         @Override
run()241         public void run() {
242             mLooperHolderLatch.countDown();
243             while (!mReleased) {
244                 try {
245                     final MessageExecution take = mExecuteQueue.take();
246                     if (take.m != null) {
247                         processMessage(take);
248                     }
249                 } catch (InterruptedException e) {
250                     Thread.currentThread().interrupt();
251                 }
252             }
253         }
254 
processMessage(MessageExecution mex)255         private void processMessage(MessageExecution mex) {
256             synchronized (mex) {
257                 try {
258                     mex.m.target.dispatchMessage(mex.m);
259                     mex.response = null;
260                 } catch (Throwable t) {
261                     mex.response = t;
262                 }
263                 mex.notifyAll();
264             }
265         }
266     }
267 
268     private static class MessageExecution {
269         private Message m;
270         private Throwable response;
271     }
272 }
273