• 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");
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 package com.android.server.testutils;
17 
18 
19 import static android.util.ExceptionUtils.appendCause;
20 import static android.util.ExceptionUtils.propagate;
21 
22 import android.os.Handler;
23 import android.os.Looper;
24 import android.os.Message;
25 import android.os.SystemClock;
26 import android.util.ArrayMap;
27 
28 import java.util.Map;
29 import java.util.PriorityQueue;
30 import java.util.function.LongSupplier;
31 
32 /**
33  * A test {@link Handler} that stores incoming {@link Message}s and {@link Runnable callbacks}
34  * in a {@link PriorityQueue} based on time, to be manually processed later in a correct order
35  * either all together with {@link #flush}, or only those due at the current time with
36  * {@link #timeAdvance}.
37  *
38  * For the latter use case this also supports providing a custom clock (in a format of a
39  * milliseconds-returning {@link LongSupplier}), that will be used for storing the messages'
40  * timestamps to be posted at, and checked against during {@link #timeAdvance}.
41  *
42  * This allows to test code that uses {@link Handler}'s delayed invocation capabilities, such as
43  * {@link Handler#sendMessageDelayed} or {@link Handler#postDelayed} without resorting to
44  * synchronously {@link Thread#sleep}ing in your test.
45  *
46  * @see OffsettableClock for a useful custom clock implementation to use with this handler
47  */
48 public class TestHandler extends Handler {
49     private static final LongSupplier DEFAULT_CLOCK = SystemClock::uptimeMillis;
50 
51     private final PriorityQueue<MsgInfo> mMessages = new PriorityQueue<>();
52     /**
53      * Map of: {@code message id -> count of such messages currently pending }
54      */
55     // Boxing is ok here - both msg ids and their pending counts tend to be well below 128
56     private final Map<Integer, Integer> mPendingMsgTypeCounts = new ArrayMap<>();
57     private final LongSupplier mClock;
58 
TestHandler(Callback callback)59     public TestHandler(Callback callback) {
60         this(callback, DEFAULT_CLOCK);
61     }
62 
TestHandler(Callback callback, LongSupplier clock)63     public TestHandler(Callback callback, LongSupplier clock) {
64         super(Looper.getMainLooper(), callback);
65         mClock = clock;
66     }
67 
68     @Override
sendMessageAtTime(Message msg, long uptimeMillis)69     public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
70         mPendingMsgTypeCounts.put(msg.what,
71                 mPendingMsgTypeCounts.getOrDefault(msg.what, 0) + 1);
72 
73         // uptimeMillis is an absolute time obtained as SystemClock.uptimeMillis() + offsetMillis
74         // if custom clock is given, recalculate the time with regards to it
75         if (mClock != DEFAULT_CLOCK) {
76             uptimeMillis = uptimeMillis - SystemClock.uptimeMillis() + mClock.getAsLong();
77         }
78 
79         // post a dummy queue entry to keep track of message removal
80         return super.sendMessageAtTime(msg, Long.MAX_VALUE)
81                 && mMessages.add(new MsgInfo(Message.obtain(msg), uptimeMillis));
82     }
83 
84     /** @see TestHandler */
timeAdvance()85     public void timeAdvance() {
86         long now = mClock.getAsLong();
87         while (!mMessages.isEmpty() && mMessages.peek().sendTime <= now) {
88             dispatch(mMessages.poll());
89         }
90     }
91 
92     /**
93      * Dispatch all messages in order
94      *
95      * @see TestHandler
96      */
flush()97     public void flush() {
98         MsgInfo msg;
99         while ((msg = mMessages.poll()) != null) {
100             dispatch(msg);
101         }
102     }
103 
getPendingMessages()104     public PriorityQueue<MsgInfo> getPendingMessages() {
105         return new PriorityQueue<>(mMessages);
106     }
107 
108     /**
109      * Optionally-overridable to allow deciphering message types
110      *
111      * @see android.util.DebugUtils#valueToString - a handy utility to use when overriding this
112      */
messageToString(Message message)113     protected String messageToString(Message message) {
114         return message.toString();
115     }
116 
dispatch(MsgInfo msg)117     private void dispatch(MsgInfo msg) {
118         int msgId = msg.message.what;
119 
120         if (!hasMessages(msgId)) {
121             // Handler.removeMessages(msgId) must have been called
122             return;
123         }
124 
125         try {
126             Integer pendingMsgCount = mPendingMsgTypeCounts.getOrDefault(msgId, 0);
127             if (pendingMsgCount <= 1) {
128                 removeMessages(msgId);
129             }
130             mPendingMsgTypeCounts.put(msgId, pendingMsgCount - 1);
131 
132             dispatchMessage(msg.message);
133         } catch (Throwable t) {
134             // Append stack trace of this message being posted as a cause for a helpful
135             // test error message
136             throw propagate(appendCause(t, msg.postPoint));
137         } finally {
138             msg.message.recycle();
139         }
140     }
141 
142     private class MsgInfo implements Comparable<MsgInfo> {
143         public final Message message;
144         public final long sendTime;
145         public final RuntimeException postPoint;
146 
MsgInfo(Message message, long sendTime)147         private MsgInfo(Message message, long sendTime) {
148             this.message = message;
149             this.sendTime = sendTime;
150             this.postPoint = new RuntimeException("Message originated from here:");
151         }
152 
153         @Override
compareTo(MsgInfo o)154         public int compareTo(MsgInfo o) {
155             return Long.compare(sendTime, o.sendTime);
156         }
157 
158         @Override
toString()159         public String toString() {
160             return "MsgInfo{" +
161                     "message=" + messageToString(message) +
162                     ", sendTime=" + sendTime +
163                     '}';
164         }
165     }
166 }
167