CEC: Add event handler to default HdmiCec

Event handler polls the file descriptor for
CEC messages and events

Bug: 185434120
Test: manual
Change-Id: Iaaec9a0a74b264e5ec8625d7fce3d821208fd5ac
diff --git a/tv/cec/1.0/default/HdmiCecDefault.cpp b/tv/cec/1.0/default/HdmiCecDefault.cpp
index 7dc0d24..c8dd511 100644
--- a/tv/cec/1.0/default/HdmiCecDefault.cpp
+++ b/tv/cec/1.0/default/HdmiCecDefault.cpp
@@ -22,7 +22,10 @@
 #include <fcntl.h>
 #include <linux/cec.h>
 #include <linux/ioctl.h>
+#include <poll.h>
+#include <pthread.h>
 #include <sys/eventfd.h>
+#include <algorithm>
 
 #include "HdmiCecDefault.h"
 
@@ -35,6 +38,7 @@
 
 int mCecFd;
 int mExitFd;
+pthread_t mEventThread;
 sp<IHdmiCecCallback> mCallback;
 
 HdmiCecDefault::HdmiCecDefault() {
@@ -281,7 +285,7 @@
         return Result::FAILURE_NOT_SUPPORTED;
     }
 
-    uint32_t mode = CEC_MODE_INITIATOR;
+    uint32_t mode = CEC_MODE_INITIATOR | CEC_MODE_EXCL_FOLLOWER_PASSTHRU;
     ret = ioctl(mCecFd, CEC_S_MODE, &mode);
     if (ret) {
         LOG(ERROR) << "Unable to set initiator mode, Error = " << strerror(errno);
@@ -289,6 +293,13 @@
         return Result::FAILURE_NOT_SUPPORTED;
     }
 
+    /* thread loop for receiving cec messages and hotplug events*/
+    if (pthread_create(&mEventThread, NULL, event_thread, NULL)) {
+        LOG(ERROR) << "Can't create event thread: " << strerror(errno);
+        release();
+        return Result::FAILURE_NOT_SUPPORTED;
+    }
+
     return Result::SUCCESS;
 }
 
@@ -296,6 +307,7 @@
     if (mExitFd > 0) {
         uint64_t tmp = 1;
         write(mExitFd, &tmp, sizeof(tmp));
+        pthread_join(mEventThread, NULL);
     }
     if (mExitFd > 0) {
         close(mExitFd);
@@ -306,6 +318,83 @@
     setCallback(nullptr);
     return Void();
 }
+
+void* HdmiCecDefault::event_thread(void*) {
+    struct pollfd ufds[3] = {
+            {mCecFd, POLLIN, 0},
+            {mCecFd, POLLERR, 0},
+            {mExitFd, POLLIN, 0},
+    };
+
+    while (1) {
+        ufds[0].revents = 0;
+        ufds[1].revents = 0;
+        ufds[2].revents = 0;
+
+        int ret = poll(ufds, /* size(ufds) = */ 3, /* timeout = */ -1);
+
+        if (ret <= 0) {
+            continue;
+        }
+
+        if (ufds[2].revents == POLLIN) { /* Exit */
+            break;
+        }
+
+        if (ufds[1].revents == POLLERR) { /* CEC Event */
+            struct cec_event ev;
+            ret = ioctl(mCecFd, CEC_DQEVENT, &ev);
+
+            if (ret) {
+                LOG(ERROR) << "CEC_DQEVENT failed, Error = " << strerror(errno);
+                continue;
+            }
+
+            if (ev.event == CEC_EVENT_STATE_CHANGE) {
+                if (mCallback != nullptr) {
+                    HotplugEvent hotplugEvent{
+                            .connected = (ev.state_change.phys_addr != CEC_PHYS_ADDR_INVALID),
+                            .portId = 1};
+                    mCallback->onHotplugEvent(hotplugEvent);
+                } else {
+                    LOG(ERROR) << "No event callback for hotplug";
+                }
+            }
+        }
+
+        if (ufds[0].revents == POLLIN) { /* CEC Driver */
+            struct cec_msg msg = {};
+            ret = ioctl(mCecFd, CEC_RECEIVE, &msg);
+
+            if (ret) {
+                LOG(ERROR) << "CEC_RECEIVE failed, Error = " << strerror(errno);
+                continue;
+            }
+
+            if (msg.rx_status != CEC_RX_STATUS_OK) {
+                LOG(ERROR) << "msg rx_status = " << msg.rx_status;
+                continue;
+            }
+
+            if (mCallback != nullptr) {
+                size_t length = std::min(msg.len - 1, (uint32_t)MaxLength::MESSAGE_BODY);
+                CecMessage cecMessage{
+                        .initiator = static_cast<CecLogicalAddress>(msg.msg[0] >> 4),
+                        .destination = static_cast<CecLogicalAddress>(msg.msg[0] & 0xf),
+                };
+                cecMessage.body.resize(length);
+                for (size_t i = 0; i < length; ++i) {
+                    cecMessage.body[i] = static_cast<uint8_t>(msg.msg[i + 1]);
+                }
+                mCallback->onCecMessage(cecMessage);
+            } else {
+                LOG(ERROR) << "no event callback for message";
+            }
+        }
+    }
+    return NULL;
+}
+
 }  // namespace implementation
 }  // namespace V1_0
 }  // namespace cec
diff --git a/tv/cec/1.0/default/HdmiCecDefault.h b/tv/cec/1.0/default/HdmiCecDefault.h
index 4812eca..c3682f7 100644
--- a/tv/cec/1.0/default/HdmiCecDefault.h
+++ b/tv/cec/1.0/default/HdmiCecDefault.h
@@ -47,6 +47,7 @@
 
     Return<Result> init();
     Return<void> release();
+    static void* event_thread(void*);
 };
 
 }  // namespace implementation