Create a helper object to wrap polling on multiple file descriptors

This helper API can be used to reliably poll on a dynamically increasing
set of file descriptors, and it provides a functional callback on top
of the C poll() API. As is, it only supports POLLIN as that is what
the Sensor API needs, but more event types can be added

Bug: 158763319
Test: atest --host android.hardware.sensors@2.0-Google-IIO-Subhal_test-MultiPoll
Change-Id: Ifabd1d06b595e93bfaf8e0ffab7b783fbcc73b40
Merged-In: Ifabd1d06b595e93bfaf8e0ffab7b783fbcc73b40
diff --git a/hal/sensors/2.0/Android.bp b/hal/sensors/2.0/Android.bp
index 7f7fd80..0ed62a1 100644
--- a/hal/sensors/2.0/Android.bp
+++ b/hal/sensors/2.0/Android.bp
@@ -18,6 +18,7 @@
     vendor: true,
     srcs: [
         "iio_utils.cpp",
+        "MultiPoll.cpp",
         "Sensor.cpp",
         "SensorsSubHal.cpp",
         "SensorThread.cpp",
@@ -50,3 +51,20 @@
     generated_sources: ["sensor_hal_configuration_V1_0"],
     generated_headers: ["sensor_hal_configuration_V1_0"],
 }
