1 /**
2  * Copyright 2024 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 #define LOG_TAG "InputTransport"
18 #define ATRACE_TAG ATRACE_TAG_INPUT
19 
20 #include <inttypes.h>
21 
22 #include <android-base/logging.h>
23 #include <android-base/properties.h>
24 #include <android-base/stringprintf.h>
25 #include <cutils/properties.h>
26 #include <ftl/enum.h>
27 #include <utils/Trace.h>
28 
29 #include <com_android_input_flags.h>
30 #include <input/InputConsumerNoResampling.h>
31 #include <input/PrintTools.h>
32 #include <input/TraceTools.h>
33 
34 namespace input_flags = com::android::input::flags;
35 
36 namespace android {
37 
38 namespace {
39 
40 /**
41  * Log debug messages relating to the consumer end of the transport channel.
42  * Enable this via "adb shell setprop log.tag.InputTransportConsumer DEBUG" (requires restart)
43  */
44 const bool DEBUG_TRANSPORT_CONSUMER =
45         __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Consumer", ANDROID_LOG_INFO);
46 
createKeyEvent(const InputMessage & msg)47 std::unique_ptr<KeyEvent> createKeyEvent(const InputMessage& msg) {
48     std::unique_ptr<KeyEvent> event = std::make_unique<KeyEvent>();
49     event->initialize(msg.body.key.eventId, msg.body.key.deviceId, msg.body.key.source,
50                       ui::LogicalDisplayId{msg.body.key.displayId}, msg.body.key.hmac,
51                       msg.body.key.action, msg.body.key.flags, msg.body.key.keyCode,
52                       msg.body.key.scanCode, msg.body.key.metaState, msg.body.key.repeatCount,
53                       msg.body.key.downTime, msg.body.key.eventTime);
54     return event;
55 }
56 
createFocusEvent(const InputMessage & msg)57 std::unique_ptr<FocusEvent> createFocusEvent(const InputMessage& msg) {
58     std::unique_ptr<FocusEvent> event = std::make_unique<FocusEvent>();
59     event->initialize(msg.body.focus.eventId, msg.body.focus.hasFocus);
60     return event;
61 }
62 
createCaptureEvent(const InputMessage & msg)63 std::unique_ptr<CaptureEvent> createCaptureEvent(const InputMessage& msg) {
64     std::unique_ptr<CaptureEvent> event = std::make_unique<CaptureEvent>();
65     event->initialize(msg.body.capture.eventId, msg.body.capture.pointerCaptureEnabled);
66     return event;
67 }
68 
createDragEvent(const InputMessage & msg)69 std::unique_ptr<DragEvent> createDragEvent(const InputMessage& msg) {
70     std::unique_ptr<DragEvent> event = std::make_unique<DragEvent>();
71     event->initialize(msg.body.drag.eventId, msg.body.drag.x, msg.body.drag.y,
72                       msg.body.drag.isExiting);
73     return event;
74 }
75 
createMotionEvent(const InputMessage & msg)76 std::unique_ptr<MotionEvent> createMotionEvent(const InputMessage& msg) {
77     std::unique_ptr<MotionEvent> event = std::make_unique<MotionEvent>();
78     const uint32_t pointerCount = msg.body.motion.pointerCount;
79     std::vector<PointerProperties> pointerProperties;
80     pointerProperties.reserve(pointerCount);
81     std::vector<PointerCoords> pointerCoords;
82     pointerCoords.reserve(pointerCount);
83     for (uint32_t i = 0; i < pointerCount; i++) {
84         pointerProperties.push_back(msg.body.motion.pointers[i].properties);
85         pointerCoords.push_back(msg.body.motion.pointers[i].coords);
86     }
87 
88     ui::Transform transform;
89     transform.set({msg.body.motion.dsdx, msg.body.motion.dtdx, msg.body.motion.tx,
90                    msg.body.motion.dtdy, msg.body.motion.dsdy, msg.body.motion.ty, 0, 0, 1});
91     ui::Transform displayTransform;
92     displayTransform.set({msg.body.motion.dsdxRaw, msg.body.motion.dtdxRaw, msg.body.motion.txRaw,
93                           msg.body.motion.dtdyRaw, msg.body.motion.dsdyRaw, msg.body.motion.tyRaw,
94                           0, 0, 1});
95     event->initialize(msg.body.motion.eventId, msg.body.motion.deviceId, msg.body.motion.source,
96                       ui::LogicalDisplayId{msg.body.motion.displayId}, msg.body.motion.hmac,
97                       msg.body.motion.action, msg.body.motion.actionButton, msg.body.motion.flags,
98                       msg.body.motion.edgeFlags, msg.body.motion.metaState,
99                       msg.body.motion.buttonState, msg.body.motion.classification, transform,
100                       msg.body.motion.xPrecision, msg.body.motion.yPrecision,
101                       msg.body.motion.xCursorPosition, msg.body.motion.yCursorPosition,
102                       displayTransform, msg.body.motion.downTime, msg.body.motion.eventTime,
103                       pointerCount, pointerProperties.data(), pointerCoords.data());
104     return event;
105 }
106 
addSample(MotionEvent & event,const InputMessage & msg)107 void addSample(MotionEvent& event, const InputMessage& msg) {
108     uint32_t pointerCount = msg.body.motion.pointerCount;
109     std::vector<PointerCoords> pointerCoords;
110     pointerCoords.reserve(pointerCount);
111     for (uint32_t i = 0; i < pointerCount; i++) {
112         pointerCoords.push_back(msg.body.motion.pointers[i].coords);
113     }
114 
115     // TODO(b/329770983): figure out if it's safe to combine events with mismatching metaState
116     event.setMetaState(event.getMetaState() | msg.body.motion.metaState);
117     event.addSample(msg.body.motion.eventTime, pointerCoords.data());
118 }
119 
createTouchModeEvent(const InputMessage & msg)120 std::unique_ptr<TouchModeEvent> createTouchModeEvent(const InputMessage& msg) {
121     std::unique_ptr<TouchModeEvent> event = std::make_unique<TouchModeEvent>();
122     event->initialize(msg.body.touchMode.eventId, msg.body.touchMode.isInTouchMode);
123     return event;
124 }
125 
outboundMessageToString(const InputMessage & outboundMsg)126 std::string outboundMessageToString(const InputMessage& outboundMsg) {
127     switch (outboundMsg.header.type) {
128         case InputMessage::Type::FINISHED: {
129             return android::base::StringPrintf("  Finish: seq=%" PRIu32 " handled=%s",
130                                                outboundMsg.header.seq,
131                                                toString(outboundMsg.body.finished.handled));
132         }
133         case InputMessage::Type::TIMELINE: {
134             return android::base::
135                     StringPrintf("  Timeline: inputEventId=%" PRId32 " gpuCompletedTime=%" PRId64
136                                  ", presentTime=%" PRId64,
137                                  outboundMsg.body.timeline.eventId,
138                                  outboundMsg.body.timeline
139                                          .graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME],
140                                  outboundMsg.body.timeline
141                                          .graphicsTimeline[GraphicsTimeline::PRESENT_TIME]);
142         }
143         default: {
144             LOG(FATAL) << "Outbound message must be FINISHED or TIMELINE, got "
145                        << ftl::enum_string(outboundMsg.header.type);
146             return "Unreachable";
147         }
148     }
149 }
150 
createFinishedMessage(uint32_t seq,bool handled,nsecs_t consumeTime)151 InputMessage createFinishedMessage(uint32_t seq, bool handled, nsecs_t consumeTime) {
152     InputMessage msg;
153     msg.header.type = InputMessage::Type::FINISHED;
154     msg.header.seq = seq;
155     msg.body.finished.handled = handled;
156     msg.body.finished.consumeTime = consumeTime;
157     return msg;
158 }
159 
createTimelineMessage(int32_t inputEventId,nsecs_t gpuCompletedTime,nsecs_t presentTime)160 InputMessage createTimelineMessage(int32_t inputEventId, nsecs_t gpuCompletedTime,
161                                    nsecs_t presentTime) {
162     InputMessage msg;
163     msg.header.type = InputMessage::Type::TIMELINE;
164     msg.header.seq = 0;
165     msg.body.timeline.eventId = inputEventId;
166     msg.body.timeline.graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME] = gpuCompletedTime;
167     msg.body.timeline.graphicsTimeline[GraphicsTimeline::PRESENT_TIME] = presentTime;
168     return msg;
169 }
170 
171 } // namespace
172 
173 using android::base::Result;
174 using android::base::StringPrintf;
175 
176 // --- InputConsumerNoResampling ---
177 
InputConsumerNoResampling(const std::shared_ptr<InputChannel> & channel,sp<Looper> looper,InputConsumerCallbacks & callbacks)178 InputConsumerNoResampling::InputConsumerNoResampling(const std::shared_ptr<InputChannel>& channel,
179                                                      sp<Looper> looper,
180                                                      InputConsumerCallbacks& callbacks)
181       : mChannel(channel), mLooper(looper), mCallbacks(callbacks), mFdEvents(0) {
182     LOG_ALWAYS_FATAL_IF(mLooper == nullptr);
183     mCallback = sp<LooperEventCallback>::make(
184             std::bind(&InputConsumerNoResampling::handleReceiveCallback, this,
185                       std::placeholders::_1));
186     // In the beginning, there are no pending outbounds events; we only care about receiving
187     // incoming data.
188     setFdEvents(ALOOPER_EVENT_INPUT);
189 }
190 
~InputConsumerNoResampling()191 InputConsumerNoResampling::~InputConsumerNoResampling() {
192     ensureCalledOnLooperThread(__func__);
193     consumeBatchedInputEvents(std::nullopt);
194     while (!mOutboundQueue.empty()) {
195         processOutboundEvents();
196         // This is our last chance to ack the events. If we don't ack them here, we will get an ANR,
197         // so keep trying to send the events as long as they are present in the queue.
198     }
199     setFdEvents(0);
200 }
201 
handleReceiveCallback(int events)202 int InputConsumerNoResampling::handleReceiveCallback(int events) {
203     // Allowed return values of this function as documented in LooperCallback::handleEvent
204     constexpr int REMOVE_CALLBACK = 0;
205     constexpr int KEEP_CALLBACK = 1;
206 
207     if (events & (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP)) {
208         // This error typically occurs when the publisher has closed the input channel
209         // as part of removing a window or finishing an IME session, in which case
210         // the consumer will soon be disposed as well.
211         if (DEBUG_TRANSPORT_CONSUMER) {
212             LOG(INFO) << "The channel was hung up or an error occurred: " << mChannel->getName();
213         }
214         return REMOVE_CALLBACK;
215     }
216 
217     int handledEvents = 0;
218     if (events & ALOOPER_EVENT_INPUT) {
219         std::vector<InputMessage> messages = readAllMessages();
220         handleMessages(std::move(messages));
221         handledEvents |= ALOOPER_EVENT_INPUT;
222     }
223 
224     if (events & ALOOPER_EVENT_OUTPUT) {
225         processOutboundEvents();
226         handledEvents |= ALOOPER_EVENT_OUTPUT;
227     }
228     if (handledEvents != events) {
229         LOG(FATAL) << "Mismatch: handledEvents=" << handledEvents << ", events=" << events;
230     }
231     return KEEP_CALLBACK;
232 }
233 
processOutboundEvents()234 void InputConsumerNoResampling::processOutboundEvents() {
235     while (!mOutboundQueue.empty()) {
236         const InputMessage& outboundMsg = mOutboundQueue.front();
237 
238         const status_t result = mChannel->sendMessage(&outboundMsg);
239         if (result == OK) {
240             if (outboundMsg.header.type == InputMessage::Type::FINISHED) {
241                 ATRACE_ASYNC_END("InputConsumer processing", /*cookie=*/outboundMsg.header.seq);
242             }
243             // Successful send. Erase the entry and keep trying to send more
244             mOutboundQueue.pop();
245             continue;
246         }
247 
248         // Publisher is busy, try again later. Keep this entry (do not erase)
249         if (result == WOULD_BLOCK) {
250             setFdEvents(ALOOPER_EVENT_INPUT | ALOOPER_EVENT_OUTPUT);
251             return; // try again later
252         }
253 
254         // Some other error. Give up
255         LOG(FATAL) << "Failed to send outbound event on channel '" << mChannel->getName()
256                    << "'.  status=" << statusToString(result) << "(" << result << ")";
257     }
258 
259     // The queue is now empty. Tell looper there's no more output to expect.
260     setFdEvents(ALOOPER_EVENT_INPUT);
261 }
262 
finishInputEvent(uint32_t seq,bool handled)263 void InputConsumerNoResampling::finishInputEvent(uint32_t seq, bool handled) {
264     ensureCalledOnLooperThread(__func__);
265     mOutboundQueue.push(createFinishedMessage(seq, handled, popConsumeTime(seq)));
266     // also produce finish events for all batches for this seq (if any)
267     const auto it = mBatchedSequenceNumbers.find(seq);
268     if (it != mBatchedSequenceNumbers.end()) {
269         for (uint32_t subSeq : it->second) {
270             mOutboundQueue.push(createFinishedMessage(subSeq, handled, popConsumeTime(subSeq)));
271         }
272         mBatchedSequenceNumbers.erase(it);
273     }
274     processOutboundEvents();
275 }
276 
probablyHasInput() const277 bool InputConsumerNoResampling::probablyHasInput() const {
278     // Ideally, this would only be allowed to run on the looper thread, and in production, it will.
279     // However, for testing, it's convenient to call this while the looper thread is blocked, so
280     // we do not call ensureCalledOnLooperThread here.
281     return (!mBatches.empty()) || mChannel->probablyHasInput();
282 }
283 
reportTimeline(int32_t inputEventId,nsecs_t gpuCompletedTime,nsecs_t presentTime)284 void InputConsumerNoResampling::reportTimeline(int32_t inputEventId, nsecs_t gpuCompletedTime,
285                                                nsecs_t presentTime) {
286     ensureCalledOnLooperThread(__func__);
287     mOutboundQueue.push(createTimelineMessage(inputEventId, gpuCompletedTime, presentTime));
288     processOutboundEvents();
289 }
290 
popConsumeTime(uint32_t seq)291 nsecs_t InputConsumerNoResampling::popConsumeTime(uint32_t seq) {
292     auto it = mConsumeTimes.find(seq);
293     // Consume time will be missing if either 'finishInputEvent' is called twice, or if it was
294     // called for the wrong (synthetic?) input event. Either way, it is a bug that should be fixed.
295     LOG_ALWAYS_FATAL_IF(it == mConsumeTimes.end(), "Could not find consume time for seq=%" PRIu32,
296                         seq);
297     nsecs_t consumeTime = it->second;
298     mConsumeTimes.erase(it);
299     return consumeTime;
300 }
301 
setFdEvents(int events)302 void InputConsumerNoResampling::setFdEvents(int events) {
303     if (mFdEvents != events) {
304         mFdEvents = events;
305         if (events != 0) {
306             mLooper->addFd(mChannel->getFd(), 0, events, mCallback, nullptr);
307         } else {
308             mLooper->removeFd(mChannel->getFd());
309         }
310     }
311 }
312 
handleMessages(std::vector<InputMessage> && messages)313 void InputConsumerNoResampling::handleMessages(std::vector<InputMessage>&& messages) {
314     // TODO(b/297226446) : add resampling
315     for (const InputMessage& msg : messages) {
316         if (msg.header.type == InputMessage::Type::MOTION) {
317             const int32_t action = msg.body.motion.action;
318             const DeviceId deviceId = msg.body.motion.deviceId;
319             const int32_t source = msg.body.motion.source;
320             const bool batchableEvent = (action == AMOTION_EVENT_ACTION_MOVE ||
321                                          action == AMOTION_EVENT_ACTION_HOVER_MOVE) &&
322                     (isFromSource(source, AINPUT_SOURCE_CLASS_POINTER) ||
323                      isFromSource(source, AINPUT_SOURCE_CLASS_JOYSTICK));
324             if (batchableEvent) {
325                 // add it to batch
326                 mBatches[deviceId].emplace(msg);
327             } else {
328                 // consume all pending batches for this event immediately
329                 // TODO(b/329776327): figure out if this could be smarter by limiting the
330                 // consumption only to the current device.
331                 consumeBatchedInputEvents(std::nullopt);
332                 handleMessage(msg);
333             }
334         } else {
335             // Non-motion events shouldn't force the consumption of pending batched events
336             handleMessage(msg);
337         }
338     }
339     // At the end of this, if we still have pending batches, notify the receiver about it.
340 
341     // We need to carefully notify the InputConsumerCallbacks about the pending batch. The receiver
342     // could choose to consume all events when notified about the batch. That means that the
343     // "mBatches" variable could change when 'InputConsumerCallbacks::onBatchedInputEventPending' is
344     // invoked. We also can't notify the InputConsumerCallbacks in a while loop until mBatches is
345     // empty, because the receiver could choose to not consume the batch immediately.
346     std::set<int32_t> pendingBatchSources;
347     for (const auto& [_, pendingMessages] : mBatches) {
348         // Assume that all messages for a given device has the same source.
349         pendingBatchSources.insert(pendingMessages.front().body.motion.source);
350     }
351     for (const int32_t source : pendingBatchSources) {
352         const bool sourceStillRemaining =
353                 std::any_of(mBatches.begin(), mBatches.end(), [=](const auto& pair) {
354                     return pair.second.front().body.motion.source == source;
355                 });
356         if (sourceStillRemaining) {
357             mCallbacks.onBatchedInputEventPending(source);
358         }
359     }
360 }
361 
readAllMessages()362 std::vector<InputMessage> InputConsumerNoResampling::readAllMessages() {
363     std::vector<InputMessage> messages;
364     while (true) {
365         InputMessage msg;
366         status_t result = mChannel->receiveMessage(&msg);
367         switch (result) {
368             case OK: {
369                 const auto [_, inserted] =
370                         mConsumeTimes.emplace(msg.header.seq, systemTime(SYSTEM_TIME_MONOTONIC));
371                 LOG_ALWAYS_FATAL_IF(!inserted, "Already have a consume time for seq=%" PRIu32,
372                                     msg.header.seq);
373 
374                 // Trace the event processing timeline - event was just read from the socket
375                 // TODO(b/329777420): distinguish between multiple instances of InputConsumer
376                 // in the same process.
377                 ATRACE_ASYNC_BEGIN("InputConsumer processing", /*cookie=*/msg.header.seq);
378                 messages.push_back(msg);
379                 break;
380             }
381             case WOULD_BLOCK: {
382                 return messages;
383             }
384             case DEAD_OBJECT: {
385                 LOG(FATAL) << "Got a dead object for " << mChannel->getName();
386                 break;
387             }
388             case BAD_VALUE: {
389                 LOG(FATAL) << "Got a bad value for " << mChannel->getName();
390                 break;
391             }
392             default: {
393                 LOG(FATAL) << "Unexpected error: " << result;
394                 break;
395             }
396         }
397     }
398 }
399 
handleMessage(const InputMessage & msg) const400 void InputConsumerNoResampling::handleMessage(const InputMessage& msg) const {
401     switch (msg.header.type) {
402         case InputMessage::Type::KEY: {
403             std::unique_ptr<KeyEvent> keyEvent = createKeyEvent(msg);
404             mCallbacks.onKeyEvent(std::move(keyEvent), msg.header.seq);
405             break;
406         }
407 
408         case InputMessage::Type::MOTION: {
409             std::unique_ptr<MotionEvent> motionEvent = createMotionEvent(msg);
410             mCallbacks.onMotionEvent(std::move(motionEvent), msg.header.seq);
411             break;
412         }
413 
414         case InputMessage::Type::FINISHED:
415         case InputMessage::Type::TIMELINE: {
416             LOG(FATAL) << "Consumed a " << ftl::enum_string(msg.header.type)
417                        << " message, which should never be seen by InputConsumer on "
418                        << mChannel->getName();
419             break;
420         }
421 
422         case InputMessage::Type::FOCUS: {
423             std::unique_ptr<FocusEvent> focusEvent = createFocusEvent(msg);
424             mCallbacks.onFocusEvent(std::move(focusEvent), msg.header.seq);
425             break;
426         }
427 
428         case InputMessage::Type::CAPTURE: {
429             std::unique_ptr<CaptureEvent> captureEvent = createCaptureEvent(msg);
430             mCallbacks.onCaptureEvent(std::move(captureEvent), msg.header.seq);
431             break;
432         }
433 
434         case InputMessage::Type::DRAG: {
435             std::unique_ptr<DragEvent> dragEvent = createDragEvent(msg);
436             mCallbacks.onDragEvent(std::move(dragEvent), msg.header.seq);
437             break;
438         }
439 
440         case InputMessage::Type::TOUCH_MODE: {
441             std::unique_ptr<TouchModeEvent> touchModeEvent = createTouchModeEvent(msg);
442             mCallbacks.onTouchModeEvent(std::move(touchModeEvent), msg.header.seq);
443             break;
444         }
445     }
446 }
447 
consumeBatchedInputEvents(std::optional<nsecs_t> requestedFrameTime)448 bool InputConsumerNoResampling::consumeBatchedInputEvents(
449         std::optional<nsecs_t> requestedFrameTime) {
450     ensureCalledOnLooperThread(__func__);
451     // When batching is not enabled, we want to consume all events. That's equivalent to having an
452     // infinite frameTime.
453     const nsecs_t frameTime = requestedFrameTime.value_or(std::numeric_limits<nsecs_t>::max());
454     bool producedEvents = false;
455     for (auto& [deviceId, messages] : mBatches) {
456         std::unique_ptr<MotionEvent> motion;
457         std::optional<uint32_t> firstSeqForBatch;
458         std::vector<uint32_t> sequences;
459         while (!messages.empty()) {
460             const InputMessage& msg = messages.front();
461             if (msg.body.motion.eventTime > frameTime) {
462                 break;
463             }
464             if (motion == nullptr) {
465                 motion = createMotionEvent(msg);
466                 firstSeqForBatch = msg.header.seq;
467                 const auto [_, inserted] = mBatchedSequenceNumbers.insert({*firstSeqForBatch, {}});
468                 if (!inserted) {
469                     LOG(FATAL) << "The sequence " << msg.header.seq << " was already present!";
470                 }
471             } else {
472                 addSample(*motion, msg);
473                 mBatchedSequenceNumbers[*firstSeqForBatch].push_back(msg.header.seq);
474             }
475             messages.pop();
476         }
477         if (motion != nullptr) {
478             LOG_ALWAYS_FATAL_IF(!firstSeqForBatch.has_value());
479             mCallbacks.onMotionEvent(std::move(motion), *firstSeqForBatch);
480             producedEvents = true;
481         } else {
482             // This is OK, it just means that the frameTime is too old (all events that we have
483             // pending are in the future of the frametime). Maybe print a
484             // warning? If there are multiple devices active though, this might be normal and can
485             // just be ignored, unless none of them resulted in any consumption (in that case, this
486             // function would already return "false" so we could just leave it up to the caller).
487         }
488     }
489     std::erase_if(mBatches, [](const auto& pair) { return pair.second.empty(); });
490     return producedEvents;
491 }
492 
ensureCalledOnLooperThread(const char * func) const493 void InputConsumerNoResampling::ensureCalledOnLooperThread(const char* func) const {
494     sp<Looper> callingThreadLooper = Looper::getForThread();
495     if (callingThreadLooper != mLooper) {
496         LOG(FATAL) << "The function " << func << " can only be called on the looper thread";
497     }
498 }
499 
dump() const500 std::string InputConsumerNoResampling::dump() const {
501     ensureCalledOnLooperThread(__func__);
502     std::string out;
503     if (mOutboundQueue.empty()) {
504         out += "mOutboundQueue: <empty>\n";
505     } else {
506         out += "mOutboundQueue:\n";
507         // Make a copy of mOutboundQueue for printing destructively. Unfortunately std::queue
508         // doesn't provide a good way to iterate over the entire container.
509         std::queue<InputMessage> tmpQueue = mOutboundQueue;
510         while (!tmpQueue.empty()) {
511             out += std::string("  ") + outboundMessageToString(tmpQueue.front()) + "\n";
512             tmpQueue.pop();
513         }
514     }
515 
516     if (mBatches.empty()) {
517         out += "mBatches: <empty>\n";
518     } else {
519         out += "mBatches:\n";
520         for (const auto& [deviceId, messages] : mBatches) {
521             out += "  Device id ";
522             out += std::to_string(deviceId);
523             out += ":\n";
524             // Make a copy of mOutboundQueue for printing destructively. Unfortunately std::queue
525             // doesn't provide a good way to iterate over the entire container.
526             std::queue<InputMessage> tmpQueue = messages;
527             while (!tmpQueue.empty()) {
528                 LOG_ALWAYS_FATAL_IF(tmpQueue.front().header.type != InputMessage::Type::MOTION);
529                 std::unique_ptr<MotionEvent> motion = createMotionEvent(tmpQueue.front());
530                 out += std::string("    ") + streamableToString(*motion) + "\n";
531                 tmpQueue.pop();
532             }
533         }
534     }
535 
536     return out;
537 }
538 
539 } // namespace android
540