InputMonitor connected to epoll dispatcher 85/44785/7
authorMateusz Malicki <m.malicki2@samsung.com>
Mon, 27 Jul 2015 15:28:22 +0000 (17:28 +0200)
committerDariusz Michaluk <d.michaluk@samsung.com>
Mon, 3 Aug 2015 15:16:07 +0000 (08:16 -0700)
[Feature]       InputMonitor connected to epoll dispatcher
[Cause]         N/A
[Solution]      N/A
[Verification]  Build, run tests

Change-Id: If2bdcd8c8493bc22357aa93b43b5c6e530c954d4

server/input-monitor.cpp
server/input-monitor.hpp
server/zones-manager.cpp
server/zones-manager.hpp
tests/unit_tests/server/ut-input-monitor.cpp

index e99906f..bb65b90 100644 (file)
 
 #include "input-monitor-config.hpp"
 #include "input-monitor.hpp"
+#include "zones-manager.hpp"
 #include "exception.hpp"
 
 #include "logger/logger.hpp"
 #include "utils/exception.hpp"
 #include "utils/fs.hpp"
-#include "utils/callback-wrapper.hpp"
-#include "utils/scoped-gerror.hpp"
+#include "utils/fd-utils.hpp"
 
 #include <cassert>
 #include <cstring>
 #include <fcntl.h>
-#include <functional>
-#include <glib.h>
 #include <linux/input.h>
 #include <sys/stat.h>
 #include <sys/types.h>
@@ -64,10 +62,13 @@ const std::string DEVICE_DIR = "/dev/input/";
 } // namespace
 
 
