/* * 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 #include #include #include #include #include #include #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&, InputEvent&, nsecs_t)>; using DeviceCbFunc = std::function&)>; static const InputCbFunc kNoopInputCb = [](const std::shared_ptr&, InputEvent&, nsecs_t){}; static const DeviceCbFunc kNoopDeviceCb = [](const std::shared_ptr&){}; 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& node, InputEvent& event, nsecs_t event_time) override { mInputCb(node, event, event_time); } virtual void onDeviceAdded(const std::shared_ptr& node) override { mDeviceAddedCb(node); } virtual void onDeviceRemoved(const std::shared_ptr& node) override { mDeviceRemovedCb(node); } private: InputCbFunc mInputCb; DeviceCbFunc mDeviceAddedCb; DeviceCbFunc mDeviceRemovedCb; }; class InputHubTest : public ::testing::Test { protected: virtual void SetUp() { mCallback = std::make_shared(); mInputHub = std::make_shared(mCallback); } std::shared_ptr mCallback; std::shared_ptr 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(); std::string pathname; // Expect that this callback will run and set handle and pathname. mCallback->setDeviceAddedCallback( [&](const std::shared_ptr& node) { pathname = node->getPath(); }); ASSERT_EQ(OK, mInputHub->registerDevicePath(tempDir->getName())); // Create a new file in tempDir after 100ms. std::unique_ptr tempFile; std::mutex tempFileMutex; auto f = delay_async(100ms, [&]() { std::lock_guard 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 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(); auto deviceFile = std::unique_ptr(tempDir->newTempFile()); std::string tempFileName(deviceFile->getName()); std::shared_ptr tempNode; // Expect that these callbacks will run for the above device file. mCallback->setDeviceAddedCallback( [&](const std::shared_ptr& node) { tempNode = node; }); mCallback->setDeviceRemovedCallback( [&](const std::shared_ptr& 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(); auto deviceFile = std::unique_ptr(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(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& 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(); auto deviceFile1 = std::unique_ptr(tempDir->newTempFile()); auto deviceFile2 = std::unique_ptr(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&, 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& 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(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