TouchEvent (1/n): Adding TouchModeEvent to InputChannel

This CL detaches the touch mode state update from focus update. It does
that by introducing a new internal event (TouchModeEvent). This CL also
adds this event to InputChannel and related processing logic to
InputPublisher and InputConsumer. InputDispatcher will process two
different events now: FocusEvent when gaining/losing focus and
TouchModeEvent when entering/leaving touch mode.

Test: atest libinput_tests
Bug: 193718270
Change-Id: Ie4e5b6e8e798f12d7203127b4559fa40d38788de
diff --git a/libs/input/Input.cpp b/libs/input/Input.cpp
index 9390467..30d82b6 100644
--- a/libs/input/Input.cpp
+++ b/libs/input/Input.cpp
@@ -163,6 +163,9 @@
         case AINPUT_EVENT_TYPE_DRAG: {
             return "DRAG";
         }
+        case AINPUT_EVENT_TYPE_TOUCH_MODE: {
+            return "TOUCH_MODE";
+        }
     }
     return "UNKNOWN";
 }
@@ -882,6 +885,19 @@
     mY = from.mY;
 }
 
+// --- TouchModeEvent ---
+
+void TouchModeEvent::initialize(int32_t id, bool isInTouchMode) {
+    InputEvent::initialize(id, ReservedInputDeviceId::VIRTUAL_KEYBOARD_ID, AINPUT_SOURCE_UNKNOWN,
+                           ADISPLAY_ID_NONE, INVALID_HMAC);
+    mIsInTouchMode = isInTouchMode;
+}
+
+void TouchModeEvent::initialize(const TouchModeEvent& from) {
+    InputEvent::initialize(from);
+    mIsInTouchMode = from.mIsInTouchMode;
+}
+
 // --- PooledInputEventFactory ---
 
 PooledInputEventFactory::PooledInputEventFactory(size_t maxPoolSize) :
@@ -936,6 +952,15 @@
     return event;
 }
 
+TouchModeEvent* PooledInputEventFactory::createTouchModeEvent() {
+    if (mTouchModeEventPool.empty()) {
+        return new TouchModeEvent();
+    }
+    TouchModeEvent* event = mTouchModeEventPool.front().release();
+    mTouchModeEventPool.pop();
+    return event;
+}
+
 void PooledInputEventFactory::recycle(InputEvent* event) {
     switch (event->getType()) {
     case AINPUT_EVENT_TYPE_KEY:
diff --git a/libs/input/InputTransport.cpp b/libs/input/InputTransport.cpp
index d6c1161..595c9d9 100644
--- a/libs/input/InputTransport.cpp
+++ b/libs/input/InputTransport.cpp
@@ -116,6 +116,7 @@
         case Type::FOCUS:
         case Type::CAPTURE:
         case Type::DRAG:
+        case Type::TOUCH_MODE:
             return true;
         case Type::TIMELINE: {
             const nsecs_t gpuCompletedTime =
@@ -151,6 +152,8 @@
             return sizeof(Header) + body.drag.size();
         case Type::TIMELINE:
             return sizeof(Header) + body.timeline.size();
+        case Type::TOUCH_MODE:
+            return sizeof(Header) + body.touchMode.size();
     }
     return sizeof(Header);
 }
@@ -291,6 +294,10 @@
             msg->body.timeline.graphicsTimeline = body.timeline.graphicsTimeline;
             break;
         }
+        case InputMessage::Type::TOUCH_MODE: {
+            msg->body.touchMode.eventId = body.touchMode.eventId;
+            msg->body.touchMode.isInTouchMode = body.touchMode.isInTouchMode;
+        }
     }
 }
 
@@ -661,6 +668,22 @@
     return mChannel->sendMessage(&msg);
 }
 