-InputMonitor::InputMonitor(const InputConfig& inputConfig,
-                           const NotifyCallback& notifyCallback)
-    : mConfig(inputConfig),
-      mNotifyCallback(notifyCallback)
+InputMonitor::InputMonitor(ipc::epoll::EventPoll& eventPoll,
+                           const InputConfig& inputConfig,
+                           ZonesManager* zonesManager)
+    : mConfig(inputConfig)
+    , mZonesManager(zonesManager)
+    , mFd(-1)
+    , mEventPoll(eventPoll)
 {
     if (mConfig.timeWindowMs > MAX_TIME_WINDOW_SEC * 1000L) {
         LOGE("Time window exceeds maximum: " << MAX_TIME_WINDOW_SEC);
@@ -79,37 +80,39 @@ InputMonitor::InputMonitor(const InputConfig& inputConfig,
         throw InputMonitorException("Number of events exceeds maximum");
     }
 
-    std::string devicePath = getDevicePath();
+    mDevicePath = getDevicePath();
 
     LOGT("Input monitor configuration: \n"
          << "\tenabled: " << mConfig.enabled << "\n"
          << "\tdevice: " << mConfig.device << "\n"
-         << "\tpath: " << devicePath << "\n"
+         << "\tpath: " << mDevicePath << "\n"
          << "\ttype: " << EV_KEY << "\n"
          << "\tcode: " << mConfig.code << "\n"
          << "\tvalue: " << KEY_PRESSED << "\n"
          << "\tnumberOfEvents: " << mConfig.numberOfEvents << "\n"
          << "\ttimeWindowMs: " << mConfig.timeWindowMs);
-
-    createGIOChannel(devicePath);
 }
 
 InputMonitor::~InputMonitor()
 {
+    std::unique_lock<std::mutex> mMutex;
     LOGD("Destroying InputMonitor");
-    ScopedGError error;
-    g_io_channel_unref(mChannelPtr);
-    if (g_io_channel_shutdown(mChannelPtr, FALSE, &error)
-            != G_IO_STATUS_NORMAL) {
-        LOGE("Error during shutting down GIOChannel: " << error->message);
-    }
+    stop();
+}
 
-    if (!g_source_remove(mSourceId)) {
-        LOGE("Error during removing the source");
-    }
+void InputMonitor::start()
+{
+    std::unique_lock<Mutex> mMutex;
+    setHandler(mDevicePath);
+}
+
+void InputMonitor::stop()
+{
+    leaveDevice();
 }
 
 namespace {
+
 bool isDeviceWithName(const boost::regex& deviceNameRegex,
                       const fs::directory_entry& directoryEntry)
 {
@@ -119,7 +122,7 @@ bool isDeviceWithName(const boost::regex& deviceNameRegex,
         return false;
     }
 
-    int fd = open(path.c_str(), O_RDONLY);
+    int fd = ::open(path.c_str(), O_RDONLY);
     if (fd < 0) {
         LOGD("Failed to open " << path);
         return false;
@@ -128,13 +131,13 @@ bool isDeviceWithName(const boost::regex& deviceNameRegex,
     char name[DEVICE_NAME_LENGTH];
     if (ioctl(fd, EVIOCGNAME(sizeof(name)), name) < 0) {
         LOGD("Failed to get the device name of: " << path);
-        if (close(fd) < 0) {
+        if (::close(fd) < 0) {
             LOGE("Error during closing file " << path);
         }
         return false;
     }
 
-    if (close(fd) < 0) {
+    if (::close(fd) < 0) {
         LOGE("Error during closing file " << path);
     }
 
@@ -146,6 +149,7 @@ bool isDeviceWithName(const boost::regex& deviceNameRegex,
 
     return false;
 }
+
 } // namespace
 
 std::string InputMonitor::getDevicePath() const
@@ -174,80 +178,47 @@ std::string InputMonitor::getDevicePath() const
     return it->path().string();
 }
 
-
-
-void InputMonitor::createGIOChannel(const std::string& devicePath)
+void InputMonitor::setHandler(const std::string& devicePath)
 {
+    using namespace std::placeholders;
+
     // We need NONBLOCK for FIFOs in the tests
-    int fd = open(devicePath.c_str(), O_RDONLY | O_NONBLOCK);
-    if (fd < 0) {
+    mFd = ::open(devicePath.c_str(), O_RDONLY | O_NONBLOCK | O_CLOEXEC);
+    if (mFd < 0) {
         LOGE("Cannot create input monitor channel. Device file: " <<
              devicePath << " doesn't exist");
         throw InputMonitorException("Device does not exist");
     }
-
-    mChannelPtr = g_io_channel_unix_new(fd);
-
-    // Read binary data
-    if (g_io_channel_set_encoding(mChannelPtr,
-                                  NULL,
-                                  NULL) != G_IO_STATUS_NORMAL) {
-        LOGE("Cannot set encoding for input monitor channel ");
-        throw InputMonitorException("Cannot set encoding");
-    }
-
-    using namespace std::placeholders;
-    ReadDeviceCallback callback = std::bind(&InputMonitor::readDevice, this, _1);
-    // Add the callback
-    mSourceId = g_io_add_watch_full(mChannelPtr,
-                                    G_PRIORITY_DEFAULT,
-                                    G_IO_IN,
-                                    readDeviceCallback,
-                                    utils::createCallbackWrapper(callback, mGuard.spawn()),
-                                    &utils::deleteCallbackWrapper<ReadDeviceCallback>);
-    if (!mSourceId) {
-        LOGE("Cannot add watch on device input file");
-        throw InputMonitorException("Cannot add watch");
-    }
+    mEventPoll.addFD(mFd, EPOLLIN, std::bind(&InputMonitor::handleInternal, this, _1, _2));
 }
 
-gboolean InputMonitor::readDeviceCallback(GIOChannel* gio,
-        GIOCondition /*condition*/,
-        gpointer data)
-{
-    const ReadDeviceCallback& callback = utils::getCallbackFromPointer<ReadDeviceCallback>(data);
-    callback(gio);
-    return TRUE;
-}
-
-void InputMonitor::readDevice(GIOChannel* gio)
+void InputMonitor::handleInternal(int /* fd */, ipc::epoll::Events events)
 {
     struct input_event ie;
-    gsize nBytesReadTotal = 0;
-    gsize nBytesRequested = sizeof(struct input_event);
-    ScopedGError error;
-
-    do {
-        gsize nBytesRead = 0;
-        GIOStatus readStatus = g_io_channel_read_chars(gio,
-                               &reinterpret_cast<gchar*>(&ie)[nBytesReadTotal],
-                               nBytesRequested,
-                               &nBytesRead,
-                               &error);
-
-        if (readStatus == G_IO_STATUS_ERROR) {
-            LOGE("Read from input monitor channel failed: " << error->message);
+    try {
+        if (events == EPOLLHUP) {
+            stop();
             return;
         }
-
-        nBytesRequested -= nBytesRead;
-        nBytesReadTotal += nBytesRead;
-    } while (nBytesRequested > 0);
-
-
+        utils::read(mFd, &ie, sizeof(struct input_event));
+    } catch (const std::exception& ex) {
+        LOGE("Read from input monitor channel failed: " << ex.what());
+        return;
+    }
     if (isExpectedEventSequence(ie)) {
         LOGI("Input monitor detected pattern.");
-        mNotifyCallback();
+        if (mZonesManager->isRunning()) {
+            mZonesManager->switchingSequenceMonitorNotify();
+        }
+    }
+}
+
+void InputMonitor::leaveDevice()
+{
+    if (mFd != -1) {
+        mEventPoll.removeFD(mFd);
+        utils::close(mFd);
+        mFd = -1;
     }
 }
 
index 2b0ed45..7ab5344 100644 (file)
 #define SERVER_INPUT_MONITOR_HPP
 
 #include "input-monitor-config.hpp"
-#include "utils/callback-guard.hpp"
+#include "ipc/epoll/event-poll.hpp"
 
 #include <linux/input.h>
 #include <sys/time.h>
-#include <glib.h>
 
-#include <functional>
 #include <string>
 #include <list>
+#include <mutex>
 
 
 namespace vasum {
 
+class ZonesManager;
+
 class InputMonitor {
 public:
-    typedef std::function<void()> NotifyCallback;
-
-    InputMonitor(const InputConfig& inputConfig,
-                 const NotifyCallback& notifyCallback);
+    InputMonitor(ipc::epoll::EventPoll& eventPoll,
+                 const InputConfig& inputConfig,
+                 ZonesManager* zonesManager);
     ~InputMonitor();
 
+    void start();
+    void stop();
 private:
-    typedef std::function<void(GIOChannel* gio)> ReadDeviceCallback;
+    typedef std::mutex Mutex;
 
     InputConfig mConfig;
-    NotifyCallback mNotifyCallback;
-
+    ZonesManager* mZonesManager;
+    int mFd;
+    ipc::epoll::EventPoll& mEventPoll;
     std::list<struct timeval> mEventTimes;
-    GIOChannel* mChannelPtr;
+    std::string mDevicePath;
+    Mutex mMutex;
 
     std::string getDevicePath() const;
-    void createGIOChannel(const std::string& devicePath);
-
-    // Internal callback to be registered at glib g_io_add_watch()
-    static gboolean readDeviceCallback(GIOChannel*, GIOCondition, gpointer);
+    void setHandler(const std::string& devicePath);
+    void handleInternal(int fd, ipc::epoll::Events events);
+    void leaveDevice();
     bool isExpectedEventSequence(const struct input_event&);
-    void readDevice(GIOChannel*);
-    utils::CallbackGuard mGuard;
-    guint mSourceId;
 };
 
 } // namespace vasum
index 484b3e9..0d88d24 100644 (file)
@@ -173,6 +173,11 @@ ZonesManager::ZonesManager(ipc::epoll::EventPoll& eventPoll, const std::string&
                                         configPath,
                                         mDynamicConfig,
                                         getVasumDbPrefix());
+
+    if (mConfig.inputConfig.enabled) {
+        LOGI("Registering input monitor [" << mConfig.inputConfig.device.c_str() << "]");
+        mSwitchingSequenceMonitor.reset(new InputMonitor(eventPoll, mConfig.inputConfig, this));
+    }
 }
 
 ZonesManager::~ZonesManager()
@@ -207,11 +212,8 @@ void ZonesManager::start()
     LOGD("ZonesManager object instantiated");
 
     if (mConfig.inputConfig.enabled) {
-        LOGI("Registering input monitor [" << mConfig.inputConfig.device.c_str() << "]");
-        mSwitchingSequenceMonitor.reset(
-            new InputMonitor(mConfig.inputConfig,
-                             std::bind(&ZonesManager::switchingSequenceMonitorNotify,
-                                       this)));
+        LOGI("Starting input monitor ");
+        mSwitchingSequenceMonitor->start();
     }
 
     // After everything's initialized start to respond to clients' requests
@@ -238,6 +240,10 @@ void ZonesManager::stop(bool wait)
     // wait for all tasks to complete
     mWorker.reset();
     mHostIPCConnection.stop(wait);
+    if (mConfig.inputConfig.enabled) {
+        LOGI("Stopping input monitor ");
+        mSwitchingSequenceMonitor->stop();
+    }
     mIsRunning = false;
 }
 
index de6663d..c6f6918 100644 (file)
@@ -198,6 +198,8 @@ public:
                                    api::MethodResultBuilder::Pointer result);
     void handleCleanUpZonesRootCall(api::MethodResultBuilder::Pointer result);
 
+    void switchingSequenceMonitorNotify();
+
 private:
     typedef std::recursive_mutex Mutex;
     typedef std::unique_lock<Mutex> Lock;
@@ -227,7 +229,6 @@ private:
     void saveDynamicConfig();
     void updateDefaultId();
     void refocus();
-    void switchingSequenceMonitorNotify();
     void generateNewConfig(const std::string& id,
                            const std::string& templatePath);
     std::string getTemplatePathForExistingZone(const std::string& id);
index 22fba47..75079a2 100644 (file)
 #include "input-monitor.hpp"
 #include "input-monitor-config.hpp"
 #include "exception.hpp"
+#include "zones-manager.hpp"
 
 #include "utils/glib-loop.hpp"
 #include "utils/latch.hpp"
 #include "utils/scoped-dir.hpp"
+#include "ipc/epoll/thread-dispatcher.hpp"
 
 #include <sys/types.h>
 #include <sys/stat.h>
@@ -44,7 +46,6 @@
 #include <memory>
 #include <string>
 
-
 using namespace vasum;
 using namespace utils;
 
@@ -58,16 +59,24 @@ const int EVENT_CODE = 139;
 const int EVENT_BUTTON_RELEASED = 0;
 const int EVENT_BUTTON_PRESSED = 1;
 
-const int SINGLE_EVENT_TIMEOUT = 1000;
+const int EVENT_TIMEOUT = 1000;
+
+const std::string CONFIG_DIR = VSM_TEST_CONFIG_INSTALL_DIR;
+const std::string TEST_CONFIG_PATH = CONFIG_DIR + "/test-daemon.conf";
+const std::string SIMPLE_TEMPLATE = "console-ipc";
+const std::string ZONES_PATH = "/tmp/ut-zones"; // the same as in daemon.conf
 
 struct Fixture {
     utils::ScopedGlibLoop mLoop;
     ScopedDir mTestPathGuard;
+    ScopedDir mZonesPathGuard;
     InputConfig inputConfig;
     struct input_event ie;
+    ipc::epoll::ThreadDispatcher dispatcher;
 
     Fixture()
         : mTestPathGuard(TEST_DIR)
+        , mZonesPathGuard(ZONES_PATH)
     {
         inputConfig.numberOfEvents = 2;
         inputConfig.device = TEST_INPUT_DEVICE;
@@ -84,46 +93,65 @@ struct Fixture {
 
         BOOST_CHECK(::mkfifo(TEST_INPUT_DEVICE.c_str(), S_IWUSR | S_IRUSR) >= 0);
     }
+
+    ipc::epoll::EventPoll& getPoll() {
+        return dispatcher.getPoll();
+    }
 };
+
+template<class Predicate>
+bool spinWaitFor(int timeoutMs, Predicate pred)
+{
+    auto until = std::chrono::steady_clock::now() + std::chrono::milliseconds(timeoutMs);
+    while (!pred()) {
+        if (std::chrono::steady_clock::now() >= until) {
+            return false;
+        }
+        std::this_thread::sleep_for(std::chrono::milliseconds(100));
+    }
+    return true;
+}
+
 } // namespace
 
 BOOST_FIXTURE_TEST_SUITE(InputMonitorSuite, Fixture)
 
 BOOST_AUTO_TEST_CASE(ConfigOK)
 {
-    InputMonitor inputMonitor(inputConfig, InputMonitor::NotifyCallback());
+    ZonesManager cm(getPoll(), TEST_CONFIG_PATH);
+    InputMonitor inputMonitor(getPoll(), inputConfig, &cm);
 }
 
 BOOST_AUTO_TEST_CASE(ConfigTimeWindowMsTooHigh)
 {
+    ZonesManager cm(getPoll(), TEST_CONFIG_PATH);
     inputConfig.timeWindowMs = 50000;
 
-    BOOST_REQUIRE_EXCEPTION(InputMonitor inputMonitor(inputConfig, InputMonitor::NotifyCallback()),
+    BOOST_REQUIRE_EXCEPTION(InputMonitor inputMonitor(getPoll(), inputConfig, &cm),
                             InputMonitorException,
                             WhatEquals("Time window exceeds maximum"));
 }
 
 BOOST_AUTO_TEST_CASE(ConfigDeviceFilePathNotExisting)
 {
+    ZonesManager cm(getPoll(), TEST_CONFIG_PATH);
     inputConfig.device = TEST_INPUT_DEVICE + "notExisting";
 
-    BOOST_REQUIRE_EXCEPTION(InputMonitor inputMonitor(inputConfig, InputMonitor::NotifyCallback()),
+    BOOST_REQUIRE_EXCEPTION(InputMonitor inputMonitor(getPoll(), inputConfig, &cm),
                             InputMonitorException,
                             WhatEquals("Cannot find a device"));
 }
 
 namespace {
 
-void sendNEvents(Fixture& f, unsigned int noOfEventsToSend)
+void sendEvent(Fixture& f, ZonesManager& cm)
 {
-    Latch eventLatch;
-
-    InputMonitor inputMonitor(f.inputConfig, [&] {eventLatch.set();});
-
+    InputMonitor inputMonitor(f.getPoll(), f.inputConfig, &cm);
+    inputMonitor.start();
     int fd = ::open(TEST_INPUT_DEVICE.c_str(), O_WRONLY);
     BOOST_REQUIRE(fd >= 0);
 
-    for (unsigned int i = 0; i < noOfEventsToSend * f.inputConfig.numberOfEvents; ++i) {
+    for (int i = 0; i < f.inputConfig.numberOfEvents; ++i) {
         // button pressed event
         f.ie.value = EVENT_BUTTON_PRESSED;
         f.ie.time.tv_usec += 5;
@@ -140,36 +168,52 @@ void sendNEvents(Fixture& f, unsigned int noOfEventsToSend)
     }
 
     BOOST_CHECK(::close(fd) >= 0);
-    BOOST_CHECK(eventLatch.waitForN(noOfEventsToSend, SINGLE_EVENT_TIMEOUT * noOfEventsToSend));
-
-    // Check if no more events are waiting
-    BOOST_CHECK(!eventLatch.wait(10));
 }
 
 } // namespace
 
-BOOST_AUTO_TEST_CASE(EventOneAtATime)
+BOOST_AUTO_TEST_CASE(SingleEvent)
 {
-    sendNEvents(*this, 1);
+    ZonesManager cm(getPoll(), TEST_CONFIG_PATH);
+    cm.start();
+    cm.createZone("zone1", SIMPLE_TEMPLATE);
+    cm.createZone("zone2", SIMPLE_TEMPLATE);
+    cm.createZone("zone3", SIMPLE_TEMPLATE);
+    cm.restoreAll();
+    sendEvent(*this, cm);
+    BOOST_CHECK(spinWaitFor(EVENT_TIMEOUT, [&] {
+            return cm.getRunningForegroundZoneId() == "zone2";
+    }));
 }
 
-BOOST_AUTO_TEST_CASE(EventTenAtATime)
+BOOST_AUTO_TEST_CASE(MultipleEvent)
 {
-    sendNEvents(*this, 10);
+    ZonesManager cm(getPoll(), TEST_CONFIG_PATH);
+    cm.start();
+    cm.createZone("zone1", SIMPLE_TEMPLATE);
+    cm.createZone("zone2", SIMPLE_TEMPLATE);
+    cm.createZone("zone3", SIMPLE_TEMPLATE);
+    cm.restoreAll();
+    for (int i = 1; i < 10; ++i) {
+        sendEvent(*this, cm);
+        std::string zoneId = "zone" + std::to_string(i % 3 + 1);
+        BOOST_CHECK(spinWaitFor(EVENT_TIMEOUT, [&] {
+            return cm.getRunningForegroundZoneId() == zoneId;
+        }));
+    }
 }
 
 namespace {
 
-void sendNEventsWithPauses(Fixture& f, unsigned int noOfEventsToSend)
+void sendEventWithPauses(Fixture& f, ZonesManager& cm)
 {
-    Latch eventLatch;
-
-    InputMonitor inputMonitor(f.inputConfig, [&] {eventLatch.set();});
+    InputMonitor inputMonitor(f.getPoll(), f.inputConfig, &cm);
+    inputMonitor.start();
 
     int fd = ::open(TEST_INPUT_DEVICE.c_str(), O_WRONLY);
     BOOST_REQUIRE(fd >= 0);
 
-    for (unsigned int i = 0; i < noOfEventsToSend * f.inputConfig.numberOfEvents; ++i) {
+    for (int i = 0; i < f.inputConfig.numberOfEvents; ++i) {
         // Send first two bytes of the button pressed event
         f.ie.value = EVENT_BUTTON_PRESSED;
         f.ie.time.tv_usec += 5;
@@ -191,22 +235,39 @@ void sendNEventsWithPauses(Fixture& f, unsigned int noOfEventsToSend)
         BOOST_CHECK(ret > 0);
     }
     BOOST_CHECK(::close(fd) >= 0);
-    BOOST_CHECK(eventLatch.waitForN(noOfEventsToSend, SINGLE_EVENT_TIMEOUT * noOfEventsToSend));
-
-    // Check if no more events are waiting
-    BOOST_CHECK(!eventLatch.wait(10));
 }
 
 } // namespace
 
-BOOST_AUTO_TEST_CASE(EventOneAtATimeWithPauses)
+BOOST_AUTO_TEST_CASE(SingleEventWithPauses)
 {
-    sendNEventsWithPauses(*this, 1);
+    ZonesManager cm(getPoll(), TEST_CONFIG_PATH);
+    cm.start();
+    cm.createZone("zone1", SIMPLE_TEMPLATE);
+    cm.createZone("zone2", SIMPLE_TEMPLATE);
+    cm.createZone("zone3", SIMPLE_TEMPLATE);
+    cm.restoreAll();
+    sendEventWithPauses(*this, cm);
+    BOOST_CHECK(spinWaitFor(EVENT_TIMEOUT, [&] {
+            return cm.getRunningForegroundZoneId() == "zone2";
+    }));
 }
 
-BOOST_AUTO_TEST_CASE(EventTenAtATimeWithPauses)
+BOOST_AUTO_TEST_CASE(MultipleEventWithPauses)
 {
-    sendNEventsWithPauses(*this, 10);
+    ZonesManager cm(getPoll(), TEST_CONFIG_PATH);
+    cm.start();
+    cm.createZone("zone1", SIMPLE_TEMPLATE);
+    cm.createZone("zone2", SIMPLE_TEMPLATE);
+    cm.createZone("zone3", SIMPLE_TEMPLATE);
+    cm.restoreAll();
+    for (int i = 1; i < 10; ++i) {
+        sendEventWithPauses(*this, cm);
+        std::string zoneId = "zone" + std::to_string(i % 3 + 1);
+        BOOST_CHECK(spinWaitFor(EVENT_TIMEOUT, [&] {
+            return cm.getRunningForegroundZoneId() == zoneId;
+        }));
+    }
 }