/* * 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. */ #define LOG_TAG "InputHub_test" //#define LOG_NDEBUG 0 #include <linux/input.h> #include <chrono> #include <memory> #include <mutex> #include <gtest/gtest.h> #include <utils/Log.h> #include <utils/StopWatch.h> #include <utils/Timers.h> #include "InputHub.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(std::shared_ptr<InputDeviceNode>, InputEvent&, nsecs_t)>; using DeviceCbFunc = std::function<void(std::shared_ptr<InputDeviceNode>)>; static const InputCbFunc kNoopInputCb = [](std::shared_ptr<InputDeviceNode>, InputEvent&, nsecs_t){}; static const DeviceCbFunc kNoopDeviceCb = [](std::shared_ptr<InputDeviceNode>){}; class TestInputCallback : public InputCallbackInterface { public: TestInputCallback() : mInputCb(kNoopInputCb), mDeviceAddedCb(kNoopDeviceCb), mDeviceRemovedCb(kNoopDeviceCb) {} virtual ~TestInputCallback() = default; void setInputCallback(InputCbFunc cb) { mInputCb = cb; } void setDeviceAddedCallback(DeviceCbFunc cb) { mDeviceAddedCb = cb; } void setDeviceRemovedCallback(DeviceCbFunc cb) { mDeviceRemovedCb = cb; } virtual void onInputEvent(std::shared_ptr<InputDeviceNode> node, InputEvent& event, nsecs_t event_time) override { mInputCb(node, event, event_time); } virtual void onDeviceAdded(std::shared_ptr<InputDeviceNode> node) override { mDeviceAddedCb(node); } virtual void onDeviceRemoved(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( [&](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( [&](std::shared_ptr<InputDeviceNode> node) { tempNode = node; }); mCallback->setDeviceRemovedCallback( [&](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( [&](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( [&](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( [&](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