+status_t InputPublisher::publishTouchModeEvent(uint32_t seq, int32_t eventId, bool isInTouchMode) {
+    if (ATRACE_ENABLED()) {
+        std::string message =
+                StringPrintf("publishTouchModeEvent(inputChannel=%s, isInTouchMode=%s)",
+                             mChannel->getName().c_str(), toString(isInTouchMode));
+        ATRACE_NAME(message.c_str());
+    }
+
+    InputMessage msg;
+    msg.header.type = InputMessage::Type::TOUCH_MODE;
+    msg.header.seq = seq;
+    msg.body.touchMode.eventId = eventId;
+    msg.body.touchMode.isInTouchMode = isInTouchMode;
+    return mChannel->sendMessage(&msg);
+}
+
 android::base::Result<InputPublisher::ConsumerResponse> InputPublisher::receiveConsumerResponse() {
     if (DEBUG_TRANSPORT_ACTIONS) {
         ALOGD("channel '%s' publisher ~ %s", mChannel->getName().c_str(), __func__);
@@ -862,6 +885,16 @@
                 *outEvent = dragEvent;
                 break;
             }
+
+            case InputMessage::Type::TOUCH_MODE: {
+                TouchModeEvent* touchModeEvent = factory->createTouchModeEvent();
+                if (!touchModeEvent) return NO_MEMORY;
+
+                initializeTouchModeEvent(touchModeEvent, &mMsg);
+                *outSeq = mMsg.header.seq;
+                *outEvent = touchModeEvent;
+                break;
+            }
         }
     }
     return OK;
@@ -1366,6 +1399,10 @@
                       pointerProperties, pointerCoords);
 }
 
+void InputConsumer::initializeTouchModeEvent(TouchModeEvent* event, const InputMessage* msg) {
+    event->initialize(msg->body.touchMode.eventId, msg->body.touchMode.isInTouchMode);
+}
+
 void InputConsumer::addSample(MotionEvent* event, const InputMessage* msg) {
     uint32_t pointerCount = msg->body.motion.pointerCount;
     PointerCoords pointerCoords[pointerCount];
@@ -1472,6 +1509,11 @@
                                                        presentTime);
                     break;
                 }
+                case InputMessage::Type::TOUCH_MODE: {
+                    out += android::base::StringPrintf("isInTouchMode=%s",
+                                                       toString(msg.body.touchMode.isInTouchMode));
+                    break;
+                }
             }
             out += "\n";
         }
diff --git a/libs/input/tests/InputPublisherAndConsumer_test.cpp b/libs/input/tests/InputPublisherAndConsumer_test.cpp
index d0c337c..8e6f97c 100644
--- a/libs/input/tests/InputPublisherAndConsumer_test.cpp
+++ b/libs/input/tests/InputPublisherAndConsumer_test.cpp
@@ -56,6 +56,7 @@
     void PublishAndConsumeFocusEvent();
     void PublishAndConsumeCaptureEvent();
     void PublishAndConsumeDragEvent();
+    void PublishAndConsumeTouchModeEvent();
 };
 
 TEST_F(InputPublisherAndConsumerTest, GetChannel_ReturnsTheChannel) {
@@ -411,6 +412,46 @@
             << "finished signal's consume time should be greater than publish time";
 }
 
