drm_hwcomposer: Add test utility to listen for uevents

Dumping uevents is useful for debugging purposes.

1. Extract logic related to uevent socket into utils/UEvent.h class.
2. Use it by both UEventListener.cpp and tests/uevent_print.cpp.

Bump clang-tidy level of UEventListener.cpp to normal.

Signed-off-by: Roman Stratiienko <roman.o.stratiienko@globallogic.com>
diff --git a/.ci/Makefile b/.ci/Makefile
index cadbde6..a0e4b73 100644
--- a/.ci/Makefile
+++ b/.ci/Makefile
@@ -26,7 +26,6 @@
     drm/DrmProperty.h:COARSE                            \
     drm/DrmUnique.h:FINE                                \
     drm/DrmProperty.cpp:COARSE                          \
-    drm/UEventListener.cpp:COARSE                       \
     drm/VSyncWorker.cpp:COARSE                          \
     hwc2_device/DrmHwcTwo.cpp:COARSE                    \
     hwc2_device/DrmHwcTwo.h:COARSE                      \
@@ -85,7 +84,7 @@
 
 # Build
 
-BUILD_FILES_AUTO := $(shell find -L $(SRC_DIR) -not -path '*/\.*' -not -path '*/tests/*' -path '*.cpp')
+BUILD_FILES_AUTO := $(shell find -L $(SRC_DIR) -not -path '*/\.*' -not -path '*/tests/test_include/*' -path '*.cpp')
 SKIP_FILES_path := $(foreach file,$(SKIP_FILES),$(SRC_DIR)/$(file))
 
 BUILD_FILES := $(subst ./,,$(filter-out $(SKIP_FILES_path),$(BUILD_FILES_AUTO)))
@@ -108,7 +107,7 @@
 	$(CLANG) $(CXXARGS) $< -MM -MT $(OUT_DIR)/$(patsubst %.cpp,%.o,$<) -o $@
 
 # TIDY
-TIDY_FILES_AUTO := $(shell find -L $(SRC_DIR) -not -path '*/\.*' -not -path '*/tests/*' \( -path '*.cpp' -o -path '*.h' \))
+TIDY_FILES_AUTO := $(shell find -L $(SRC_DIR) -not -path '*/\.*' -not -path '*/tests/test_include/*' \( -path '*.cpp' -o -path '*.h' \))
 
 TIDY_FILES_AUTO_filtered := $(filter-out $(SKIP_FILES_path),$(TIDY_FILES_AUTO))
 
@@ -145,7 +144,7 @@
 $$(_TARG): TIDY_ARGS := $$(TIDY_ARGS)
 $$(_TARG): $$(_DEP)
 	mkdir -p $$(dir $$(_TARG))
-	$$(CLANG_TIDY) $$(_DEP) $$(TIDY_ARGS) -- -x c++ $$(CXXARGS)
+	$$(CLANG_TIDY) $$(_DEP) $$(TIDY_ARGS) -- -x c++ $$(CXXARGS) -Wno-pragma-once-outside-header
 	touch $$(_TARG)
 
 endef
diff --git a/drm/UEventListener.cpp b/drm/UEventListener.cpp
index 8d33ad2..b56b8e1 100644
--- a/drm/UEventListener.cpp
+++ b/drm/UEventListener.cpp
@@ -18,82 +18,43 @@
 
 #include "UEventListener.h"
 
-#include <linux/netlink.h>
-#include <sys/socket.h>
-#include <xf86drm.h>
-
-#include <cassert>
 #include <cerrno>
-#include <cstring>
 
 #include "utils/log.h"
 
