| /* | |
| * Copyright (C) 2018 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 "keychords.h" | |
| #include <dirent.h> | |
| #include <fcntl.h> | |
| #include <linux/input.h> | |
| #include <linux/uinput.h> | |
| #include <stdint.h> | |
| #include <sys/types.h> | |
| #include <chrono> | |
| #include <set> | |
| #include <string> | |
| #include <vector> | |
| #include <android-base/properties.h> | |
| #include <android-base/stringprintf.h> | |
| #include <android-base/strings.h> | |
| #include <gtest/gtest.h> | |
| #include "epoll.h" | |
| using namespace std::chrono_literals; | |
| namespace android { | |
| namespace init { | |
| namespace { | |
| // This class is used to inject keys. | |
| class EventHandler { | |
| public: | |
| EventHandler(); | |
| EventHandler(const EventHandler&) = delete; | |
| EventHandler(EventHandler&&) noexcept; | |
| EventHandler& operator=(const EventHandler&) = delete; | |
| EventHandler& operator=(EventHandler&&) noexcept; | |
| ~EventHandler() noexcept; | |
| bool init(); | |
| bool send(struct input_event& e); | |
| bool send(uint16_t type, uint16_t code, uint16_t value); | |
| bool send(uint16_t code, bool value); | |
| private: | |
| int fd_; | |
| }; | |
| EventHandler::EventHandler() : fd_(-1) {} | |
| EventHandler::EventHandler(EventHandler&& rval) noexcept : fd_(rval.fd_) { | |
| rval.fd_ = -1; | |
| } | |
| EventHandler& EventHandler::operator=(EventHandler&& rval) noexcept { | |
| fd_ = rval.fd_; | |
| rval.fd_ = -1; | |
| return *this; | |
| } | |
| EventHandler::~EventHandler() { | |
| if (fd_ == -1) return; | |
| ::ioctl(fd_, UI_DEV_DESTROY); | |
| ::close(fd_); | |
| } | |
| bool EventHandler::init() { | |
| if (fd_ != -1) return true; | |
| auto fd = TEMP_FAILURE_RETRY(::open("/dev/uinput", O_WRONLY | O_NONBLOCK | O_CLOEXEC)); | |
| if (fd == -1) return false; | |
| if (::ioctl(fd, UI_SET_EVBIT, EV_KEY) == -1) { | |
| ::close(fd); | |
| return false; | |
| } | |
| static const struct uinput_user_dev u = { | |
| .name = "com.google.android.init.test", | |
| .id.bustype = BUS_VIRTUAL, | |
| .id.vendor = 0x1AE0, // Google | |
| .id.product = 0x494E, // IN | |
| .id.version = 1, | |
| }; | |
| if (TEMP_FAILURE_RETRY(::write(fd, &u, sizeof(u))) != sizeof(u)) { | |
| ::close(fd); | |
| return false; | |
| } | |
| // all keys | |
| for (uint16_t i = 0; i < KEY_MAX; ++i) { | |
| if (::ioctl(fd, UI_SET_KEYBIT, i) == -1) { | |
| ::close(fd); | |
| return false; | |
| } | |
| } | |
| if (::ioctl(fd, UI_DEV_CREATE) == -1) { | |
| ::close(fd); | |
| return false; | |
| } | |
| fd_ = fd; | |
| return true; | |
| } | |
| bool EventHandler::send(struct input_event& e) { | |
| gettimeofday(&e.time, nullptr); | |
| return TEMP_FAILURE_RETRY(::write(fd_, &e, sizeof(e))) == sizeof(e); | |
| } | |
| bool EventHandler::send(uint16_t type, uint16_t code, uint16_t value) { | |
| struct input_event e = {.type = type, .code = code, .value = value}; | |
| return send(e); | |
| } | |
| bool EventHandler::send(uint16_t code, bool value) { | |
| return (code < KEY_MAX) && init() && send(EV_KEY, code, value) && send(EV_SYN, SYN_REPORT, 0); | |
| } | |
| std::string InitFds(const char* prefix, pid_t pid = getpid()) { | |
| std::string ret; | |
| std::string init_fds("/proc/"); | |
| init_fds += std::to_string(pid) + "/fd"; | |
| std::unique_ptr<DIR, decltype(&closedir)> fds(opendir(init_fds.c_str()), closedir); | |
| if (!fds) return ret; | |
| dirent* entry; | |
| while ((entry = readdir(fds.get()))) { | |
| if (entry->d_name[0] == '.') continue; | |
| std::string devname = init_fds + '/' + entry->d_name; | |
| char buf[256]; | |
| auto retval = readlink(devname.c_str(), buf, sizeof(buf) - 1); | |
| if ((retval < 0) || (size_t(retval) >= (sizeof(buf) - 1))) continue; | |
| buf[retval] = '\0'; | |
| if (!android::base::StartsWith(buf, prefix)) continue; | |
| if (ret.size() != 0) ret += ","; | |
| ret += buf; | |
| } | |
| return ret; | |
| } | |
| std::string InitInputFds() { | |
| return InitFds("/dev/input/"); | |
| } | |
| std::string InitInotifyFds() { | |
| return InitFds("anon_inode:inotify"); | |
| } | |
| // NB: caller (this series of tests, or conversely the service parser in init) | |
| // is responsible for validation, sorting and uniqueness of the chords, so no | |
| // fuzzing is advised. | |
| const std::vector<int> escape_chord = {KEY_ESC}; | |
| const std::vector<int> triple1_chord = {KEY_BACKSPACE, KEY_VOLUMEDOWN, KEY_VOLUMEUP}; | |
| const std::vector<int> triple2_chord = {KEY_VOLUMEDOWN, KEY_VOLUMEUP, KEY_BACK}; | |
| const std::vector<const std::vector<int>> empty_chords; | |
| const std::vector<const std::vector<int>> chords = { | |
| escape_chord, | |
| triple1_chord, | |
| triple2_chord, | |
| }; | |
| class TestFrame { | |
| public: | |
| TestFrame(const std::vector<const std::vector<int>>& chords, EventHandler* ev = nullptr); | |
| void RelaxForMs(std::chrono::milliseconds wait = 1ms); | |
| void SetChord(int key, bool value = true); | |
| void SetChords(const std::vector<int>& chord, bool value = true); | |
| void ClrChord(int key); | |
| void ClrChords(const std::vector<int>& chord); | |
| bool IsOnlyChord(const std::vector<int>& chord) const; | |
| bool IsNoChord() const; | |
| bool IsChord(const std::vector<int>& chord) const; | |
| void WaitForChord(const std::vector<int>& chord); | |
| std::string Format() const; | |
| private: | |
| static std::string Format(const std::vector<const std::vector<int>>& chords); | |
| Epoll epoll_; | |
| Keychords keychords_; | |
| std::vector<const std::vector<int>> keycodes_; | |
| EventHandler* ev_; | |
| }; | |
| TestFrame::TestFrame(const std::vector<const std::vector<int>>& chords, EventHandler* ev) | |
| : ev_(ev) { | |
| if (!epoll_.Open()) return; | |
| for (const auto& keycodes : chords) keychords_.Register(keycodes); | |
| keychords_.Start(&epoll_, [this](const std::vector<int>& keycodes) { | |
| this->keycodes_.emplace_back(keycodes); | |
| }); | |
| } | |
| void TestFrame::RelaxForMs(std::chrono::milliseconds wait) { | |
| epoll_.Wait(wait).IgnoreError(); | |
| } | |
| void TestFrame::SetChord(int key, bool value) { | |
| ASSERT_TRUE(!!ev_); | |
| RelaxForMs(); | |
| EXPECT_TRUE(ev_->send(key, value)); | |
| } | |
| void TestFrame::SetChords(const std::vector<int>& chord, bool value) { | |
| ASSERT_TRUE(!!ev_); | |
| for (auto& key : chord) SetChord(key, value); | |
| RelaxForMs(); | |
| } | |
| void TestFrame::ClrChord(int key) { | |
| ASSERT_TRUE(!!ev_); | |
| SetChord(key, false); | |
| } | |
| void TestFrame::ClrChords(const std::vector<int>& chord) { | |
| ASSERT_TRUE(!!ev_); | |
| SetChords(chord, false); | |
| } | |
| bool TestFrame::IsOnlyChord(const std::vector<int>& chord) const { | |
| auto ret = false; | |
| for (const auto& keycode : keycodes_) { | |
| if (keycode != chord) return false; | |
| ret = true; | |
| } | |
| return ret; | |
| } | |
| bool TestFrame::IsNoChord() const { | |
| return keycodes_.empty(); | |
| } | |
| bool TestFrame::IsChord(const std::vector<int>& chord) const { | |
| for (const auto& keycode : keycodes_) { | |
| if (keycode == chord) return true; | |
| } | |
| return false; | |
| } | |
| void TestFrame::WaitForChord(const std::vector<int>& chord) { | |
| for (int retry = 1000; retry && !IsChord(chord); --retry) RelaxForMs(); | |
| } | |
| std::string TestFrame::Format(const std::vector<const std::vector<int>>& chords) { | |
| std::string ret("{"); | |
| if (!chords.empty()) { | |
| ret += android::base::Join(chords.front(), ' '); | |
| for (auto it = std::next(chords.begin()); it != chords.end(); ++it) { | |
| ret += ','; | |
| ret += android::base::Join(*it, ' '); | |
| } | |
| } | |
| return ret + '}'; | |
| } | |
| std::string TestFrame::Format() const { | |
| return Format(keycodes_); | |
| } | |
| } // namespace | |
| TEST(keychords, not_instantiated) { | |
| TestFrame test_frame(empty_chords); | |
| EXPECT_TRUE(InitInotifyFds().size() == 0); | |
| } | |
| TEST(keychords, instantiated) { | |
| // Test if a valid set of chords results in proper instantiation of the | |
| // underlying mechanisms for /dev/input/ attachment. | |
| TestFrame test_frame(chords); | |
| EXPECT_TRUE(InitInotifyFds().size() != 0); | |
| } | |
| TEST(keychords, init_inotify) { | |
| std::string before(InitInputFds()); | |
| TestFrame test_frame(chords); | |
| EventHandler ev; | |
| EXPECT_TRUE(ev.init()); | |
| for (int retry = 1000; retry && before == InitInputFds(); --retry) test_frame.RelaxForMs(); | |
| std::string after(InitInputFds()); | |
| EXPECT_NE(before, after); | |
| } | |
| TEST(keychords, key) { | |
| EventHandler ev; | |
| EXPECT_TRUE(ev.init()); | |
| TestFrame test_frame(chords, &ev); | |
| test_frame.SetChords(escape_chord); | |
| test_frame.WaitForChord(escape_chord); | |
| test_frame.ClrChords(escape_chord); | |
| EXPECT_TRUE(test_frame.IsOnlyChord(escape_chord)) | |
| << "expected only " << android::base::Join(escape_chord, ' ') << " got " | |
| << test_frame.Format(); | |
| } | |
| TEST(keychords, keys_in_series) { | |
| EventHandler ev; | |
| EXPECT_TRUE(ev.init()); | |
| TestFrame test_frame(chords, &ev); | |
| for (auto& key : triple1_chord) { | |
| test_frame.SetChord(key); | |
| test_frame.ClrChord(key); | |
| } | |
| test_frame.WaitForChord(triple1_chord); | |
| EXPECT_TRUE(test_frame.IsNoChord()) << "expected nothing got " << test_frame.Format(); | |
| } | |
| TEST(keychords, keys_in_parallel) { | |
| EventHandler ev; | |
| EXPECT_TRUE(ev.init()); | |
| TestFrame test_frame(chords, &ev); | |
| test_frame.SetChords(triple2_chord); | |
| test_frame.WaitForChord(triple2_chord); | |
| test_frame.ClrChords(triple2_chord); | |
| EXPECT_TRUE(test_frame.IsOnlyChord(triple2_chord)) | |
| << "expected only " << android::base::Join(triple2_chord, ' ') << " got " | |
| << test_frame.Format(); | |
| } | |
| } // namespace init | |
| } // namespace android |