• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.bluetooth;
18 
19 import static org.junit.Assert.assertTrue;
20 
21 import android.os.Build;
22 import android.os.Handler;
23 import android.os.Looper;
24 import android.os.Message;
25 import android.os.MessageQueue;
26 import android.os.SystemClock;
27 import android.os.TestLooperManager;
28 import android.util.Log;
29 
30 import com.android.modules.utils.HandlerExecutor;
31 
32 import com.google.common.util.concurrent.Uninterruptibles;
33 
34 import androidx.test.platform.app.InstrumentationRegistry;
35 
36 import java.lang.reflect.Constructor;
37 import java.lang.reflect.Field;
38 import java.lang.reflect.InvocationTargetException;
39 import java.lang.reflect.Method;
40 import java.util.ArrayDeque;
41 import java.util.Queue;
42 import java.util.concurrent.Executor;
43 
44 /**
45  * Creates a looper whose message queue can be manipulated This allows testing code that uses a
46  * looper to dispatch messages in a deterministic manner Creating a TestLooper will also install it
47  * as the looper for the current thread
48  *
49  * @deprecated Use {@link android.os.TestLooperManager} or {@link
50  *     org.robolectric.shadows.ShadowLooper} instead.
51  *     This class is not actively maintained.
52  *     Both of the recommended alternatives allow fine control of execution.
53  *     The Robolectric class also allows advancing time.
54  */
55 public class TestLooper {
56     private static final String TAG = TestLooper.class.getSimpleName();
57 
58     private final Looper mLooper;
59     private final TestLooperManager mTestLooperManager;
60     private final Clock mClock;
61 
62     private static final Constructor<Looper> LOOPER_CONSTRUCTOR;
63     private static final Field THREAD_LOCAL_LOOPER_FIELD;
64     private static final Field MESSAGE_QUEUE_MESSAGES_FIELD;
65     private static final Field MESSAGE_NEXT_FIELD;
66     private static final Field MESSAGE_WHEN_FIELD;
67     private static final Method MESSAGE_MARK_IN_USE_METHOD;
68 
69     private AutoDispatchThread mAutoDispatchThread;
70 
71     /**
72      * Baklava introduces new {@link TestLooperManager} APIs that we can use instead of reflection.
73      */
isAtLeastBaklava()74     private static boolean isAtLeastBaklava() {
75         return Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA;
76     }
77 
78     static {
79         try {
80             LOOPER_CONSTRUCTOR = Looper.class.getDeclaredConstructor(Boolean.TYPE);
81             LOOPER_CONSTRUCTOR.setAccessible(true);
82             THREAD_LOCAL_LOOPER_FIELD = Looper.class.getDeclaredField("sThreadLocal");
83             THREAD_LOCAL_LOOPER_FIELD.setAccessible(true);
84 
85             if (isAtLeastBaklava()) {
86                 MESSAGE_QUEUE_MESSAGES_FIELD = null;
87                 MESSAGE_NEXT_FIELD = null;
88                 MESSAGE_WHEN_FIELD = null;
89                 MESSAGE_MARK_IN_USE_METHOD = null;
90             } else {
91                 MESSAGE_QUEUE_MESSAGES_FIELD = MessageQueue.class.getDeclaredField("mMessages");
92                 MESSAGE_QUEUE_MESSAGES_FIELD.setAccessible(true);
93                 MESSAGE_NEXT_FIELD = Message.class.getDeclaredField("next");
94                 MESSAGE_NEXT_FIELD.setAccessible(true);
95                 MESSAGE_WHEN_FIELD = Message.class.getDeclaredField("when");
96                 MESSAGE_WHEN_FIELD.setAccessible(true);
97                 MESSAGE_MARK_IN_USE_METHOD = Message.class.getDeclaredMethod("markInUse");
98                 MESSAGE_MARK_IN_USE_METHOD.setAccessible(true);
99             }
100         } catch (NoSuchFieldException | NoSuchMethodException e) {
101             throw new RuntimeException("Failed to initialize TestLooper", e);
102         }
103     }
104 
105     /**
106      * Creates a TestLooper and installs it as the looper for the current thread.
107      */
TestLooper()108     public TestLooper() {
109         this(SystemClock::uptimeMillis);
110     }
111 
112     /**
113      * Creates a TestLooper with a custom clock and installs it as the looper for the current
114      * thread.
115      *
116      * Messages are dispatched when their {@link Message#when} is before or at {@link
117      * Clock#uptimeMillis()}.
118      * Use a custom clock with care. When using an offsettable clock like {@link
119      * com.android.server.testutils.OffsettableClock} be sure not to double offset messages by
120      * offsetting the clock and calling {@link #moveTimeForward(long)}. Instead, offset the clock
121      * and call {@link #dispatchAll()}.
122      */
TestLooper(Clock clock)123     public TestLooper(Clock clock) {
124         try {
125             mLooper = LOOPER_CONSTRUCTOR.newInstance(false);
126 
127             ThreadLocal<Looper> threadLocalLooper =
128                     (ThreadLocal<Looper>) THREAD_LOCAL_LOOPER_FIELD.get(null);
129             threadLocalLooper.set(mLooper);
130         } catch (IllegalAccessException | InstantiationException | InvocationTargetException e) {
131             throw new RuntimeException("Reflection error constructing or accessing looper", e);
132         }
133 
134         if (isAtLeastBaklava()) {
135             mTestLooperManager =
136                 InstrumentationRegistry.getInstrumentation().acquireLooperManager(mLooper);
137         } else {
138             mTestLooperManager = null;
139         }
140 
141         mClock = clock;
142     }
143 
getLooper()144     public Looper getLooper() {
145         return mLooper;
146     }
147 
getNewExecutor()148     public Executor getNewExecutor() {
149         return new HandlerExecutor(new Handler(getLooper()));
150     }
151 
getMessageLinkedListLegacy()152     private Message getMessageLinkedListLegacy() {
153         try {
154             MessageQueue queue = mLooper.getQueue();
155             return (Message) MESSAGE_QUEUE_MESSAGES_FIELD.get(queue);
156         } catch (IllegalAccessException e) {
157             throw new RuntimeException("Access failed in TestLooper: get - MessageQueue.mMessages",
158                 e);
159         }
160     }
161 
moveTimeForward(long milliSeconds)162     public void moveTimeForward(long milliSeconds) {
163         if (isAtLeastBaklava()) {
164             moveTimeForwardBaklava(milliSeconds);
165         } else {
166             moveTimeForwardLegacy(milliSeconds);
167         }
168     }
169 
moveTimeForwardBaklava(long milliSeconds)170     private void moveTimeForwardBaklava(long milliSeconds) {
171         // Drain all Messages from the queue.
172         Queue<Message> messages = new ArrayDeque<>();
173         while (true) {
174             Message message = mTestLooperManager.poll();
175             if (message == null) {
176                 break;
177             }
178             messages.add(message);
179         }
180 
181         // Repost all Messages back to the queue with a new time.
182         while (true) {
183             Message message = messages.poll();
184             if (message == null) {
185                 break;
186             }
187 
188             // Ugly trick to reset the Message's "in use" flag.
189             // This is needed because the Message cannot be re-enqueued if it's
190             // marked in use.
191             message.copyFrom(message);
192 
193             // Adjust the Message's delivery time.
194             long newWhen = message.getWhen() - milliSeconds;
195             if (newWhen < 0) {
196                 newWhen = 0;
197             }
198 
199             // Send the Message back to its Handler to be re-enqueued.
200             message.getTarget().sendMessageAtTime(message, newWhen);
201         }
202     }
203 
moveTimeForwardLegacy(long milliSeconds)204     private void moveTimeForwardLegacy(long milliSeconds) {
205         try {
206             Message msg = getMessageLinkedListLegacy();
207             while (msg != null) {
208                 long updatedWhen = msg.getWhen() - milliSeconds;
209                 if (updatedWhen < 0) {
210                     updatedWhen = 0;
211                 }
212                 MESSAGE_WHEN_FIELD.set(msg, updatedWhen);
213                 msg = (Message) MESSAGE_NEXT_FIELD.get(msg);
214             }
215         } catch (IllegalAccessException e) {
216             throw new RuntimeException("Access failed in TestLooper: set - Message.when", e);
217         }
218     }
219 
currentTime()220     private long currentTime() {
221         return mClock.uptimeMillis();
222     }
223 
messageQueueNextLegacy()224     private Message messageQueueNextLegacy() {
225         try {
226             long now = currentTime();
227 
228             Message prevMsg = null;
229             Message msg = getMessageLinkedListLegacy();
230             if (msg != null && msg.getTarget() == null) {
231                 // Stalled by a barrier. Find the next asynchronous message in
232                 // the queue.
233                 do {
234                     prevMsg = msg;
235                     msg = (Message) MESSAGE_NEXT_FIELD.get(msg);
236                 } while (msg != null && !msg.isAsynchronous());
237             }
238             if (msg != null) {
239                 if (now >= msg.getWhen()) {
240                     // Got a message.
241                     if (prevMsg != null) {
242                         MESSAGE_NEXT_FIELD.set(prevMsg, MESSAGE_NEXT_FIELD.get(msg));
243                     } else {
244                         MESSAGE_QUEUE_MESSAGES_FIELD.set(mLooper.getQueue(),
245                                 MESSAGE_NEXT_FIELD.get(msg));
246                     }
247                     MESSAGE_NEXT_FIELD.set(msg, null);
248                     MESSAGE_MARK_IN_USE_METHOD.invoke(msg);
249                     return msg;
250                 }
251             }
252         } catch (IllegalAccessException | InvocationTargetException e) {
253             throw new RuntimeException("Access failed in TestLooper", e);
254         }
255 
256         return null;
257     }
258 
259     /**
260      * @return true if there are pending messages in the message queue
261      */
isIdle()262     public boolean isIdle() {
263         if (isAtLeastBaklava()) {
264             return isIdleBaklava();
265         } else {
266             return isIdleLegacy();
267         }
268     }
269 
isIdleBaklava()270     private boolean isIdleBaklava() {
271         Long when = mTestLooperManager.peekWhen();
272         return when != null && currentTime() >= when;
273     }
274 
isIdleLegacy()275     private synchronized boolean isIdleLegacy() {
276         Message messageList = getMessageLinkedListLegacy();
277         return messageList != null && currentTime() >= messageList.getWhen();
278     }
279 
280     /**
281      * @return the next message in the Looper's message queue or null if there is none
282      */
nextMessage()283     public Message nextMessage() {
284         if (isAtLeastBaklava()) {
285             return nextMessageBaklava();
286         } else {
287             return nextMessageLegacy();
288         }
289     }
290 
nextMessageBaklava()291     private Message nextMessageBaklava() {
292         if (isIdle()) {
293             return mTestLooperManager.poll();
294         } else {
295             return null;
296         }
297     }
298 
nextMessageLegacy()299     private synchronized Message nextMessageLegacy() {
300         if (isIdle()) {
301             return messageQueueNextLegacy();
302         } else {
303             return null;
304         }
305     }
306 
307     /**
308      * Dispatch the next message in the queue
309      * Asserts that there is a message in the queue
310      */
dispatchNext()311     public void dispatchNext() {
312         if (isAtLeastBaklava()) {
313             dispatchNextBaklava();
314         } else {
315             dispatchNextLegacy();
316         }
317     }
318 
dispatchNextBaklava()319     private void dispatchNextBaklava() {
320         assertTrue(isIdle());
321         Message msg = mTestLooperManager.poll();
322         if (msg == null) {
323             return;
324         }
325         msg.getTarget().dispatchMessage(msg);
326     }
327 
dispatchNextLegacy()328     private synchronized void dispatchNextLegacy() {
329         assertTrue(isIdle());
330         Message msg = messageQueueNextLegacy();
331         if (msg == null) {
332             return;
333         }
334         msg.getTarget().dispatchMessage(msg);
335     }
336 
337     /**
338      * Dispatch all messages currently in the queue
339      * Will not fail if there are no messages pending
340      *
341      * @return the number of messages dispatched
342      */
dispatchAll()343     public synchronized int dispatchAll() {
344         int count = 0;
345         while (isIdle()) {
346             dispatchNext();
347             ++count;
348         }
349         return count;
350     }
351 
352     public interface Clock {
uptimeMillis()353         long uptimeMillis();
354     }
355 
356     /**
357      * Thread used to dispatch messages when the main thread is blocked waiting for a response.
358      */
359     private class AutoDispatchThread extends Thread {
360         private static final int MAX_LOOPS = 100;
361         private static final int LOOP_SLEEP_TIME_MS = 10;
362 
363         private RuntimeException mAutoDispatchException = null;
364 
365         /**
366          * Run method for the auto dispatch thread.
367          * The thread loops a maximum of MAX_LOOPS times with a 10ms sleep between loops.
368          * The thread continues looping and attempting to dispatch all messages until
369          * {@link #stopAutoDispatch()} has been invoked.
370          */
371         @Override
run()372         public void run() {
373             int dispatchCount = 0;
374             for (int i = 0; i < MAX_LOOPS; i++) {
375                 try {
376                     dispatchCount += dispatchAll();
377                 } catch (RuntimeException e) {
378                     mAutoDispatchException = e;
379                     return;
380                 }
381                 Log.d(TAG, "dispatched " + dispatchCount + " messages");
382                 try {
383                     Thread.sleep(LOOP_SLEEP_TIME_MS);
384                 } catch (InterruptedException e) {
385                     if (dispatchCount == 0) {
386                         Log.e(TAG, "stopAutoDispatch called before any messages were dispatched.");
387                         mAutoDispatchException = new IllegalStateException(
388                                 "stopAutoDispatch called before any messages were dispatched.");
389                     }
390                     return;
391                 }
392             }
393             if (dispatchCount == 0) {
394                 Log.e(TAG, "AutoDispatchThread did not dispatch any messages.");
395                 mAutoDispatchException = new IllegalStateException(
396                         "TestLooper did not dispatch any messages before exiting.");
397             }
398         }
399 
400         /**
401          * Method allowing the TestLooper to pass any exceptions thrown by the thread to be passed
402          * to the main thread.
403          *
404          * @return RuntimeException Exception created by stopping without dispatching a message
405          */
getException()406         public RuntimeException getException() {
407             return mAutoDispatchException;
408         }
409     }
410 
411     /**
412      * Create and start a new AutoDispatchThread if one is not already running.
413      */
startAutoDispatch()414     public void startAutoDispatch() {
415         if (mAutoDispatchThread != null) {
416             throw new IllegalStateException(
417                     "startAutoDispatch called with the AutoDispatchThread already running.");
418         }
419         mAutoDispatchThread = new AutoDispatchThread();
420         mAutoDispatchThread.start();
421     }
422 
423     /**
424      * If an AutoDispatchThread is currently running, stop and clean up.
425      */
stopAutoDispatch()426     public void stopAutoDispatch() {
427         if (mAutoDispatchThread != null) {
428             if (mAutoDispatchThread.isAlive()) {
429                 mAutoDispatchThread.interrupt();
430             }
431             Uninterruptibles.joinUninterruptibly(mAutoDispatchThread);
432 
433             RuntimeException e = mAutoDispatchThread.getException();
434             mAutoDispatchThread = null;
435             if (e != null) {
436                 throw e;
437             }
438         } else {
439             // stopAutoDispatch was called when startAutoDispatch has not created a new thread.
440             throw new IllegalStateException(
441                     "stopAutoDispatch called without startAutoDispatch.");
442         }
443     }
444 
445     /**
446      * If an AutoDispatchThread is currently running, stop and clean up.
447      * This method ignores exceptions raised for indicating that no messages were dispatched.
448      */
stopAutoDispatchAndIgnoreExceptions()449     public void stopAutoDispatchAndIgnoreExceptions() {
450         try {
451             stopAutoDispatch();
452         } catch (IllegalStateException e) {
453             Log.e(TAG, "stopAutoDispatch", e);
454         }
455 
456     }
457 }