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