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 59 public TestHandler(Callback callback) { 60 this(callback, DEFAULT_CLOCK); 61 } 62 63 public TestHandler(Callback callback, LongSupplier clock) { 64 super(Looper.getMainLooper(), callback); 65 mClock = clock; 66 } 67 68 @Override 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 */ 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 */ 97 public void flush() { 98 MsgInfo msg; 99 while ((msg = mMessages.poll()) != null) { 100 dispatch(msg); 101 } 102 } 103 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 */ 113 protected String messageToString(Message message) { 114 return message.toString(); 115 } 116 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 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 154 public int compareTo(MsgInfo o) { 155 return (int) (sendTime - o.sendTime); 156 } 157 158 @Override 159 public String toString() { 160 return "MsgInfo{" + 161 "message=" + messageToString(message) + 162 ", sendTime=" + sendTime + 163 '}'; 164 } 165 } 166 } 167