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 private int mMessageCount = 0; 59 TestHandler(Callback callback)60 public TestHandler(Callback callback) { 61 this(callback, DEFAULT_CLOCK); 62 } 63 TestHandler(Callback callback, LongSupplier clock)64 public TestHandler(Callback callback, LongSupplier clock) { 65 this(Looper.getMainLooper(), callback, clock); 66 } 67 TestHandler(Looper looper, Callback callback, LongSupplier clock)68 public TestHandler(Looper looper, Callback callback, LongSupplier clock) { 69 super(looper, callback); 70 mClock = clock; 71 } 72 73 @Override sendMessageAtTime(Message msg, long uptimeMillis)74 public boolean sendMessageAtTime(Message msg, long uptimeMillis) { 75 ++mMessageCount; 76 mPendingMsgTypeCounts.put(msg.what, 77 mPendingMsgTypeCounts.getOrDefault(msg.what, 0) + 1); 78 79 // uptimeMillis is an absolute time obtained as SystemClock.uptimeMillis() + offsetMillis 80 // if custom clock is given, recalculate the time with regards to it 81 if (mClock != DEFAULT_CLOCK) { 82 uptimeMillis = uptimeMillis - SystemClock.uptimeMillis() + mClock.getAsLong(); 83 } 84 85 // post a sentinel queue entry to keep track of message removal 86 return super.sendMessageAtTime(msg, Long.MAX_VALUE) 87 && mMessages.add(new MsgInfo(Message.obtain(msg), uptimeMillis, mMessageCount)); 88 } 89 90 /** @see TestHandler */ timeAdvance()91 public void timeAdvance() { 92 long now = mClock.getAsLong(); 93 while (!mMessages.isEmpty() && mMessages.peek().sendTime <= now) { 94 dispatch(mMessages.poll()); 95 } 96 } 97 98 /** 99 * Dispatch all messages in order 100 * 101 * @see TestHandler 102 */ flush()103 public void flush() { 104 MsgInfo msg; 105 while ((msg = mMessages.poll()) != null) { 106 dispatch(msg); 107 } 108 } 109 110 /** 111 * Deletes all messages in queue. 112 */ clear()113 public void clear() { 114 mMessages.clear(); 115 } 116 getPendingMessages()117 public PriorityQueue<MsgInfo> getPendingMessages() { 118 return new PriorityQueue<>(mMessages); 119 } 120 121 /** 122 * Optionally-overridable to allow deciphering message types 123 * 124 * @see android.util.DebugUtils#valueToString - a handy utility to use when overriding this 125 */ messageToString(Message message)126 protected String messageToString(Message message) { 127 return message.toString(); 128 } 129 dispatch(MsgInfo msg)130 private void dispatch(MsgInfo msg) { 131 int msgId = msg.message.what; 132 133 if (!hasMessages(msgId)) { 134 // Handler.removeMessages(msgId) must have been called 135 return; 136 } 137 138 try { 139 Integer pendingMsgCount = mPendingMsgTypeCounts.getOrDefault(msgId, 0); 140 if (pendingMsgCount <= 1) { 141 removeMessages(msgId); 142 } 143 mPendingMsgTypeCounts.put(msgId, pendingMsgCount - 1); 144 145 dispatchMessage(msg.message); 146 } catch (Throwable t) { 147 // Append stack trace of this message being posted as a cause for a helpful 148 // test error message 149 throw propagate(appendCause(t, msg.postPoint)); 150 } finally { 151 msg.message.recycle(); 152 } 153 } 154 155 public class MsgInfo implements Comparable<MsgInfo> { 156 public final Message message; 157 public final long sendTime; 158 public final int mMessageOrder; 159 public final RuntimeException postPoint; 160 MsgInfo(Message message, long sendTime, int messageOrder)161 private MsgInfo(Message message, long sendTime, int messageOrder) { 162 this.message = message; 163 this.sendTime = sendTime; 164 this.postPoint = new RuntimeException("Message originated from here:"); 165 mMessageOrder = messageOrder; 166 } 167 168 @Override compareTo(MsgInfo o)169 public int compareTo(MsgInfo o) { 170 final int result = Long.compare(sendTime, o.sendTime); 171 return result != 0 ? result : Integer.compare(mMessageOrder, o.mMessageOrder); 172 } 173 174 @Override toString()175 public String toString() { 176 return "MsgInfo{" + 177 "message =" + messageToString(message) 178 + ", sendTime =" + sendTime 179 + ", mMessageOrder =" + mMessageOrder 180 + '}'; 181 } 182 } 183 } 184