| /* |
| * Copyright (C) 2015 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 "InputHub.h" |
| |
| #include <chrono> |
| #include <memory> |
| #include <mutex> |
| |
| #include <linux/input.h> |
| |
| #include <gtest/gtest.h> |
| |
| #include <utils/StopWatch.h> |
| #include <utils/Timers.h> |
| |
| #include "TestHelpers.h" |
| |
| // # of milliseconds to fudge stopwatch measurements |
| #define TIMING_TOLERANCE_MS 25 |
| #define NO_TIMEOUT (-1) |
| |
| namespace android { |
| namespace tests { |
| |
| using namespace std::literals::chrono_literals; |
| |
| using InputCbFunc = std::function<void(const std::shared_ptr<InputDeviceNode>&, InputEvent&, nsecs_t)>; |
| using DeviceCbFunc = std::function<void(const std::shared_ptr<InputDeviceNode>&)>; |
| |
| static const InputCbFunc kNoopInputCb = [](const std::shared_ptr<InputDeviceNode>&, InputEvent&, nsecs_t){}; |
| static const DeviceCbFunc kNoopDeviceCb = [](const std::shared_ptr<InputDeviceNode>&){}; |
| |
| class TestInputCallback : public InputCallbackInterface { |
| public: |
| TestInputCallback() : |
| mInputCb(kNoopInputCb), mDeviceAddedCb(kNoopDeviceCb), mDeviceRemovedCb(kNoopDeviceCb) {} |
| virtual ~TestInputCallback() = default; |
| |
| void setInputCallback(const InputCbFunc& cb) { mInputCb = cb; } |
| void setDeviceAddedCallback(const DeviceCbFunc& cb) { mDeviceAddedCb = cb; } |
| void setDeviceRemovedCallback(const DeviceCbFunc& cb) { mDeviceRemovedCb = cb; } |
| |
| virtual void onInputEvent(const std::shared_ptr<InputDeviceNode>& node, InputEvent& event, |
| nsecs_t event_time) override { |
| mInputCb(node, event, event_time); |
| } |
| virtual void onDeviceAdded(const std::shared_ptr<InputDeviceNode>& node) override { |
| mDeviceAddedCb(node); |
| } |
| virtual void onDeviceRemoved(const std::shared_ptr<InputDeviceNode>& node) override { |
| mDeviceRemovedCb(node); |
| } |
| |
| private: |
| InputCbFunc mInputCb; |
| DeviceCbFunc mDeviceAddedCb; |
| DeviceCbFunc mDeviceRemovedCb; |
| }; |
| |
| class InputHubTest : public ::testing::Test { |
| protected: |
| virtual void SetUp() { |
| mCallback = std::make_shared<TestInputCallback>(); |
| mInputHub = std::make_shared<InputHub>(mCallback); |
| } |
| |
| std::shared_ptr<TestInputCallback> mCallback; |
| std::shared_ptr<InputHub> mInputHub; |
| }; |
| |
| TEST_F(InputHubTest, testWake) { |
| // Call wake() after 100ms. |
| auto f = delay_async(100ms, [&]() { EXPECT_EQ(OK, mInputHub->wake()); }); |
| |
| StopWatch stopWatch("poll"); |
| EXPECT_EQ(OK, mInputHub->poll()); |
| int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); |
| |
| EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS); |
| } |
| |
| TEST_F(InputHubTest, DISABLED_testDeviceAdded) { |
| auto tempDir = std::make_shared<TempDir>(); |
| std::string pathname; |
| // Expect that this callback will run and set handle and pathname. |
| mCallback->setDeviceAddedCallback( |
| [&](const std::shared_ptr<InputDeviceNode>& node) { |
| pathname = node->getPath(); |
| }); |
| |
| ASSERT_EQ(OK, mInputHub->registerDevicePath(tempDir->getName())); |
| |
| // Create a new file in tempDir after 100ms. |
| std::unique_ptr<TempFile> tempFile; |
| std::mutex tempFileMutex; |
| auto f = delay_async(100ms, |
| [&]() { |
| std::lock_guard<std::mutex> lock(tempFileMutex); |
| tempFile.reset(tempDir->newTempFile()); |
| }); |
| |
| StopWatch stopWatch("poll"); |
| EXPECT_EQ(OK, mInputHub->poll()); |
| int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); |
| |
| |
| EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS); |
| std::lock_guard<std::mutex> lock(tempFileMutex); |
| EXPECT_EQ(tempFile->getName(), pathname); |
| } |
| |
| TEST_F(InputHubTest, DISABLED_testDeviceRemoved) { |
| // Create a temp dir and file. Save its name and handle (to be filled in |
| // once InputHub scans the dir). |
| auto tempDir = std::make_unique<TempDir>(); |
| auto deviceFile = std::unique_ptr<TempFile>(tempDir->newTempFile()); |
| std::string tempFileName(deviceFile->getName()); |
| |
| std::shared_ptr<InputDeviceNode> tempNode; |
| // Expect that these callbacks will run for the above device file. |
| mCallback->setDeviceAddedCallback( |
| [&](const std::shared_ptr<InputDeviceNode>& node) { |
| tempNode = node; |
| }); |
| mCallback->setDeviceRemovedCallback( |
| [&](const std::shared_ptr<InputDeviceNode>& node) { |
| EXPECT_EQ(tempNode, node); |
| }); |
| |
| ASSERT_EQ(OK, mInputHub->registerDevicePath(tempDir->getName())); |
| // Ensure that tempDir was scanned to find the device. |
| ASSERT_TRUE(tempNode != nullptr); |
| |
| auto f = delay_async(100ms, [&]() { deviceFile.reset(); }); |
| |
| StopWatch stopWatch("poll"); |
| EXPECT_EQ(OK, mInputHub->poll()); |
| int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); |
| |
| EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS); |
| } |
| |
| TEST_F(InputHubTest, DISABLED_testInputEvent) { |
| // Create a temp dir and file. Save its name and handle (to be filled in |
| // once InputHub scans the dir.) |
| auto tempDir = std::make_unique<TempDir>(); |
| auto deviceFile = std::unique_ptr<TempFile>(tempDir->newTempFile()); |
| std::string tempFileName(deviceFile->getName()); |
| |
| // Send a key event corresponding to HOME. |
| struct input_event iev; |
| iev.time = { 1, 0 }; |
| iev.type = EV_KEY; |
| iev.code = KEY_HOME; |
| iev.value = 0x01; |
| |
| auto inputDelayMs = 100ms; |
| auto f = delay_async(inputDelayMs, [&] { |
| ssize_t nWrite = TEMP_FAILURE_RETRY(write(deviceFile->getFd(), &iev, sizeof(iev))); |
| |
| ASSERT_EQ(static_cast<ssize_t>(sizeof(iev)), nWrite) << "could not write to " |
| << deviceFile->getFd() << ". errno: " << errno; |
| }); |
| |
| // Expect this callback to run when the input event is read. |
| nsecs_t expectedWhen = systemTime(CLOCK_MONOTONIC) + ms2ns(inputDelayMs.count()); |
| mCallback->setInputCallback( |
| [&](const std::shared_ptr<InputDeviceNode>& node, InputEvent& event, |
| nsecs_t event_time) { |
| EXPECT_NEAR(expectedWhen, event_time, ms2ns(TIMING_TOLERANCE_MS)); |
| EXPECT_EQ(s2ns(1), event.when); |
| EXPECT_EQ(tempFileName, node->getPath()); |
| EXPECT_EQ(EV_KEY, event.type); |
| EXPECT_EQ(KEY_HOME, event.code); |
| EXPECT_EQ(0x01, event.value); |
| }); |
| ASSERT_EQ(OK, mInputHub->registerDevicePath(tempDir->getName())); |
| |
| StopWatch stopWatch("poll"); |
| EXPECT_EQ(OK, mInputHub->poll()); |
| int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); |
| |
| EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS); |
| } |
| |
| TEST_F(InputHubTest, DISABLED_testCallbackOrder) { |
| // Create two "devices": one to receive input and the other to go away. |
| auto tempDir = std::make_unique<TempDir>(); |
| auto deviceFile1 = std::unique_ptr<TempFile>(tempDir->newTempFile()); |
| auto deviceFile2 = std::unique_ptr<TempFile>(tempDir->newTempFile()); |
| std::string tempFileName(deviceFile2->getName()); |
| |
| bool inputCallbackFinished = false, deviceCallbackFinished = false; |
| |
| // Setup the callback for input events. Should run before the device |
| // callback. |
| mCallback->setInputCallback( |
| [&](const std::shared_ptr<InputDeviceNode>&, InputEvent&, nsecs_t) { |
| ASSERT_FALSE(deviceCallbackFinished); |
| inputCallbackFinished = true; |
| }); |
| |
| // Setup the callback for device removal. Should run after the input |
| // callback. |
| mCallback->setDeviceRemovedCallback( |
| [&](const std::shared_ptr<InputDeviceNode>& node) { |
| ASSERT_TRUE(inputCallbackFinished) |
| << "input callback did not run before device changed callback"; |
| // Make sure the correct device was removed. |
| EXPECT_EQ(tempFileName, node->getPath()); |
| deviceCallbackFinished = true; |
| }); |
| ASSERT_EQ(OK, mInputHub->registerDevicePath(tempDir->getName())); |
| |
| auto f = delay_async(100ms, |
| [&]() { |
| // Delete the second device file first. |
| deviceFile2.reset(); |
| |
| // Then inject an input event into the first device. |
| struct input_event iev; |
| iev.time = { 1, 0 }; |
| iev.type = EV_KEY; |
| iev.code = KEY_HOME; |
| iev.value = 0x01; |
| |
| ssize_t nWrite = TEMP_FAILURE_RETRY(write(deviceFile1->getFd(), &iev, sizeof(iev))); |
| |
| ASSERT_EQ(static_cast<ssize_t>(sizeof(iev)), nWrite) << "could not write to " |
| << deviceFile1->getFd() << ". errno: " << errno; |
| }); |
| |
| StopWatch stopWatch("poll"); |
| EXPECT_EQ(OK, mInputHub->poll()); |
| int32_t elapsedMillis = ns2ms(stopWatch.elapsedTime()); |
| |
| EXPECT_NEAR(100, elapsedMillis, TIMING_TOLERANCE_MS); |
| EXPECT_TRUE(inputCallbackFinished); |
| EXPECT_TRUE(deviceCallbackFinished); |
| } |
| |
| } // namespace tests |
| } // namespace android |