You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
257 lines
9.2 KiB
257 lines
9.2 KiB
/* |
|
* 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
|
|
|