5f366ef448
am: 1c1e311e06
Change-Id: I00ee76484a102dc1577566fe370af16f9283fc3e
257 lines
9.2 KiB
C++
257 lines
9.2 KiB
C++
/*
|
|
* 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
|