-/* Originally defined in system/core/libsystem/include/system/graphics.h */
-#define HAL_PRIORITY_URGENT_DISPLAY (-8)
+/* Originally defined in system/core/libsystem/include/system/graphics.h as
+ * #define HAL_PRIORITY_URGENT_DISPLAY (-8)*/
+constexpr int kHalPriorityUrgentDisplay = -8;
 
 namespace android {
 
 UEventListener::UEventListener()
-    : Worker("uevent-listener", HAL_PRIORITY_URGENT_DISPLAY){};
+    : Worker("uevent-listener", kHalPriorityUrgentDisplay){};
 
 int UEventListener::Init() {
-  uevent_fd_ = UniqueFd(
-      socket(PF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_KOBJECT_UEVENT));
-  if (!uevent_fd_) {
-    // NOLINTNEXTLINE(concurrency-mt-unsafe): Fixme
-    ALOGE("Failed to open uevent socket: %s", strerror(errno));
-    return -errno;
-  }
-
-  struct sockaddr_nl addr {};
-  addr.nl_family = AF_NETLINK;
-  addr.nl_pid = 0;
-  addr.nl_groups = 0xFFFFFFFF;
-
-  int ret = bind(uevent_fd_.Get(), (struct sockaddr *)&addr, sizeof(addr));
-  if (ret) {
-    // NOLINTNEXTLINE(concurrency-mt-unsafe): Fixme
-    ALOGE("Failed to bind uevent socket: %s", strerror(errno));
-    return -errno;
+  uevent_ = UEvent::CreateInstance();
+  if (!uevent_) {
+    return -ENODEV;
   }
 
   return InitWorker();
 }
 
 void UEventListener::Routine() {
-  char buffer[1024];
-  ssize_t ret = 0;
-
   while (true) {
-    ret = read(uevent_fd_.Get(), &buffer, sizeof(buffer));
-    if (ret == 0)
-      return;
+    auto uevent_str = uevent_->ReadNext();
 
-    if (ret < 0) {
-      ALOGE("Got error reading uevent %zd", ret);
-      return;
-    }
-
-    if (!hotplug_handler_)
+    if (!hotplug_handler_ || !uevent_str)
       continue;
 
-    bool drm_event = false;
-    bool hotplug_event = false;
-    for (uint32_t i = 0; (ssize_t)i < ret;) {
-      char *event = buffer + i;
-      if (strcmp(event, "DEVTYPE=drm_minor") != 0)
-        drm_event = true;
-      else if (strcmp(event, "HOTPLUG=1") != 0)
-        hotplug_event = true;
+    bool drm_event = uevent_str->find("DEVTYPE=drm_minor") != std::string::npos;
+    bool hotplug_event = uevent_str->find("HOTPLUG=1") != std::string::npos;
 
-      i += strlen(event) + 1;
-    }
-
-    if (drm_event && hotplug_event && hotplug_handler_) {
-      constexpr useconds_t delay_after_uevent_us = 200000;
+    if (drm_event && hotplug_event) {
+      constexpr useconds_t kDelayAfterUeventUs = 200000;
       /* We need some delay to ensure DrmConnector::UpdateModes() will query
        * correct modes list, otherwise at least RPI4 board may report 0 modes */
-      usleep(delay_after_uevent_us);
+      usleep(kDelayAfterUeventUs);
       hotplug_handler_();
     }
   }
diff --git a/drm/UEventListener.h b/drm/UEventListener.h
index 0724443..c8b8582 100644
--- a/drm/UEventListener.h
+++ b/drm/UEventListener.h
@@ -19,7 +19,7 @@
 
 #include <functional>
 
-#include "utils/UniqueFd.h"
+#include "utils/UEvent.h"
 #include "utils/Worker.h"
 
 namespace android {
@@ -39,7 +39,7 @@
   void Routine() override;
 
  private:
-  UniqueFd uevent_fd_;
+  std::unique_ptr<UEvent> uevent_;
 
   std::function<void()> hotplug_handler_;
 };
diff --git a/tests/Android.bp b/tests/Android.bp
index 56f8c4f..e56ff5c 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -33,3 +33,17 @@
         "external/drm_hwcomposer/include",
     ],
 }
+
+// Tool for listening and dumping uevents
+cc_test {
+    name: "hwc-drm-uevent-print",
+
+    srcs: ["uevent_print.cpp"],
+
+    vendor: true,
+    header_libs: ["libhardware_headers"],
+    shared_libs: ["liblog"],
+    include_dirs: [
+        "external/drm_hwcomposer",
+    ],
+}
diff --git a/tests/uevent_print.cpp b/tests/uevent_print.cpp
new file mode 100644
index 0000000..6ffbbfb
--- /dev/null
+++ b/tests/uevent_print.cpp
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#include <iostream>
+
+#include "utils/UEvent.h"
+
+int main() {
+  auto uevent = android::UEvent::CreateInstance();
+  if (!uevent) {
+    std::cout << "Can't initialize UEvent class" << std::endl;
+    return -ENODEV;
+  }
+
+  int number = 0;
+  for (;;) {
+    auto msg = uevent->ReadNext();
+    if (!msg) {
+      continue;
+    }
+
+    std::cout << "New event #" << number++ << std::endl
+              << *msg << std::endl
+              << std::endl;
+  }
+}
diff --git a/utils/UEvent.h b/utils/UEvent.h
new file mode 100644
index 0000000..17b3cab
--- /dev/null
+++ b/utils/UEvent.h
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <linux/netlink.h>
+#include <sys/socket.h>
+
+#include <cerrno>
+#include <memory>
+#include <optional>
+#include <string>
+
+#include "UniqueFd.h"
+#include "log.h"
+
+namespace android {
+
+class UEvent {
+ public:
+  static auto CreateInstance() -> std::unique_ptr<UEvent> {
+    auto fd = UniqueFd(
+        socket(PF_NETLINK, SOCK_DGRAM | SOCK_CLOEXEC, NETLINK_KOBJECT_UEVENT));
+
+    if (!fd) {
+      ALOGE("Failed to open uevent socket: errno=%i", errno);
+      return {};
+    }
+
+    struct sockaddr_nl addr {};
+    addr.nl_family = AF_NETLINK;
+    addr.nl_pid = 0;
+    addr.nl_groups = UINT32_MAX;
+
+    // NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast)
+    int ret = bind(fd.Get(), (struct sockaddr *)&addr, sizeof(addr));
+    if (ret != 0) {
+      ALOGE("Failed to bind uevent socket: errno=%i", errno);
+      return {};
+    }
+
+    return std::unique_ptr<UEvent>(new UEvent(fd));
+  }
+
+  auto ReadNext() -> std::optional<std::string> {
+    constexpr int kUEventBufferSize = 1024;
+    char buffer[kUEventBufferSize];
+    ssize_t ret = 0;
+    ret = read(fd_.Get(), &buffer, sizeof(buffer));
+    if (ret == 0)
+      return {};
+
+    if (ret < 0) {
+      ALOGE("Got error reading uevent %zd", ret);
+      return {};
+    }
+
+    for (int i = 0; i < ret - 1; i++) {
+      if (buffer[i] == '\0') {
+        buffer[i] = '\n';
+      }
+    }
+
+    return std::string(buffer);
+  }
+
+ private:
+  explicit UEvent(UniqueFd &fd) : fd_(std::move(fd)){};
+  UniqueFd fd_;
+};
+
+}  // namespace android