+void InputPublisherAndConsumerTest::PublishAndConsumeTouchModeEvent() {
+    status_t status;
+
+    constexpr uint32_t seq = 15;
+    int32_t eventId = InputEvent::nextId();
+    constexpr bool touchModeEnabled = true;
+    const nsecs_t publishTime = systemTime(SYSTEM_TIME_MONOTONIC);
+
+    status = mPublisher->publishTouchModeEvent(seq, eventId, touchModeEnabled);
+    ASSERT_EQ(OK, status) << "publisher publishTouchModeEvent should return OK";
+
+    uint32_t consumeSeq;
+    InputEvent* event;
+    status = mConsumer->consume(&mEventFactory, true /*consumeBatches*/, -1, &consumeSeq, &event);
+    ASSERT_EQ(OK, status) << "consumer consume should return OK";
+
+    ASSERT_TRUE(event != nullptr) << "consumer should have returned non-NULL event";
+    ASSERT_EQ(AINPUT_EVENT_TYPE_TOUCH_MODE, event->getType())
+            << "consumer should have returned a touch mode event";
+
+    const TouchModeEvent& touchModeEvent = static_cast<const TouchModeEvent&>(*event);
+    EXPECT_EQ(seq, consumeSeq);
+    EXPECT_EQ(eventId, touchModeEvent.getId());
+    EXPECT_EQ(touchModeEnabled, touchModeEvent.isInTouchMode());
+
+    status = mConsumer->sendFinishedSignal(seq, true);
+    ASSERT_EQ(OK, status) << "consumer sendFinishedSignal should return OK";
+
+    Result<InputPublisher::ConsumerResponse> result = mPublisher->receiveConsumerResponse();
+    ASSERT_TRUE(result.ok()) << "receiveConsumerResponse should return OK";
+    ASSERT_TRUE(std::holds_alternative<InputPublisher::Finished>(*result));
+    const InputPublisher::Finished& finish = std::get<InputPublisher::Finished>(*result);
+    ASSERT_EQ(seq, finish.seq)
+            << "receiveConsumerResponse should have returned the original sequence number";
+    ASSERT_TRUE(finish.handled)
+            << "receiveConsumerResponse should have set handled to consumer's reply";
+    ASSERT_GE(finish.consumeTime, publishTime)
+            << "finished signal's consume time should be greater than publish time";
+}
+
 TEST_F(InputPublisherAndConsumerTest, SendTimeline) {
     const int32_t inputEventId = 20;
     std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline;
@@ -447,6 +488,10 @@
     ASSERT_NO_FATAL_FAILURE(PublishAndConsumeDragEvent());
 }
 
+TEST_F(InputPublisherAndConsumerTest, PublishTouchModeEvent_EndToEnd) {
+    ASSERT_NO_FATAL_FAILURE(PublishAndConsumeTouchModeEvent());
+}
+
 TEST_F(InputPublisherAndConsumerTest, PublishMotionEvent_WhenSequenceNumberIsZero_ReturnsError) {
     status_t status;
     const size_t pointerCount = 1;
@@ -515,6 +560,7 @@
     ASSERT_NO_FATAL_FAILURE(PublishAndConsumeDragEvent());
     ASSERT_NO_FATAL_FAILURE(PublishAndConsumeMotionEvent());
     ASSERT_NO_FATAL_FAILURE(PublishAndConsumeKeyEvent());
+    ASSERT_NO_FATAL_FAILURE(PublishAndConsumeTouchModeEvent());
 }
 
 } // namespace android
diff --git a/libs/input/tests/StructLayout_test.cpp b/libs/input/tests/StructLayout_test.cpp
index 5861d55..1a9ba50 100644
--- a/libs/input/tests/StructLayout_test.cpp
+++ b/libs/input/tests/StructLayout_test.cpp
@@ -102,6 +102,10 @@
   CHECK_OFFSET(InputMessage::Body::Timeline, eventId, 0);
   CHECK_OFFSET(InputMessage::Body::Timeline, empty, 4);
   CHECK_OFFSET(InputMessage::Body::Timeline, graphicsTimeline, 8);
+
+  CHECK_OFFSET(InputMessage::Body::TouchMode, eventId, 0);
+  CHECK_OFFSET(InputMessage::Body::TouchMode, isInTouchMode, 4);
+  CHECK_OFFSET(InputMessage::Body::TouchMode, empty, 5);
 }
 
 void TestHeaderSize() {
@@ -123,6 +127,7 @@
     static_assert(sizeof(InputMessage::Body::Focus) == 8);
     static_assert(sizeof(InputMessage::Body::Capture) == 8);
     static_assert(sizeof(InputMessage::Body::Drag) == 16);
+    static_assert(sizeof(InputMessage::Body::TouchMode) == 8);
     // Timeline
     static_assert(GraphicsTimeline::SIZE == 2);
     static_assert(sizeof(InputMessage::Body::Timeline) == 24);