Merge "Do not crash when server channel has closed" into main
diff --git a/libs/input/InputConsumerNoResampling.cpp b/libs/input/InputConsumerNoResampling.cpp
index 2c0f77a..cd85821 100644
--- a/libs/input/InputConsumerNoResampling.cpp
+++ b/libs/input/InputConsumerNoResampling.cpp
@@ -169,6 +169,12 @@
     msg.body.timeline.graphicsTimeline[GraphicsTimeline::PRESENT_TIME] = presentTime;
     return msg;
 }
+
+std::ostream& operator<<(std::ostream& out, const InputMessage& msg) {
+    out << ftl::enum_string(msg.header.type);
+    return out;
+}
+
 } // namespace
 
 // --- InputConsumerNoResampling ---
@@ -272,6 +278,15 @@
             return; // try again later
         }
 
+        if (result == DEAD_OBJECT) {
+            // If there's no one to receive events in the channel, there's no point in sending them.
+            // Drop all outbound events.
+            LOG(INFO) << "Channel " << mChannel->getName() << " died. Dropping outbound event "
+                      << outboundMsg;
+            mOutboundQueue.pop();
+            setFdEvents(0);
+            continue;
+        }
         // Some other error. Give up
         LOG(FATAL) << "Failed to send outbound event on channel '" << mChannel->getName()
                    << "'.  status=" << statusToString(result) << "(" << result << ")";
diff --git a/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp b/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp
index 39bb841..1dadae9 100644
--- a/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp
+++ b/libs/input/tests/InputPublisherAndConsumerNoResampling_test.cpp
@@ -319,6 +319,8 @@
 
 protected:
     // Interaction with the looper thread
+    void blockLooper();
+    void unblockLooper();
     enum class LooperMessage : int {
         CALL_PROBABLY_HAS_INPUT,
         CREATE_CONSUMER,
@@ -389,6 +391,26 @@
     };
 };
 
+void InputPublisherAndConsumerNoResamplingTest::blockLooper() {
+    {
+        std::scoped_lock l(mLock);
+        mLooperMayProceed = false;
+    }
+    sendMessage(LooperMessage::BLOCK_LOOPER);
+    {
+        std::unique_lock l(mLock);
+        mNotifyLooperWaiting.wait(l, [this] { return mLooperIsBlocked; });
+    }
+}
+
+void InputPublisherAndConsumerNoResamplingTest::unblockLooper() {
+    {
+        std::scoped_lock l(mLock);
+        mLooperMayProceed = true;
+    }
+    mNotifyLooperMayProceed.notify_all();
+}
+
 void InputPublisherAndConsumerNoResamplingTest::sendMessage(LooperMessage message) {
     Message msg{ftl::to_underlying(message)};
     mLooper->sendMessage(mMessageHandler, msg);
@@ -600,15 +622,7 @@
     std::queue<uint32_t> publishedSequenceNumbers;
 
     // Block Looper to increase the chance of batching events
-    {
-        std::scoped_lock l(mLock);
-        mLooperMayProceed = false;
-    }
-    sendMessage(LooperMessage::BLOCK_LOOPER);
-    {
-        std::unique_lock l(mLock);
-        mNotifyLooperWaiting.wait(l, [this] { return mLooperIsBlocked; });
-    }
+    blockLooper();
 
     uint32_t firstSampleId;
     for (size_t i = 0; i < nSamples; ++i) {
@@ -629,12 +643,7 @@
 
     std::vector<MotionEvent> singleSampledMotionEvents;
 
-    // Unblock Looper
-    {
-        std::scoped_lock l(mLock);
-        mLooperMayProceed = true;
-    }
-    mNotifyLooperMayProceed.notify_all();
+    unblockLooper();
 
     // We have no control over the socket behavior, so the consumer can receive
     // the motion as a batched event, or as a sequence of multiple single-sample MotionEvents (or a
@@ -809,6 +818,15 @@
     verifyFinishedSignal(*mPublisher, seq, publishTime);
 }
 
+/**
+ * If the publisher has died, consumer should not crash when trying to send an outgoing message.
+ */
+TEST_F(InputPublisherAndConsumerNoResamplingTest, ConsumerWritesAfterPublisherDies) {
+    mPublisher.reset(); // The publisher has died
+    mReportTimelineArgs.emplace(/*inputEventId=*/10, /*gpuCompletedTime=*/20, /*presentTime=*/30);
+    sendMessage(LooperMessage::CALL_REPORT_TIMELINE);
+}
+
 TEST_F(InputPublisherAndConsumerNoResamplingTest, SendTimeline) {
     const int32_t inputEventId = 20;
     const nsecs_t gpuCompletedTime = 30;