blob: 5e7549fa67d83000f8670466256391669dfeb24f [file] [log] [blame]
Jason Monk5be50f72017-03-10 10:57:34 -05001/*
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
15package android.os;
16
17import android.util.ArraySet;
18
19import 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 */
31public 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 */
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 */
Jeff Sharkey000ce802017-04-29 13:13:27 -060061 public MessageQueue getMessageQueue() {
Jason Monk5be50f72017-03-10 10:57:34 -050062 checkReleased();
63 return mQueue;
64 }
65
Jeff Sharkey000ce802017-04-29 13:13:27 -060066 /** @removed */
67 @Deprecated
68 public MessageQueue getQueue() {
69 return getMessageQueue();
70 }
71
Jason Monk5be50f72017-03-10 10:57:34 -050072 /**
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 */
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 */
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 */
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 */
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 */
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 */
166 public boolean hasMessages(Handler h, Object object, Runnable r) {
167 checkReleased();
168 return mQueue.hasMessages(h, r, object);
169 }
170
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
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
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}