+
+cc_test_host {
+    name: "android.hardware.sensors@2.0-Google-IIO-Subhal_test-MultiPoll",
+    srcs: [
+        "MultiPoll.cpp",
+        "tests/MultiPoll.cpp"
+    ],
+    static_libs: [
+        "libgtest",
+        "android.hardware.sensors@1.0",
+        "android.hardware.sensors@2.0",
+    ],
+    shared_libs: [
+        "libbase",
+        "liblog",
+    ],
+}
diff --git a/hal/sensors/2.0/MultiPoll.cpp b/hal/sensors/2.0/MultiPoll.cpp
new file mode 100644
index 0000000..7014c98
--- /dev/null
+++ b/hal/sensors/2.0/MultiPoll.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#include "MultiPoll.h"
+
+namespace android::hardware::sensors::V2_0::subhal::implementation {
+
+MultiPoll::MultiPoll(uint64_t periodMs) : mSamplingPeriodMs(periodMs) {}
+
+void MultiPoll::addDescriptor(int fd) {
+    pollfd pfd{.fd = fd, .events = POLLIN, .revents = 0};
+    std::unique_lock<std::mutex> lck(mDescriptorsMutex);
+    mDescriptors.push_back(pfd);
+}
+
+int MultiPoll::poll(OnPollIn in) {
+    std::vector<pollfd> fds;
+    {
+        // make a copy so you don't need to lock for prolonged periods of time
+        std::unique_lock<std::mutex> lck(mDescriptorsMutex);
+        fds.assign(mDescriptors.begin(), mDescriptors.end());
+    }
+
+    int err = ::poll(&fds[0], fds.size(), mSamplingPeriodMs);
+    if (err < 0) return err;
+
+    for (const auto& fd : fds) {
+        if (fd.revents & POLLIN) {
+            in(fd.fd);
+        }
+    }
+
+    return 0;
+}
+
+}  // namespace android::hardware::sensors::V2_0::subhal::implementation
diff --git a/hal/sensors/2.0/MultiPoll.h b/hal/sensors/2.0/MultiPoll.h
new file mode 100644
index 0000000..e6ee889
--- /dev/null
+++ b/hal/sensors/2.0/MultiPoll.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2021 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 <poll.h>
+#include <functional>
+#include <mutex>
+#include <vector>
+
+namespace android::hardware::sensors::V2_0::subhal::implementation {
+
+class MultiPoll {
+  public:
+    explicit MultiPoll(uint64_t periodMs = 0);
+    ~MultiPoll() = default;
+
+    // TODO(egranata): add support for events other than POLLIN
+    void addDescriptor(int fd);
+    using OnPollIn = std::function<void(int fd)>;
+    int poll(OnPollIn in);
+
+  private:
+    uint64_t mSamplingPeriodMs;
+    std::mutex mDescriptorsMutex;
+    std::vector<pollfd> mDescriptors;
+};
+
+}  // namespace android::hardware::sensors::V2_0::subhal::implementation
diff --git a/hal/sensors/2.0/tests/MultiPoll.cpp b/hal/sensors/2.0/tests/MultiPoll.cpp
new file mode 100644
index 0000000..5adef7e
--- /dev/null
+++ b/hal/sensors/2.0/tests/MultiPoll.cpp
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#include <gtest/gtest.h>
+#include <unistd.h>
+#include <chrono>
+#include <thread>
+
+#include "MultiPoll.h"
+
+using android::hardware::sensors::V2_0::subhal::implementation::MultiPoll;
+
+// using namespace here is the sanctioned C++ way
+// NOLINTNEXTLINE(build/namespaces)
+using namespace std::chrono_literals;
+
+class PipeHelper {
+  public:
+    PipeHelper() {
+        int pipefd[2] = {0, 0};
+        int err = ::pipe(pipefd);
+        if (err == 0) {
+            mReadFd = pipefd[0];
+            mWriteFd = pipefd[1];
+        } else {
+            mReadFd = mWriteFd = -1;
+        }
+    }
+
+    ~PipeHelper() {
+        close(mReadFd);
+        close(mWriteFd);
+    }
+
+    int readFd() const { return mReadFd; }
+    int writeFd() const { return mWriteFd; }
+
+    explicit operator bool() const { return (mReadFd >= 0) && (mWriteFd >= 0); }
+
+    size_t read(char* buf, size_t n) { return ::read(mReadFd, reinterpret_cast<void*>(buf), n); }
+    size_t write(const char* buf, size_t n) {
+        return ::write(mWriteFd, reinterpret_cast<const void*>(buf), n);
+    }
+
+  private:
+    int mReadFd;
+    int mWriteFd;
+};
+
+TEST(MultiPollTest, EmptyList) {
+    MultiPoll mp(100);
+    bool called = false;
+    MultiPoll::OnPollIn f = [&called](int) -> void { called = true; };
+    mp.poll(f);
+    EXPECT_FALSE(called);
+}
+
+TEST(MultiPollTest, DataAvailable) {
+    MultiPoll mp(100);
+    PipeHelper pe;
+    ASSERT_TRUE(pe);
+
+    mp.addDescriptor(pe.readFd());
+    pe.write("hello", 5);
+    bool called = false;
+    int poll_fd;
+    MultiPoll::OnPollIn f = [&called, &poll_fd](int fd) -> void {
+        called = true;
+        poll_fd = fd;
+    };
+    mp.poll(f);
+    EXPECT_TRUE(called);
+    EXPECT_EQ(poll_fd, pe.readFd());
+}
+
+TEST(MultiPollTest, DataComesUpLater) {
+    MultiPoll mp(120000 /* 2 minutes */);
+    PipeHelper pe;
+    ASSERT_TRUE(pe);
+    mp.addDescriptor(pe.readFd());
+
+    bool called = false;
+    int poll_fd;
+    MultiPoll::OnPollIn f = [&called, &poll_fd](int fd) -> void {
+        called = true;
+        poll_fd = fd;
+    };
+    std::thread pollerThread([&mp, &f]() -> void { mp.poll(f); });
+
+    std::this_thread::sleep_for(100ms);
+    pe.write("hello", 5);
+
+    pollerThread.join();
+    EXPECT_TRUE(called);
+    EXPECT_EQ(poll_fd, pe.readFd());
+}
+
+TEST(MultiPollTest, OneFdHasData) {
+    MultiPoll mp(100);
+    PipeHelper p1;
+    PipeHelper p2;
+
+    mp.addDescriptor(p1.readFd());
+    mp.addDescriptor(p2.readFd());
+
+    int called = 0;
+    MultiPoll::OnPollIn f = [&called](int) -> void { ++called; };
+
+    p1.write("hello", 5);
+    mp.poll(f);
+    EXPECT_EQ(1, called);
+}
+
+TEST(MultiPollTest, TwoFdHaveData) {
+    MultiPoll mp(100);
+    PipeHelper p1;
+    PipeHelper p2;
+
+    mp.addDescriptor(p1.readFd());
+    mp.addDescriptor(p2.readFd());
+
+    int called = 0;
+    int prev_fd = -1;
+    bool repeat_fd = false;
+    MultiPoll::OnPollIn f = [&called, &prev_fd, &repeat_fd](int fd) -> void {
+        ++called;
+        if (prev_fd == fd) repeat_fd = true;
+        prev_fd = fd;
+    };
+
+    p1.write("hello", 5);
+    p2.write("hi", 2);
+    mp.poll(f);
+    EXPECT_EQ(2, called);
+    EXPECT_FALSE(repeat_fd);
+    EXPECT_TRUE(prev_fd == p1.readFd() || prev_fd == p2.readFd());
+}
+
+TEST(MultiPollTest, ZeroWait) {
+    MultiPoll mp(0);
+    PipeHelper pe;
+    ASSERT_TRUE(pe);
+    mp.addDescriptor(pe.readFd());
+
+    bool called = false;
+    int poll_fd;
+    MultiPoll::OnPollIn f = [&called, &poll_fd](int fd) -> void {
+        called = true;
+        poll_fd = fd;
+    };
+    std::thread pollerThread([&mp, &f, &called]() -> void {
+        while (!called) mp.poll(f);
+    });
+
+    std::this_thread::sleep_for(100ms);
+    pe.write("hello", 5);
+
+    pollerThread.join();
+    EXPECT_TRUE(called);
+    EXPECT_EQ(poll_fd, pe.readFd());
+}
+
+TEST(MultiPollTest, AddOneLater) {
+    MultiPoll mp(100);
+    PipeHelper p1;
+    PipeHelper p2;
+
+    mp.addDescriptor(p1.readFd());
+
+    bool called = false;
+    int poll_fd;
+    MultiPoll::OnPollIn f = [&called, &poll_fd](int fd) -> void {
+        called = true;
+        poll_fd = fd;
+    };
+
+    std::thread pollerThread([&mp, &f, &called]() -> void {
+        while (!called) mp.poll(f);
+    });
+
+    std::this_thread::sleep_for(250ms);
+    mp.addDescriptor(p2.readFd());
+    std::this_thread::sleep_for(100ms);
+    p2.write("hello", 5);
+
+    pollerThread.join();
+    EXPECT_TRUE(called);
+    EXPECT_EQ(p2.readFd(), poll_fd);
+}