diff options
| -rw-r--r-- | include/input/TouchVideoFrame.h | 8 | ||||
| -rw-r--r-- | services/inputflinger/Android.bp | 8 | ||||
| -rw-r--r-- | services/inputflinger/BlockingQueue.h | 98 | ||||
| -rw-r--r-- | services/inputflinger/InputClassifier.cpp | 692 | ||||
| -rw-r--r-- | services/inputflinger/InputClassifier.h | 204 | ||||
| -rw-r--r-- | services/inputflinger/InputListener.cpp | 70 | ||||
| -rw-r--r-- | services/inputflinger/InputManager.cpp | 3 | ||||
| -rw-r--r-- | services/inputflinger/InputManager.h | 4 | ||||
| -rw-r--r-- | services/inputflinger/include/InputListener.h | 10 | ||||
| -rw-r--r-- | services/inputflinger/tests/Android.bp | 6 | ||||
| -rw-r--r-- | services/inputflinger/tests/BlockingQueue_test.cpp | 142 | ||||
| -rw-r--r-- | services/inputflinger/tests/InputClassifier_test.cpp | 233 | ||||
| -rw-r--r-- | services/inputflinger/tests/InputReader_test.cpp | 125 | ||||
| -rw-r--r-- | services/inputflinger/tests/TestInputListener.cpp | 115 | ||||
| -rw-r--r-- | services/inputflinger/tests/TestInputListener.h | 73 |
15 files changed, 1672 insertions, 119 deletions
diff --git a/include/input/TouchVideoFrame.h b/include/input/TouchVideoFrame.h index d68f27431a..640bf1f693 100644 --- a/include/input/TouchVideoFrame.h +++ b/include/input/TouchVideoFrame.h @@ -35,6 +35,14 @@ public: mWidth(width), mHeight(height), mData(std::move(data)), mTimestamp(timestamp) { } + bool operator==(const TouchVideoFrame& rhs) const { + return mWidth == rhs.mWidth + && mHeight == rhs.mHeight + && mData == rhs.mData + && mTimestamp.tv_sec == rhs.mTimestamp.tv_sec + && mTimestamp.tv_usec == rhs.mTimestamp.tv_usec; + } + /** * Width of the frame */ diff --git a/services/inputflinger/Android.bp b/services/inputflinger/Android.bp index e3a237ef73..f73d498bb2 100644 --- a/services/inputflinger/Android.bp +++ b/services/inputflinger/Android.bp @@ -16,21 +16,25 @@ cc_library_shared { name: "libinputflinger", srcs: [ + "InputClassifier.cpp", "InputDispatcher.cpp", "InputManager.cpp", ], shared_libs: [ + "android.hardware.input.classifier@1.0", "libinputflinger_base", "libinputreporter", "libinputreader", "libbase", "libbinder", "libcutils", + "libhidlbase", "libinput", "liblog", "libutils", "libui", + "server_configurable_flags", ], cflags: [ @@ -38,7 +42,9 @@ cc_library_shared { "-Wextra", "-Werror", "-Wno-unused-parameter", - // TODO: Move inputflinger to its own process and mark it hidden + // TODO(b/123097103): annotate InputDispatcher and uncomment the following line + //"-Wthread-safety", + // TODO(b/23084678): Move inputflinger to its own process and mark it hidden //-fvisibility=hidden ], diff --git a/services/inputflinger/BlockingQueue.h b/services/inputflinger/BlockingQueue.h new file mode 100644 index 0000000000..b892120d41 --- /dev/null +++ b/services/inputflinger/BlockingQueue.h @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2019 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. + */ + +#ifndef _UI_INPUT_BLOCKING_QUEUE_H +#define _UI_INPUT_BLOCKING_QUEUE_H + +#include <condition_variable> +#include <mutex> +#include <vector> + +namespace android { + +/** + * A FIFO queue that stores up to <i>capacity</i> objects. + * Objects can always be added. Objects are added immediately. + * If the queue is full, new objects cannot be added. + * + * The action of retrieving an object will block until an element is available. + */ +template <class T> +class BlockingQueue { +public: + BlockingQueue(size_t capacity) : mCapacity(capacity) { + mQueue.reserve(mCapacity); + }; + + /** + * Retrieve and remove the oldest object. + * Blocks execution while queue is empty. + */ + T pop() { + std::unique_lock<std::mutex> lock(mLock); + mHasElements.wait(lock, [this]{ return !this->mQueue.empty(); }); + T t = std::move(mQueue.front()); + mQueue.erase(mQueue.begin()); + return std::move(t); + }; + + /** + * Add a new object to the queue. + * Does not block. + * Return true if an element was successfully added. + * Return false if the queue is full. + */ + bool push(T&& t) { + std::unique_lock<std::mutex> lock(mLock); + if (mQueue.size() == mCapacity) { + return false; + } + mQueue.push_back(std::move(t)); + mHasElements.notify_one(); + return true; + }; + + void erase(const std::function<bool(const T&)>& lambda) { + std::unique_lock<std::mutex> lock(mLock); + mQueue.erase(std::remove_if(mQueue.begin(), mQueue.end(), + [&lambda](const T& t) { return lambda(t); }), mQueue.end()); + } + + /** + * Remove all elements. + * Does not block. + */ + void clear() { + std::scoped_lock lock(mLock); + mQueue.clear(); + }; + +private: + size_t mCapacity; + /** + * Used to signal that mQueue is non-empty. + */ + std::condition_variable mHasElements; + /** + * Lock for accessing and waiting on elements. + */ + std::mutex mLock; + std::vector<T> mQueue; //GUARDED_BY(mLock) +}; + + +} // namespace android +#endif diff --git a/services/inputflinger/InputClassifier.cpp b/services/inputflinger/InputClassifier.cpp new file mode 100644 index 0000000000..11427c3135 --- /dev/null +++ b/services/inputflinger/InputClassifier.cpp @@ -0,0 +1,692 @@ +/* + * Copyright (C) 2019 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 "InputClassifier" + +#include "InputClassifier.h" + +#include <algorithm> +#include <cmath> +#include <inttypes.h> +#include <log/log.h> +#if defined(__linux__) + #include <pthread.h> +#endif +#include <server_configurable_flags/get_flags.h> + +#include <android/hardware/input/classifier/1.0/IInputClassifier.h> + +using android::hardware::hidl_bitfield; +using android::hardware::hidl_vec; +using namespace android::hardware::input; + +namespace android { + +static constexpr bool DEBUG = false; + +// Category (=namespace) name for the input settings that are applied at boot time +static const char* INPUT_NATIVE_BOOT = "input_native_boot"; +// Feature flag name for the deep press feature +static const char* DEEP_PRESS_ENABLED = "deep_press_enabled"; + +//Max number of elements to store in mEvents. +static constexpr size_t MAX_EVENTS = 5; + +template<class K, class V> +static V getValueForKey(const std::unordered_map<K, V>& map, K key, V defaultValue) { + auto it = map.find(key); + if (it == map.end()) { + return defaultValue; + } + return it->second; +} + +static common::V1_0::Source getSource(uint32_t source) { + static_assert(static_cast<common::V1_0::Source>(AINPUT_SOURCE_UNKNOWN) == + common::V1_0::Source::UNKNOWN, "SOURCE_UNKNOWN mismatch"); + static_assert(static_cast<common::V1_0::Source>(AINPUT_SOURCE_KEYBOARD) == + common::V1_0::Source::KEYBOARD, "SOURCE_KEYBOARD mismatch"); + static_assert(static_cast<common::V1_0::Source>(AINPUT_SOURCE_DPAD) == + common::V1_0::Source::DPAD, "SOURCE_DPAD mismatch"); + static_assert(static_cast<common::V1_0::Source>(AINPUT_SOURCE_GAMEPAD) == + common::V1_0::Source::GAMEPAD, "SOURCE_GAMEPAD mismatch"); + static_assert(static_cast<common::V1_0::Source>(AINPUT_SOURCE_TOUCHSCREEN) == + common::V1_0::Source::TOUCHSCREEN, "SOURCE_TOUCHSCREEN mismatch"); + static_assert(static_cast<common::V1_0::Source>(AINPUT_SOURCE_MOUSE) == + common::V1_0::Source::MOUSE, "SOURCE_MOUSE mismatch"); + static_assert(static_cast<common::V1_0::Source>(AINPUT_SOURCE_STYLUS) == + common::V1_0::Source::STYLUS, "SOURCE_STYLUS mismatch"); + static_assert(static_cast<common::V1_0::Source>(AINPUT_SOURCE_BLUETOOTH_STYLUS) == + common::V1_0::Source::BLUETOOTH_STYLUS, "SOURCE_BLUETOOTH_STYLUS mismatch"); + static_assert(static_cast<common::V1_0::Source>(AINPUT_SOURCE_TRACKBALL) == + common::V1_0::Source::TRACKBALL, "SOURCE_TRACKBALL mismatch"); + static_assert(static_cast<common::V1_0::Source>(AINPUT_SOURCE_MOUSE_RELATIVE) == + common::V1_0::Source::MOUSE_RELATIVE, "SOURCE_MOUSE_RELATIVE mismatch"); + static_assert(static_cast<common::V1_0::Source>(AINPUT_SOURCE_TOUCHPAD) == + common::V1_0::Source::TOUCHPAD, "SOURCE_TOUCHPAD mismatch"); + static_assert(static_cast<common::V1_0::Source>(AINPUT_SOURCE_TOUCH_NAVIGATION) == + common::V1_0::Source::TOUCH_NAVIGATION, "SOURCE_TOUCH_NAVIGATION mismatch"); + static_assert(static_cast<common::V1_0::Source>(AINPUT_SOURCE_JOYSTICK) == + common::V1_0::Source::JOYSTICK, "SOURCE_JOYSTICK mismatch"); + static_assert(static_cast<common::V1_0::Source>(AINPUT_SOURCE_ROTARY_ENCODER) == + common::V1_0::Source::ROTARY_ENCODER, "SOURCE_ROTARY_ENCODER mismatch"); + static_assert(static_cast<common::V1_0::Source>(AINPUT_SOURCE_ANY) == + common::V1_0::Source::ANY, "SOURCE_ANY mismatch"); + return static_cast<common::V1_0::Source>(source); +} + +static common::V1_0::Action getAction(int32_t actionMasked) { + static_assert(static_cast<common::V1_0::Action>(AMOTION_EVENT_ACTION_DOWN) == + common::V1_0::Action::DOWN, "ACTION_DOWN mismatch"); + static_assert(static_cast<common::V1_0::Action>(AMOTION_EVENT_ACTION_UP) == + common::V1_0::Action::UP, "ACTION_UP mismatch"); + static_assert(static_cast<common::V1_0::Action>(AMOTION_EVENT_ACTION_MOVE) == + common::V1_0::Action::MOVE, "ACTION_MOVE mismatch"); + static_assert(static_cast<common::V1_0::Action>(AMOTION_EVENT_ACTION_CANCEL) == + common::V1_0::Action::CANCEL, "ACTION_CANCEL mismatch"); + static_assert(static_cast<common::V1_0::Action>(AMOTION_EVENT_ACTION_OUTSIDE) == + common::V1_0::Action::OUTSIDE, "ACTION_OUTSIDE mismatch"); + static_assert(static_cast<common::V1_0::Action>(AMOTION_EVENT_ACTION_POINTER_DOWN) == + common::V1_0::Action::POINTER_DOWN, "ACTION_POINTER_DOWN mismatch"); + static_assert(static_cast<common::V1_0::Action>(AMOTION_EVENT_ACTION_POINTER_UP) == + common::V1_0::Action::POINTER_UP, "ACTION_POINTER_UP mismatch"); + static_assert(static_cast<common::V1_0::Action>( AMOTION_EVENT_ACTION_HOVER_MOVE) == + common::V1_0::Action::HOVER_MOVE, "ACTION_HOVER_MOVE mismatch"); + static_assert(static_cast<common::V1_0::Action>(AMOTION_EVENT_ACTION_SCROLL) == + common::V1_0::Action::SCROLL, "ACTION_SCROLL mismatch"); + static_assert(static_cast<common::V1_0::Action>(AMOTION_EVENT_ACTION_HOVER_ENTER) == + common::V1_0::Action::HOVER_ENTER, "ACTION_HOVER_ENTER mismatch"); + static_assert(static_cast<common::V1_0::Action>(AMOTION_EVENT_ACTION_HOVER_EXIT) == + common::V1_0::Action::HOVER_EXIT, "ACTION_HOVER_EXIT mismatch"); + static_assert(static_cast<common::V1_0::Action>(AMOTION_EVENT_ACTION_BUTTON_PRESS) == + common::V1_0::Action::BUTTON_PRESS, "ACTION_BUTTON_PRESS mismatch"); + static_assert(static_cast<common::V1_0::Action>(AMOTION_EVENT_ACTION_BUTTON_RELEASE) == + common::V1_0::Action::BUTTON_RELEASE, "ACTION_BUTTON_RELEASE mismatch"); + return static_cast<common::V1_0::Action>(actionMasked); +} + +static common::V1_0::Button getActionButton(int32_t actionButton) { + static_assert(static_cast<common::V1_0::Button>(0) == + common::V1_0::Button::NONE, "BUTTON_NONE mismatch"); + static_assert(static_cast<common::V1_0::Button>(AMOTION_EVENT_BUTTON_PRIMARY) == + common::V1_0::Button::PRIMARY, "BUTTON_PRIMARY mismatch"); + static_assert(static_cast<common::V1_0::Button>(AMOTION_EVENT_BUTTON_SECONDARY) == + common::V1_0::Button::SECONDARY, "BUTTON_SECONDARY mismatch"); + static_assert(static_cast<common::V1_0::Button>(AMOTION_EVENT_BUTTON_TERTIARY) == + common::V1_0::Button::TERTIARY, "BUTTON_TERTIARY mismatch"); + static_assert(static_cast<common::V1_0::Button>(AMOTION_EVENT_BUTTON_BACK) == + common::V1_0::Button::BACK, "BUTTON_BACK mismatch"); + static_assert(static_cast<common::V1_0::Button>(AMOTION_EVENT_BUTTON_FORWARD) == + common::V1_0::Button::FORWARD, "BUTTON_FORWARD mismatch"); + static_assert(static_cast<common::V1_0::Button>(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY) == + common::V1_0::Button::STYLUS_PRIMARY, "BUTTON_STYLUS_PRIMARY mismatch"); + static_assert(static_cast<common::V1_0::Button>(AMOTION_EVENT_BUTTON_STYLUS_SECONDARY) == + common::V1_0::Button::STYLUS_SECONDARY, "BUTTON_STYLUS_SECONDARY mismatch"); + return static_cast<common::V1_0::Button>(actionButton); +} + +static hidl_bitfield<common::V1_0::Flag> getFlags(int32_t flags) { + static_assert(static_cast<common::V1_0::Flag>(AMOTION_EVENT_FLAG_WINDOW_IS_OBSCURED) == + common::V1_0::Flag::WINDOW_IS_OBSCURED); + static_assert(static_cast<common::V1_0::Flag>(AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE) == + common::V1_0::Flag::IS_GENERATED_GESTURE); + static_assert(static_cast<common::V1_0::Flag>(AMOTION_EVENT_FLAG_TAINTED) == + common::V1_0::Flag::TAINTED); + return static_cast<hidl_bitfield<common::V1_0::Flag>>(flags); +} + +static hidl_bitfield<common::V1_0::PolicyFlag> getPolicyFlags(int32_t flags) { + static_assert(static_cast<common::V1_0::PolicyFlag>(POLICY_FLAG_WAKE) == + common::V1_0::PolicyFlag::WAKE); + static_assert(static_cast<common::V1_0::PolicyFlag>(POLICY_FLAG_VIRTUAL) == + common::V1_0::PolicyFlag::VIRTUAL); + static_assert(static_cast<common::V1_0::PolicyFlag>(POLICY_FLAG_FUNCTION) == + common::V1_0::PolicyFlag::FUNCTION); + static_assert(static_cast<common::V1_0::PolicyFlag>(POLICY_FLAG_GESTURE) == + common::V1_0::PolicyFlag::GESTURE); + static_assert(static_cast<common::V1_0::PolicyFlag>(POLICY_FLAG_INJECTED) == + common::V1_0::PolicyFlag::INJECTED); + static_assert(static_cast<common::V1_0::PolicyFlag>(POLICY_FLAG_TRUSTED) == + common::V1_0::PolicyFlag::TRUSTED); + static_assert(static_cast<common::V1_0::PolicyFlag>(POLICY_FLAG_FILTERED) == + common::V1_0::PolicyFlag::FILTERED); + static_assert(static_cast<common::V1_0::PolicyFlag>(POLICY_FLAG_DISABLE_KEY_REPEAT) == + common::V1_0::PolicyFlag::DISABLE_KEY_REPEAT); + static_assert(static_cast<common::V1_0::PolicyFlag>(POLICY_FLAG_INTERACTIVE) == + common::V1_0::PolicyFlag::INTERACTIVE); + static_assert(static_cast<common::V1_0::PolicyFlag>(POLICY_FLAG_PASS_TO_USER) == + common::V1_0::PolicyFlag::PASS_TO_USER); + return static_cast<hidl_bitfield<common::V1_0::PolicyFlag>>(flags); +} + +static hidl_bitfield<common::V1_0::EdgeFlag> getEdgeFlags(int32_t flags) { + static_assert(static_cast<common::V1_0::EdgeFlag>(AMOTION_EVENT_EDGE_FLAG_NONE) == + common::V1_0::EdgeFlag::NONE); + static_assert(static_cast<common::V1_0::EdgeFlag>(AMOTION_EVENT_EDGE_FLAG_TOP) == + common::V1_0::EdgeFlag::TOP); + static_assert(static_cast<common::V1_0::EdgeFlag>(AMOTION_EVENT_EDGE_FLAG_BOTTOM) == + common::V1_0::EdgeFlag::BOTTOM); + static_assert(static_cast<common::V1_0::EdgeFlag>(AMOTION_EVENT_EDGE_FLAG_LEFT) == + common::V1_0::EdgeFlag::LEFT); + static_assert(static_cast<common::V1_0::EdgeFlag>(AMOTION_EVENT_EDGE_FLAG_RIGHT) == + common::V1_0::EdgeFlag::RIGHT); + return static_cast<hidl_bitfield<common::V1_0::EdgeFlag>>(flags); +} + +static hidl_bitfield<common::V1_0::Meta> getMetastate(int32_t state) { + static_assert(static_cast<common::V1_0::Meta>(AMETA_NONE) == + common::V1_0::Meta::NONE); + static_assert(static_cast<common::V1_0::Meta>(AMETA_ALT_ON) == + common::V1_0::Meta::ALT_ON); + static_assert(static_cast<common::V1_0::Meta>(AMETA_ALT_LEFT_ON) == + common::V1_0::Meta::ALT_LEFT_ON); + static_assert(static_cast<common::V1_0::Meta>(AMETA_ALT_RIGHT_ON) == + common::V1_0::Meta::ALT_RIGHT_ON); + static_assert(static_cast<common::V1_0::Meta>(AMETA_SHIFT_ON) == + common::V1_0::Meta::SHIFT_ON); + static_assert(static_cast<common::V1_0::Meta>(AMETA_SHIFT_LEFT_ON) == + common::V1_0::Meta::SHIFT_LEFT_ON); + static_assert(static_cast<common::V1_0::Meta>(AMETA_SHIFT_RIGHT_ON) == + common::V1_0::Meta::SHIFT_RIGHT_ON); + static_assert(static_cast<common::V1_0::Meta>(AMETA_SYM_ON) == + common::V1_0::Meta::SYM_ON); + static_assert(static_cast<common::V1_0::Meta>(AMETA_FUNCTION_ON) == + common::V1_0::Meta::FUNCTION_ON); + static_assert(static_cast<common::V1_0::Meta>(AMETA_CTRL_ON) == + common::V1_0::Meta::CTRL_ON); + static_assert(static_cast<common::V1_0::Meta>(AMETA_CTRL_LEFT_ON) == + common::V1_0::Meta::CTRL_LEFT_ON); + static_assert(static_cast<common::V1_0::Meta>(AMETA_CTRL_RIGHT_ON) == + common::V1_0::Meta::CTRL_RIGHT_ON); + static_assert(static_cast<common::V1_0::Meta>(AMETA_META_ON) == + common::V1_0::Meta::META_ON); + static_assert(static_cast<common::V1_0::Meta>(AMETA_META_LEFT_ON) == + common::V1_0::Meta::META_LEFT_ON); + static_assert(static_cast<common::V1_0::Meta>(AMETA_META_RIGHT_ON) == + common::V1_0::Meta::META_RIGHT_ON); + static_assert(static_cast<common::V1_0::Meta>(AMETA_CAPS_LOCK_ON) == + common::V1_0::Meta::CAPS_LOCK_ON); + static_assert(static_cast<common::V1_0::Meta>(AMETA_NUM_LOCK_ON) == + common::V1_0::Meta::NUM_LOCK_ON); + static_assert(static_cast<common::V1_0::Meta>(AMETA_SCROLL_LOCK_ON) == + common::V1_0::Meta::SCROLL_LOCK_ON); + return static_cast<hidl_bitfield<common::V1_0::Meta>>(state); +} + +static hidl_bitfield<common::V1_0::Button> getButtonState(int32_t buttonState) { + // No need for static_assert here. + // The button values have already been asserted in getActionButton(..) above + return static_cast<hidl_bitfield<common::V1_0::Button>>(buttonState); +} + +static common::V1_0::ToolType getToolType(int32_t toolType) { + static_assert(static_cast<common::V1_0::ToolType>(AMOTION_EVENT_TOOL_TYPE_UNKNOWN) == + common::V1_0::ToolType::UNKNOWN); + static_assert(static_cast<common::V1_0::ToolType>(AMOTION_EVENT_TOOL_TYPE_FINGER) == + common::V1_0::ToolType::FINGER); + static_assert(static_cast<common::V1_0::ToolType>(AMOTION_EVENT_TOOL_TYPE_STYLUS) == + common::V1_0::ToolType::STYLUS); + static_assert(static_cast<common::V1_0::ToolType>(AMOTION_EVENT_TOOL_TYPE_MOUSE) == + common::V1_0::ToolType::MOUSE); + static_assert(static_cast<common::V1_0::ToolType>(AMOTION_EVENT_TOOL_TYPE_ERASER) == + common::V1_0::ToolType::ERASER); + return static_cast<common::V1_0::ToolType>(toolType); +} + +static common::V1_0::Axis getAxis(uint64_t axis) { + static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_X) == + common::V1_0::Axis::X); + static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_Y) == + common::V1_0::Axis::Y); + static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_PRESSURE) == + common::V1_0::Axis::PRESSURE); + static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_SIZE) == + common::V1_0::Axis::SIZE); + static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_TOUCH_MAJOR) == + common::V1_0::Axis::TOUCH_MAJOR); + static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_TOUCH_MINOR) == + common::V1_0::Axis::TOUCH_MINOR); + static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_TOOL_MAJOR) == + common::V1_0::Axis::TOOL_MAJOR); + static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_TOOL_MINOR) == + common::V1_0::Axis::TOOL_MINOR); + static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_ORIENTATION) == + common::V1_0::Axis::ORIENTATION); + static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_VSCROLL) == + common::V1_0::Axis::VSCROLL); + static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_HSCROLL) == + common::V1_0::Axis::HSCROLL); + static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_Z) == + common::V1_0::Axis::Z); + static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_RX) == + common::V1_0::Axis::RX); + static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_RY) == + common::V1_0::Axis::RY); + static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_RZ) == + common::V1_0::Axis::RZ); + static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_HAT_X) == + common::V1_0::Axis::HAT_X); + static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_HAT_Y) == + common::V1_0::Axis::HAT_Y); + static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_LTRIGGER) == + common::V1_0::Axis::LTRIGGER); + static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_RTRIGGER) == + common::V1_0::Axis::RTRIGGER); + static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_THROTTLE) == + common::V1_0::Axis::THROTTLE); + static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_RUDDER) == + common::V1_0::Axis::RUDDER); + static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_WHEEL) == + common::V1_0::Axis::WHEEL); + static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_GAS) == + common::V1_0::Axis::GAS); + static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_BRAKE) == + common::V1_0::Axis::BRAKE); + static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_DISTANCE) == + common::V1_0::Axis::DISTANCE); + static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_TILT) == + common::V1_0::Axis::TILT); + static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_SCROLL) == + common::V1_0::Axis::SCROLL); + static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_RELATIVE_X) == + common::V1_0::Axis::RELATIVE_X); + static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_RELATIVE_Y) == + common::V1_0::Axis::RELATIVE_Y); + static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_GENERIC_1) == + common::V1_0::Axis::GENERIC_1); + static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_GENERIC_2) == + common::V1_0::Axis::GENERIC_2); + static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_GENERIC_3) == + common::V1_0::Axis::GENERIC_3); + static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_GENERIC_4) == + common::V1_0::Axis::GENERIC_4); + static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_GENERIC_5) == + common::V1_0::Axis::GENERIC_5); + static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_GENERIC_6) == + common::V1_0::Axis::GENERIC_6); + static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_GENERIC_7) == + common::V1_0::Axis::GENERIC_7); + static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_GENERIC_8) == + common::V1_0::Axis::GENERIC_8); + static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_GENERIC_9) == + common::V1_0::Axis::GENERIC_9); + static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_GENERIC_10) == + common::V1_0::Axis::GENERIC_10); + static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_GENERIC_11) == + common::V1_0::Axis::GENERIC_11); + static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_GENERIC_12) == + common::V1_0::Axis::GENERIC_12); + static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_GENERIC_13) == + common::V1_0::Axis::GENERIC_13); + static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_GENERIC_14) == + common::V1_0::Axis::GENERIC_14); + static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_GENERIC_15) == + common::V1_0::Axis::GENERIC_15); + static_assert(static_cast<common::V1_0::Axis>(AMOTION_EVENT_AXIS_GENERIC_16) == + common::V1_0::Axis::GENERIC_16); + return static_cast<common::V1_0::Axis>(axis); +} + +static common::V1_0::VideoFrame getHalVideoFrame(const TouchVideoFrame& frame) { + common::V1_0::VideoFrame out; + out.width = frame.getWidth(); + out.height = frame.getHeight(); + out.data = frame.getData(); + struct timeval timestamp = frame.getTimestamp(); + out.timestamp = seconds_to_nanoseconds(timestamp.tv_sec) + + microseconds_to_nanoseconds(timestamp.tv_usec); + return out; +} + +static std::vector<common::V1_0::VideoFrame> convertVideoFrames( + const std::vector<TouchVideoFrame>& frames) { + std::vector<common::V1_0::VideoFrame> out; + for (const TouchVideoFrame& frame : frames) { + out.push_back(getHalVideoFrame(frame)); + } + return out; +} + +static uint8_t getActionIndex(int32_t action) { + return (action & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> + AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; +} + +static void getHidlPropertiesAndCoords(const NotifyMotionArgs& args, + std::vector<common::V1_0::PointerProperties>* outPointerProperties, + std::vector<common::V1_0::PointerCoords>* outPointerCoords) { + outPointerProperties->reserve(args.pointerCount); + outPointerCoords->reserve(args.pointerCount); + for (size_t i = 0; i < args.pointerCount; i++) { + common::V1_0::PointerProperties properties; + properties.id = args.pointerProperties[i].id; + properties.toolType = getToolType(args.pointerProperties[i].toolType); + outPointerProperties->push_back(properties); + + common::V1_0::PointerCoords coords; + BitSet64 bits (args.pointerCoords[i].bits); + std::vector<float> values; + size_t index = 0; + while (!bits.isEmpty()) { + uint32_t axis = bits.clearFirstMarkedBit(); + coords.bits |= 1 << static_cast<uint64_t>(getAxis(axis)); + float value = args.pointerCoords[i].values[index++]; + values.push_back(value); + } + coords.values = values; + outPointerCoords->push_back(coords); + } +} + +static common::V1_0::MotionEvent getMotionEvent(const NotifyMotionArgs& args) { + common::V1_0::MotionEvent event; + event.deviceId = args.deviceId; + event.source = getSource(args.source); + event.displayId = args.displayId; + event.downTime = args.downTime; + event.eventTime = args.eventTime; + event.action = getAction(args.action & AMOTION_EVENT_ACTION_MASK); + event.actionIndex = getActionIndex(args.action); + event.actionButton = getActionButton(args.actionButton); + event.flags = getFlags(args.flags); + event.policyFlags = getPolicyFlags(args.policyFlags); + event.edgeFlags = getEdgeFlags(args.edgeFlags); + event.metaState = getMetastate(args.metaState); + event.buttonState = getButtonState(args.buttonState); + event.xPrecision = args.xPrecision; + event.yPrecision = args.yPrecision; + + std::vector<common::V1_0::PointerProperties> pointerProperties; + std::vector<common::V1_0::PointerCoords> pointerCoords; + getHidlPropertiesAndCoords(args, /*out*/&pointerProperties, /*out*/&pointerCoords); + event.pointerProperties = pointerProperties; + event.pointerCoords = pointerCoords; + + event.deviceTimestamp = args.deviceTimestamp; + event.frames = convertVideoFrames(args.videoFrames); + + return event; +} + +static MotionClassification getMotionClassification(common::V1_0::Classification classification) { + static_assert(MotionClassification::NONE == + static_cast<MotionClassification>(common::V1_0::Classification::NONE)); + static_assert(MotionClassification::AMBIGUOUS_GESTURE == + static_cast<MotionClassification>(common::V1_0::Classification::AMBIGUOUS_GESTURE)); + static_assert(MotionClassification::DEEP_PRESS == + static_cast<MotionClassification>(common::V1_0::Classification::DEEP_PRESS)); + return static_cast<MotionClassification>(classification); +} + +static bool isTouchEvent(const NotifyMotionArgs& args) { + return args.source == AINPUT_SOURCE_TOUCHPAD || args.source == AINPUT_SOURCE_TOUCHSCREEN; +} + +// Check if the "deep touch" feature is on. +static bool deepPressEnabled() { + std::string flag_value = server_configurable_flags::GetServerConfigurableFlag( + INPUT_NATIVE_BOOT, DEEP_PRESS_ENABLED, "true"); + std::transform(flag_value.begin(), flag_value.end(), flag_value.begin(), ::tolower); + if (flag_value == "1" || flag_value == "true") { + ALOGI("Deep press feature enabled."); + return true; + } + ALOGI("Deep press feature is not enabled."); + return false; +} + + +// --- ClassifierEvent --- + +ClassifierEvent::ClassifierEvent(std::unique_ptr<NotifyMotionArgs> args) : + type(ClassifierEventType::MOTION), args(std::move(args)) { }; +ClassifierEvent::ClassifierEvent(std::unique_ptr<NotifyDeviceResetArgs> args) : + type(ClassifierEventType::DEVICE_RESET), args(std::move(args)) { }; +ClassifierEvent::ClassifierEvent(ClassifierEventType type, std::unique_ptr<NotifyArgs> args) : + type(type), args(std::move(args)) { }; + +ClassifierEvent::ClassifierEvent(ClassifierEvent&& other) : + type(other.type), args(std::move(other.args)) { }; + +ClassifierEvent& ClassifierEvent::operator=(ClassifierEvent&& other) { + type = other.type; + args = std::move(other.args); + return *this; +} + +ClassifierEvent ClassifierEvent::createHalResetEvent() { + return ClassifierEvent(ClassifierEventType::HAL_RESET, nullptr); +} + +ClassifierEvent ClassifierEvent::createExitEvent() { + return ClassifierEvent(ClassifierEventType::EXIT, nullptr); +} + +std::optional<int32_t> ClassifierEvent::getDeviceId() const { + switch (type) { + case ClassifierEventType::MOTION: { + NotifyMotionArgs* motionArgs = static_cast<NotifyMotionArgs*>(args.get()); + return motionArgs->deviceId; + } + case ClassifierEventType::DEVICE_RESET: { + NotifyDeviceResetArgs* deviceResetArgs = + static_cast<NotifyDeviceResetArgs*>(args.get()); + return deviceResetArgs->deviceId; + } + case ClassifierEventType::HAL_RESET: { + return std::nullopt; + } + case ClassifierEventType::EXIT: { + return std::nullopt; + } + } +} + +// --- MotionClassifier --- + +MotionClassifier::MotionClassifier( + sp<android::hardware::input::classifier::V1_0::IInputClassifier> service) : + mEvents(MAX_EVENTS), mService(service) { + mHalThread = std::thread(&MotionClassifier::callInputClassifierHal, this); +#if defined(__linux__) + // Set the thread name for debugging + pthread_setname_np(mHalThread.native_handle(), "InputClassifier"); +#endif +} + +MotionClassifier::~MotionClassifier() { + requestExit(); + mHalThread.join(); +} + +void MotionClassifier::ensureHalThread(const char* function) { + if (DEBUG) { + if (std::this_thread::get_id() != mHalThread.get_id()) { + ALOGE("Function %s should only be called from InputClassifier thread", function); + } + } +} + +/** + * Obtain the classification from the HAL for a given MotionEvent. + * Should only be called from the InputClassifier thread (mHalThread). + * Should not be called from the thread that notifyMotion runs on. + * + * There is no way to provide a timeout for a HAL call. So if the HAL takes too long + * to return a classification, this would directly impact the touch latency. + * To remove any possibility of negatively affecting the touch latency, the HAL + * is called from a dedicated thread. + */ +void MotionClassifier::callInputClassifierHal() { + ensureHalThread(__func__); + while (true) { + ClassifierEvent event = mEvents.pop(); + switch (event.type) { + case ClassifierEventType::MOTION: { + NotifyMotionArgs* motionArgs = static_cast<NotifyMotionArgs*>(event.args.get()); + common::V1_0::MotionEvent motionEvent = getMotionEvent(*motionArgs); + common::V1_0::Classification halClassification = mService->classify(motionEvent); + updateClassification(motionArgs->deviceId, motionArgs->eventTime, + getMotionClassification(halClassification)); + break; + } + case ClassifierEventType::DEVICE_RESET: { + const int32_t deviceId = *(event.getDeviceId()); + mService->resetDevice(deviceId); + setClassification(deviceId, MotionClassification::NONE); + break; + } + case ClassifierEventType::HAL_RESET: { + mService->reset(); + clearClassifications(); + break; + } + case ClassifierEventType::EXIT: { + clearClassifications(); + return; + } + } + } +} + +void MotionClassifier::requestExit() { + reset(); + mEvents.push(ClassifierEvent::createExitEvent()); +} + +void MotionClassifier::updateClassification(int32_t deviceId, nsecs_t eventTime, + MotionClassification classification) { + std::scoped_lock lock(mLock); + const nsecs_t lastDownTime = getValueForKey(mLastDownTimes, deviceId, static_cast<nsecs_t>(0)); + if (eventTime < lastDownTime) { + // HAL just finished processing an event that belonged to an earlier gesture, + // but new gesture is already in progress. Drop this classification. + ALOGW("Received late classification. Late by at least %" PRId64 " ms.", + nanoseconds_to_milliseconds(lastDownTime - eventTime)); + return; + } + mClassifications[deviceId] = classification; +} + +void MotionClassifier::setClassification(int32_t deviceId, MotionClassification classification) { + std::scoped_lock lock(mLock); + mClassifications[deviceId] = classification; +} + +void MotionClassifier::clearClassifications() { + std::scoped_lock lock(mLock); + mClassifications.clear(); +} + +MotionClassification MotionClassifier::getClassification(int32_t deviceId) { + std::scoped_lock lock(mLock); + return getValueForKey(mClassifications, deviceId, MotionClassification::NONE); +} + +void MotionClassifier::updateLastDownTime(int32_t deviceId, nsecs_t downTime) { + std::scoped_lock lock(mLock); + mLastDownTimes[deviceId] = downTime; + mClassifications[deviceId] = MotionClassification::NONE; +} + +MotionClassification MotionClassifier::classify(const NotifyMotionArgs& args) { + if (!mService) { + // If HAL is not present, do nothing + return MotionClassification::NONE; + } + if ((args.action & AMOTION_EVENT_ACTION_MASK) == AMOTION_EVENT_ACTION_DOWN) { + updateLastDownTime(args.deviceId, args.downTime); + } + + ClassifierEvent event(std::make_unique<NotifyMotionArgs>(args)); + bool elementAdded = mEvents.push(std::move(event)); + if (!elementAdded) { + // Queue should not ever overfill. Suspect HAL is slow. + ALOGE("Dropped element with eventTime %" PRIu64, args.eventTime); + reset(); + return MotionClassification::NONE; + } + return getClassification(args.deviceId); +} + +void MotionClassifier::reset() { + mEvents.clear(); + mEvents.push(ClassifierEvent::createHalResetEvent()); +} + +/** + * Per-device reset. Clear the outstanding events that are going to be sent to HAL. + * Request InputClassifier thread to call resetDevice for this particular device. + */ +void MotionClassifier::reset(const NotifyDeviceResetArgs& args) { + int32_t deviceId = args.deviceId; + // Clear the pending events right away, to avoid unnecessary work done by the HAL. + mEvents.erase([deviceId](const ClassifierEvent& event) { + std::optional<int32_t> eventDeviceId = event.getDeviceId(); + return eventDeviceId && (*eventDeviceId == deviceId); + }); + mEvents.push(std::make_unique<NotifyDeviceResetArgs>(args)); +} + +// --- InputClassifier --- + +InputClassifier::InputClassifier(const sp<InputListenerInterface>& listener) : + mListener(listener) { + if (deepPressEnabled()) { + sp<android::hardware::input::classifier::V1_0::IInputClassifier> service = + classifier::V1_0::IInputClassifier::getService(); + if (service) { + mMotionClassifier = std::make_unique<MotionClassifier>(service); + } else { + ALOGI("Could not obtain InputClassifier HAL"); + } + } +}; + +void InputClassifier::notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) { + // pass through + mListener->notifyConfigurationChanged(args); +} + +void InputClassifier::notifyKey(const NotifyKeyArgs* args) { + // pass through + mListener->notifyKey(args); +} + +void InputClassifier::notifyMotion(const NotifyMotionArgs* args) { + if (mMotionClassifier && isTouchEvent(*args)) { + // We only cover touch events, for now. + NotifyMotionArgs newArgs = NotifyMotionArgs(*args); + newArgs.classification = mMotionClassifier->classify(newArgs); + args = &newArgs; + } + mListener->notifyMotion(args); +} + +void InputClassifier::notifySwitch(const NotifySwitchArgs* args) { + // pass through + mListener->notifySwitch(args); +} + +void InputClassifier::notifyDeviceReset(const NotifyDeviceResetArgs* args) { + if (mMotionClassifier) { + mMotionClassifier->reset(*args); + } + // continue to next stage + mListener->notifyDeviceReset(args); +} + +} // namespace android
\ No newline at end of file diff --git a/services/inputflinger/InputClassifier.h b/services/inputflinger/InputClassifier.h new file mode 100644 index 0000000000..8133623127 --- /dev/null +++ b/services/inputflinger/InputClassifier.h @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2019 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. + */ + +#ifndef _UI_INPUT_CLASSIFIER_H +#define _UI_INPUT_CLASSIFIER_H + +#include <utils/RefBase.h> +#include <unordered_map> +#include <thread> + +#include "BlockingQueue.h" +#include "InputListener.h" +#include <android/hardware/input/classifier/1.0/IInputClassifier.h> + +namespace android { + +enum class ClassifierEventType : uint8_t { + MOTION = 0, + DEVICE_RESET = 1, + HAL_RESET = 2, + EXIT = 3, +}; + +struct ClassifierEvent { + ClassifierEventType type; + std::unique_ptr<NotifyArgs> args; + + ClassifierEvent(ClassifierEventType type, std::unique_ptr<NotifyArgs> args); + ClassifierEvent(std::unique_ptr<NotifyMotionArgs> args); + ClassifierEvent(std::unique_ptr<NotifyDeviceResetArgs> args); + ClassifierEvent(ClassifierEvent&& other); + ClassifierEvent& operator=(ClassifierEvent&& other); + + // Convenience function to create a HAL_RESET event + static ClassifierEvent createHalResetEvent(); + // Convenience function to create an EXIT event + static ClassifierEvent createExitEvent(); + + std::optional<int32_t> getDeviceId() const; +}; + +// --- Interfaces --- + +/** + * Interface for adding a MotionClassification to NotifyMotionArgs. + * + * To implement, override the classify function. + */ +class MotionClassifierInterface { +public: + MotionClassifierInterface() { } + virtual ~MotionClassifierInterface() { } + /** + * Based on the motion event described by NotifyMotionArgs, + * provide a MotionClassification for the current gesture. + */ + virtual MotionClassification classify(const NotifyMotionArgs& args) = 0; + virtual void reset() = 0; + virtual void reset(const NotifyDeviceResetArgs& args) = 0; +}; + +/** + * Base interface for an InputListener stage. + * Provides classification to events. + */ +class InputClassifierInterface : public virtual RefBase, public InputListenerInterface { +protected: + InputClassifierInterface() { } + virtual ~InputClassifierInterface() { } +}; + +// --- Implementations --- + +/** + * Implementation of MotionClassifierInterface that calls the InputClassifier HAL + * in order to determine the classification for the current gesture. + * + * The InputClassifier HAL may keep track of the entire gesture in order to determine + * the classification, and may be hardware-specific. It may use the data in + * NotifyMotionArgs::videoFrames field to drive the classification decisions. + * The HAL is called from a separate thread. + */ +class MotionClassifier : public MotionClassifierInterface { +public: + MotionClassifier(sp<android::hardware::input::classifier::V1_0::IInputClassifier> service); + ~MotionClassifier(); + /** + * Classifies events asynchronously; that is, it doesn't block events on a classification, + * but instead sends them over to the classifier HAL + * and after a classification is determined, + * it then marks the next event it sees in the stream with it. + * + * Therefore, it is acceptable to have the classifications be delayed by 1-2 events + * in a particular gesture. + */ + virtual MotionClassification classify(const NotifyMotionArgs& args) override; + virtual void reset() override; + virtual void reset(const NotifyDeviceResetArgs& args) override; + +private: + // The events that need to be sent to the HAL. + BlockingQueue<ClassifierEvent> mEvents; + /** + * Thread that will communicate with InputClassifier HAL. + * This should be the only thread that communicates with InputClassifier HAL, + * because this thread is allowed to block on the HAL calls. + */ + std::thread mHalThread; + /** + * Print an error message if the caller is not on the InputClassifier thread. + * Caller must supply the name of the calling function as __function__ + */ + void ensureHalThread(const char* function); + /** + * Call the InputClassifier HAL + */ + void callInputClassifierHal(); + /** + * Access to the InputClassifier HAL + */ + sp<android::hardware::input::classifier::V1_0::IInputClassifier> mService; + std::mutex mLock; + /** + * Per-device input classifications. Should only be accessed using the + * getClassification / setClassification methods. + */ + std::unordered_map<int32_t /*deviceId*/, MotionClassification> + mClassifications; //GUARDED_BY(mLock); + /** + * Set the current classification for a given device. + */ + void setClassification(int32_t deviceId, MotionClassification classification); + /** + * Get the current classification for a given device. + */ + MotionClassification getClassification(int32_t deviceId); + void updateClassification(int32_t deviceId, nsecs_t eventTime, + MotionClassification classification); + /** + * Clear all current classifications + */ + void clearClassifications(); + /** + * Per-device times when the last ACTION_DOWN was received. + * Used to reject late classifications that do not belong to the current gesture. + * + * Accessed indirectly by both InputClassifier thread and the thread that receives notifyMotion. + */ + std::unordered_map<int32_t /*deviceId*/, nsecs_t /*downTime*/> + mLastDownTimes; //GUARDED_BY(mLock); + void updateLastDownTime(int32_t deviceId, nsecs_t downTime); + // Should only be accessed through isResetNeeded() and setResetNeeded() + bool mResetNeeded = false; //GUARDED_BY(mLock); + /** + * Check whether reset should be performed. Reset should be performed + * if the eventTime of the current event is older than mLastDownTime, + * i.e. a new gesture has already begun, but an older gesture is still being processed. + */ + bool isResetNeeded(nsecs_t eventTime); + void setResetNeeded(bool isNeeded); + + /** + * Exit the InputClassifier HAL thread. + * Useful for tests to ensure proper cleanup. + */ + void requestExit(); +}; + +/** + * Implementation of the InputClassifierInterface. + * Represents a separate stage of input processing. All of the input events go through this stage. + * Acts as a passthrough for all input events except for motion events. + * The events of motion type are sent to MotionClassifier. + */ +class InputClassifier : public InputClassifierInterface { +public: + explicit InputClassifier(const sp<InputListenerInterface>& listener); + virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args); + virtual void notifyKey(const NotifyKeyArgs* args); + virtual void notifyMotion(const NotifyMotionArgs* args); + virtual void notifySwitch(const NotifySwitchArgs* args); + virtual void notifyDeviceReset(const NotifyDeviceResetArgs* args); + +private: + std::unique_ptr<MotionClassifierInterface> mMotionClassifier = nullptr; + // The next stage to pass input events to + sp<InputListenerInterface> mListener; +}; + +} // namespace android +#endif
\ No newline at end of file diff --git a/services/inputflinger/InputListener.cpp b/services/inputflinger/InputListener.cpp index b349c73d5a..e537e090a4 100644 --- a/services/inputflinger/InputListener.cpp +++ b/services/inputflinger/InputListener.cpp @@ -36,6 +36,10 @@ NotifyConfigurationChangedArgs::NotifyConfigurationChangedArgs( NotifyArgs(other.sequenceNum), eventTime(other.eventTime) { } +bool NotifyConfigurationChangedArgs::operator==(const NotifyConfigurationChangedArgs& rhs) const { + return sequenceNum == rhs.sequenceNum && eventTime == rhs.eventTime; +} + void NotifyConfigurationChangedArgs::notify(const sp<InputListenerInterface>& listener) const { listener->notifyConfigurationChanged(this); } @@ -61,6 +65,21 @@ NotifyKeyArgs::NotifyKeyArgs(const NotifyKeyArgs& other) : metaState(other.metaState), downTime(other.downTime) { } +bool NotifyKeyArgs::operator==(const NotifyKeyArgs& rhs) const { + return sequenceNum == rhs.sequenceNum + && eventTime == rhs.eventTime + && deviceId == rhs.deviceId + && source == rhs.source + && displayId == rhs.displayId + && policyFlags == rhs.policyFlags + && action == rhs.action + && flags == rhs.flags + && keyCode == rhs.keyCode + && scanCode == rhs.scanCode + && metaState == rhs.metaState + && downTime == rhs.downTime; +} + void NotifyKeyArgs::notify(const sp<InputListenerInterface>& listener) const { listener->notifyKey(this); } @@ -105,6 +124,43 @@ NotifyMotionArgs::NotifyMotionArgs(const NotifyMotionArgs& other) : } } +bool NotifyMotionArgs::operator==(const NotifyMotionArgs& rhs) const { + bool equal = + sequenceNum == rhs.sequenceNum + && eventTime == rhs.eventTime + && deviceId == rhs.deviceId + && source == rhs.source + && displayId == rhs.displayId + && policyFlags == rhs.policyFlags + && action == rhs.action + && actionButton == rhs.actionButton + && flags == rhs.flags + && metaState == rhs.metaState + && buttonState == rhs.buttonState + && classification == rhs.classification + && edgeFlags == rhs.edgeFlags + && deviceTimestamp == rhs.deviceTimestamp + && pointerCount == rhs.pointerCount + // PointerProperties and PointerCoords are compared separately below + && xPrecision == rhs.xPrecision + && yPrecision == rhs.yPrecision + && downTime == rhs.downTime + && videoFrames == rhs.videoFrames; + if (!equal) { + return false; + } + + for (size_t i = 0; i < pointerCount; i++) { + equal = + pointerProperties[i] == rhs.pointerProperties[i] + && pointerCoords[i] == rhs.pointerCoords[i]; + if (!equal) { + return false; + } + } + return true; +} + void NotifyMotionArgs::notify(const sp<InputListenerInterface>& listener) const { listener->notifyMotion(this); } @@ -123,6 +179,14 @@ NotifySwitchArgs::NotifySwitchArgs(const NotifySwitchArgs& other) : switchValues(other.switchValues), switchMask(other.switchMask) { } +bool NotifySwitchArgs::operator==(const NotifySwitchArgs rhs) const { + return sequenceNum == rhs.sequenceNum + && eventTime == rhs.eventTime + && policyFlags == rhs.policyFlags + && switchValues == rhs.switchValues + && switchMask == rhs.switchMask; +} + void NotifySwitchArgs::notify(const sp<InputListenerInterface>& listener) const { listener->notifySwitch(this); } @@ -139,6 +203,12 @@ NotifyDeviceResetArgs::NotifyDeviceResetArgs(const NotifyDeviceResetArgs& other) NotifyArgs(other.sequenceNum), eventTime(other.eventTime), deviceId(other.deviceId) { } +bool NotifyDeviceResetArgs::operator==(const NotifyDeviceResetArgs& rhs) const { + return sequenceNum == rhs.sequenceNum + && eventTime == rhs.eventTime + && deviceId == rhs.deviceId; +} + void NotifyDeviceResetArgs::notify(const sp<InputListenerInterface>& listener) const { listener->notifyDeviceReset(this); } diff --git a/services/inputflinger/InputManager.cpp b/services/inputflinger/InputManager.cpp index 1d7ea00e0c..b3b9e3e101 100644 --- a/services/inputflinger/InputManager.cpp +++ b/services/inputflinger/InputManager.cpp @@ -34,7 +34,8 @@ InputManager::InputManager( const sp<InputReaderPolicyInterface>& readerPolicy, const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) { mDispatcher = new InputDispatcher(dispatcherPolicy); - mReader = createInputReader(readerPolicy, mDispatcher); + mClassifier = new InputClassifier(mDispatcher); + mReader = createInputReader(readerPolicy, mClassifier); initialize(); } diff --git a/services/inputflinger/InputManager.h b/services/inputflinger/InputManager.h index ab309b1888..142ec0c1ea 100644 --- a/services/inputflinger/InputManager.h +++ b/services/inputflinger/InputManager.h @@ -23,7 +23,9 @@ #include "EventHub.h" #include "InputReaderBase.h" +#include "InputClassifier.h" #include "InputDispatcher.h" +#include "InputReader.h" #include <input/Input.h> #include <input/InputTransport.h> @@ -100,6 +102,8 @@ private: sp<InputReaderInterface> mReader; sp<InputReaderThread> mReaderThread; + sp<InputClassifierInterface> mClassifier; + sp<InputDispatcherInterface> mDispatcher; sp<InputDispatcherThread> mDispatcherThread; diff --git a/services/inputflinger/include/InputListener.h b/services/inputflinger/include/InputListener.h index 53247a059b..13ae7dd0c1 100644 --- a/services/inputflinger/include/InputListener.h +++ b/services/inputflinger/include/InputListener.h @@ -49,6 +49,8 @@ struct NotifyConfigurationChangedArgs : public NotifyArgs { inline NotifyConfigurationChangedArgs() { } + bool operator==(const NotifyConfigurationChangedArgs& rhs) const; + NotifyConfigurationChangedArgs(uint32_t sequenceNum, nsecs_t eventTime); NotifyConfigurationChangedArgs(const NotifyConfigurationChangedArgs& other); @@ -79,6 +81,8 @@ struct NotifyKeyArgs : public NotifyArgs { int32_t displayId, uint32_t policyFlags, int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode, int32_t metaState, nsecs_t downTime); + bool operator==(const NotifyKeyArgs& rhs) const; + NotifyKeyArgs(const NotifyKeyArgs& other); virtual ~NotifyKeyArgs() { } @@ -134,6 +138,8 @@ struct NotifyMotionArgs : public NotifyArgs { virtual ~NotifyMotionArgs() { } + bool operator==(const NotifyMotionArgs& rhs) const; + virtual void notify(const sp<InputListenerInterface>& listener) const; }; @@ -152,6 +158,8 @@ struct NotifySwitchArgs : public NotifyArgs { NotifySwitchArgs(const NotifySwitchArgs& other); + bool operator==(const NotifySwitchArgs rhs) const; + virtual ~NotifySwitchArgs() { } virtual void notify(const sp<InputListenerInterface>& listener) const; @@ -170,6 +178,8 @@ struct NotifyDeviceResetArgs : public NotifyArgs { NotifyDeviceResetArgs(const NotifyDeviceResetArgs& other); + bool operator==(const NotifyDeviceResetArgs& rhs) const; + virtual ~NotifyDeviceResetArgs() { } virtual void notify(const sp<InputListenerInterface>& listener) const; diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp index 5b275fb9f6..1835449b5d 100644 --- a/services/inputflinger/tests/Android.bp +++ b/services/inputflinger/tests/Android.bp @@ -3,8 +3,11 @@ cc_test { name: "inputflinger_tests", srcs: [ - "InputReader_test.cpp", + "BlockingQueue_test.cpp", + "TestInputListener.cpp", + "InputClassifier_test.cpp", "InputDispatcher_test.cpp", + "InputReader_test.cpp", ], cflags: [ "-Wall", @@ -13,6 +16,7 @@ cc_test { "-Wno-unused-parameter", ], shared_libs: [ + "android.hardware.input.classifier@1.0", "libbase", "libbinder", "libcutils", diff --git a/services/inputflinger/tests/BlockingQueue_test.cpp b/services/inputflinger/tests/BlockingQueue_test.cpp new file mode 100644 index 0000000000..0dea8d7861 --- /dev/null +++ b/services/inputflinger/tests/BlockingQueue_test.cpp @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2019 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 "../BlockingQueue.h" + + +#include <gtest/gtest.h> +#include <thread> + +namespace android { + + +// --- BlockingQueueTest --- + +/** + * Sanity check of basic pop and push operation. + */ +TEST(BlockingQueueTest, Queue_AddAndRemove) { + constexpr size_t capacity = 10; + BlockingQueue<int> queue(capacity); + + ASSERT_TRUE(queue.push(1)); + ASSERT_EQ(queue.pop(), 1); +} + +/** + * Make sure the queue has strict capacity limits. + */ +TEST(BlockingQueueTest, Queue_ReachesCapacity) { + constexpr size_t capacity = 3; + BlockingQueue<int> queue(capacity); + + // First 3 elements should be added successfully + ASSERT_TRUE(queue.push(1)); + ASSERT_TRUE(queue.push(2)); + ASSERT_TRUE(queue.push(3)); + ASSERT_FALSE(queue.push(4)) << "Queue should reach capacity at size " << capacity; +} + +/** + * Make sure the queue maintains FIFO order. + * Add elements and remove them, and check the order. + */ +TEST(BlockingQueueTest, Queue_isFIFO) { + constexpr size_t capacity = 10; + BlockingQueue<int> queue(capacity); + + for (size_t i = 0; i < capacity; i++) { + ASSERT_TRUE(queue.push(static_cast<int>(i))); + } + for (size_t i = 0; i < capacity; i++) { + ASSERT_EQ(queue.pop(), static_cast<int>(i)); + } +} + +TEST(BlockingQueueTest, Queue_Clears) { + constexpr size_t capacity = 2; + BlockingQueue<int> queue(capacity); + + queue.push(1); + queue.push(2); + queue.clear(); + queue.push(3); + // Should no longer receive elements 1 and 2 + ASSERT_EQ(3, queue.pop()); +} + +TEST(BlockingQueueTest, Queue_Erases) { + constexpr size_t capacity = 4; + BlockingQueue<int> queue(capacity); + + queue.push(1); + queue.push(2); + queue.push(3); + queue.push(4); + // Erase elements 2 and 4 + queue.erase([](int element) { return element == 2 || element == 4; }); + // Should no longer receive elements 2 and 4 + ASSERT_EQ(1, queue.pop()); + ASSERT_EQ(3, queue.pop()); +} + +// --- BlockingQueueTest - Multiple threads --- + +TEST(BlockingQueueTest, Queue_AllowsMultipleThreads) { + constexpr size_t capacity = 100; // large capacity to increase likelihood that threads overlap + BlockingQueue<int> queue(capacity); + + // Fill queue from a different thread + std::thread fillQueue([&queue](){ + for (size_t i = 0; i < capacity; i++) { + ASSERT_TRUE(queue.push(static_cast<int>(i))); + } + }); + + // Make sure all elements are received in correct order + for (size_t i = 0; i < capacity; i++) { + ASSERT_EQ(queue.pop(), static_cast<int>(i)); + } + + fillQueue.join(); +} + +/** + * When the queue has no elements, and pop is called, it should block + * the current thread until an element is added to the queue (from another thread). + * Here we create a separate thread and call pop on an empty queue. Next, + * we check that the thread is blocked. + */ +TEST(BlockingQueueTest, Queue_BlocksWhileWaitingForElements) { + constexpr size_t capacity = 1; + BlockingQueue<int> queue(capacity); + + std::atomic_bool hasReceivedElement = false; + + // fill queue from a different thread + std::thread waitUntilHasElements([&queue, &hasReceivedElement](){ + queue.pop(); // This should block until an element has been added + hasReceivedElement = true; + }); + + ASSERT_FALSE(hasReceivedElement); + queue.push(1); + waitUntilHasElements.join(); + ASSERT_TRUE(hasReceivedElement); +} + + +} // namespace android diff --git a/services/inputflinger/tests/InputClassifier_test.cpp b/services/inputflinger/tests/InputClassifier_test.cpp new file mode 100644 index 0000000000..20699c9288 --- /dev/null +++ b/services/inputflinger/tests/InputClassifier_test.cpp @@ -0,0 +1,233 @@ +/* + * Copyright (C) 2019 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 "../InputClassifier.h" +#include <gtest/gtest.h> + +#include "TestInputListener.h" + +#include <android/hardware/input/classifier/1.0/IInputClassifier.h> + +using namespace android::hardware::input; + +namespace android { + +// --- InputClassifierTest --- + +static NotifyMotionArgs generateBasicMotionArgs() { + // Create a basic motion event for testing + constexpr size_t pointerCount = 1; + PointerProperties properties[pointerCount]; + properties[0].id = 0; + properties[0].toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; + + PointerCoords coords[pointerCount]; + coords[0].setAxisValue(AMOTION_EVENT_AXIS_X, 1); + coords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, 1); + static constexpr nsecs_t downTime = 2; + NotifyMotionArgs motionArgs(1/*sequenceNum*/, downTime/*eventTime*/, 3/*deviceId*/, + AINPUT_SOURCE_ANY, ADISPLAY_ID_DEFAULT, 4/*policyFlags*/, AMOTION_EVENT_ACTION_DOWN, + 0/*actionButton*/, 0/*flags*/, AMETA_NONE, 0/*buttonState*/, MotionClassification::NONE, + AMOTION_EVENT_EDGE_FLAG_NONE, 5/*deviceTimestamp*/, + 0/*pointerCount*/, properties, coords, 0/*xPrecision*/, 0/*yPrecision*/, + downTime, {}/*videoFrames*/); + return motionArgs; +} + +class InputClassifierTest : public testing::Test { +protected: + sp<InputClassifierInterface> mClassifier; + sp<TestInputListener> mTestListener; + + virtual void SetUp() override { + mTestListener = new TestInputListener(); + mClassifier = new InputClassifier(mTestListener); + } + + virtual void TearDown() override { + mClassifier.clear(); + mTestListener.clear(); + } +}; + +/** + * Create a basic configuration change and send it to input classifier. + * Expect that the event is received by the next input stage, unmodified. + */ +TEST_F(InputClassifierTest, SendToNextStage_NotifyConfigurationChangedArgs) { + // Create a basic configuration change and send to classifier + NotifyConfigurationChangedArgs args(1/*sequenceNum*/, 2/*eventTime*/); + + mClassifier->notifyConfigurationChanged(&args); + NotifyConfigurationChangedArgs outArgs; + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled(&outArgs)); + ASSERT_EQ(args, outArgs); +} + +TEST_F(InputClassifierTest, SendToNextStage_NotifyKeyArgs) { + // Create a basic key event and send to classifier + NotifyKeyArgs args(1/*sequenceNum*/, 2/*eventTime*/, 3/*deviceId*/, AINPUT_SOURCE_KEYBOARD, + ADISPLAY_ID_DEFAULT, 0/*policyFlags*/, AKEY_EVENT_ACTION_DOWN, 4/*flags*/, + AKEYCODE_HOME, 5/*scanCode*/, AMETA_NONE, 6/*downTime*/); + + mClassifier->notifyKey(&args); + NotifyKeyArgs outArgs; + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled(&outArgs)); + ASSERT_EQ(args, outArgs); +} + + +/** + * Create a basic motion event and send it to input classifier. + * Expect that the event is received by the next input stage, unmodified. + */ +TEST_F(InputClassifierTest, SendToNextStage_NotifyMotionArgs) { + NotifyMotionArgs motionArgs = generateBasicMotionArgs(); + mClassifier->notifyMotion(&motionArgs); + NotifyMotionArgs args; + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args)); + ASSERT_EQ(motionArgs, args); +} + +/** + * Create a basic switch event and send it to input classifier. + * Expect that the event is received by the next input stage, unmodified. + */ +TEST_F(InputClassifierTest, SendToNextStage_NotifySwitchArgs) { + NotifySwitchArgs args(1/*sequenceNum*/, 2/*eventTime*/, 3/*policyFlags*/, 4/*switchValues*/, + 5/*switchMask*/); + + mClassifier->notifySwitch(&args); + NotifySwitchArgs outArgs; + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifySwitchWasCalled(&outArgs)); + ASSERT_EQ(args, outArgs); +} + +/** + * Create a basic device reset event and send it to input classifier. + * Expect that the event is received by the next input stage, unmodified. + */ +TEST_F(InputClassifierTest, SendToNextStage_NotifyDeviceResetArgs) { + NotifyDeviceResetArgs args(1/*sequenceNum*/, 2/*eventTime*/, 3/*deviceId*/); + + mClassifier->notifyDeviceReset(&args); + NotifyDeviceResetArgs outArgs; + ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyDeviceResetWasCalled(&outArgs)); + ASSERT_EQ(args, outArgs); +} + +// --- MotionClassifierTest --- + +class MotionClassifierTest : public testing::Test { +protected: + std::unique_ptr<MotionClassifierInterface> mMotionClassifier; + + virtual void SetUp() override { + sp<android::hardware::input::classifier::V1_0::IInputClassifier> service = + classifier::V1_0::IInputClassifier::getService(); + if (service) { + mMotionClassifier = std::make_unique<MotionClassifier>(service); + } + } +}; + +/** + * Since MotionClassifier creates a new thread to communicate with HAL, + * it's not really expected to ever exit. However, for testing purposes, + * we need to ensure that it is able to exit cleanly. + * If the thread is not properly cleaned up, it will generate SIGABRT. + * The logic for exiting the thread and cleaning up the resources is inside + * the destructor. Here, we just make sure the destructor does not crash. + */ +TEST_F(MotionClassifierTest, Destructor_DoesNotCrash) { + mMotionClassifier = nullptr; +} + +/** + * Make sure MotionClassifier can handle events that don't have any + * video frames. + */ +TEST_F(MotionClassifierTest, Classify_NoVideoFrames) { + NotifyMotionArgs motionArgs = generateBasicMotionArgs(); + + // We are not checking the return value, because we can't be making assumptions + // about the HAL operation, since it will be highly hardware-dependent + if (mMotionClassifier) { + ASSERT_NO_FATAL_FAILURE(mMotionClassifier->classify(motionArgs)); + } +} + +/** + * Make sure nothing crashes when a videoFrame is sent. + */ +TEST_F(MotionClassifierTest, Classify_OneVideoFrame) { + NotifyMotionArgs motionArgs = generateBasicMotionArgs(); + + std::vector<int16_t> videoData = {1, 2, 3, 4}; + timeval timestamp = { 1, 1}; + TouchVideoFrame frame(2, 2, std::move(videoData), timestamp); + motionArgs.videoFrames = {frame}; + + // We are not checking the return value, because we can't be making assumptions + // about the HAL operation, since it will be highly hardware-dependent + if (mMotionClassifier) { + ASSERT_NO_FATAL_FAILURE(mMotionClassifier->classify(motionArgs)); + } +} + +/** + * Make sure nothing crashes when 2 videoFrames are sent. + */ +TEST_F(MotionClassifierTest, Classify_TwoVideoFrames) { + NotifyMotionArgs motionArgs = generateBasicMotionArgs(); + + std::vector<int16_t> videoData1 = {1, 2, 3, 4}; + timeval timestamp1 = { 1, 1}; + TouchVideoFrame frame1(2, 2, std::move(videoData1), timestamp1); + + std::vector<int16_t> videoData2 = {6, 6, 6, 6}; + timeval timestamp2 = { 1, 2}; + TouchVideoFrame frame2(2, 2, std::move(videoData2), timestamp2); + + motionArgs.videoFrames = {frame1, frame2}; + + // We are not checking the return value, because we can't be making assumptions + // about the HAL operation, since it will be highly hardware-dependent + if (mMotionClassifier) { + ASSERT_NO_FATAL_FAILURE(mMotionClassifier->classify(motionArgs)); + } +} + +/** + * Make sure MotionClassifier does not crash when it is reset. + */ +TEST_F(MotionClassifierTest, Reset_DoesNotCrash) { + if (mMotionClassifier) { + ASSERT_NO_FATAL_FAILURE(mMotionClassifier->reset()); + } +} + +/** + * Make sure MotionClassifier does not crash when a device is reset. + */ +TEST_F(MotionClassifierTest, DeviceReset_DoesNotCrash) { + NotifyDeviceResetArgs args(1/*sequenceNum*/, 2/*eventTime*/, 3/*deviceId*/); + if (mMotionClassifier) { + ASSERT_NO_FATAL_FAILURE(mMotionClassifier->reset(args)); + } +} + +} // namespace android diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp index de87e7f6b9..0b86555fb3 100644 --- a/services/inputflinger/tests/InputReader_test.cpp +++ b/services/inputflinger/tests/InputReader_test.cpp @@ -15,12 +15,13 @@ */ #include "../InputReader.h" +#include "TestInputListener.h" -#include <inttypes.h> -#include <utils/List.h> #include <gtest/gtest.h> +#include <inttypes.h> #include <math.h> + namespace android { // An arbitrary time value. @@ -273,114 +274,6 @@ private: } }; - -// --- FakeInputListener --- - -class FakeInputListener : public InputListenerInterface { -private: - List<NotifyConfigurationChangedArgs> mNotifyConfigurationChangedArgsQueue; - List<NotifyDeviceResetArgs> mNotifyDeviceResetArgsQueue; - List<NotifyKeyArgs> mNotifyKeyArgsQueue; - List<NotifyMotionArgs> mNotifyMotionArgsQueue; - List<NotifySwitchArgs> mNotifySwitchArgsQueue; - -protected: - virtual ~FakeInputListener() { } - -public: - FakeInputListener() { - } - - void assertNotifyConfigurationChangedWasCalled( - NotifyConfigurationChangedArgs* outEventArgs = nullptr) { - ASSERT_FALSE(mNotifyConfigurationChangedArgsQueue.empty()) - << "Expected notifyConfigurationChanged() to have been called."; - if (outEventArgs) { - *outEventArgs = *mNotifyConfigurationChangedArgsQueue.begin(); - } - mNotifyConfigurationChangedArgsQueue.erase(mNotifyConfigurationChangedArgsQueue.begin()); - } - - void assertNotifyConfigurationChangedWasNotCalled() { - ASSERT_TRUE(mNotifyConfigurationChangedArgsQueue.empty()) - << "Expected notifyConfigurationChanged() to not have been called."; - } - - void assertNotifyDeviceResetWasCalled( - NotifyDeviceResetArgs* outEventArgs = nullptr) { - ASSERT_FALSE(mNotifyDeviceResetArgsQueue.empty()) - << "Expected notifyDeviceReset() to have been called."; - if (outEventArgs) { - *outEventArgs = *mNotifyDeviceResetArgsQueue.begin(); - } - mNotifyDeviceResetArgsQueue.erase(mNotifyDeviceResetArgsQueue.begin()); - } - - void assertNotifyDeviceResetWasNotCalled() { - ASSERT_TRUE(mNotifyDeviceResetArgsQueue.empty()) - << "Expected notifyDeviceReset() to not have been called."; - } - - void assertNotifyKeyWasCalled(NotifyKeyArgs* outEventArgs = nullptr) { - ASSERT_FALSE(mNotifyKeyArgsQueue.empty()) - << "Expected notifyKey() to have been called."; - if (outEventArgs) { - *outEventArgs = *mNotifyKeyArgsQueue.begin(); - } - mNotifyKeyArgsQueue.erase(mNotifyKeyArgsQueue.begin()); - } - - void assertNotifyKeyWasNotCalled() { - ASSERT_TRUE(mNotifyKeyArgsQueue.empty()) - << "Expected notifyKey() to not have been called."; - } - - void assertNotifyMotionWasCalled(NotifyMotionArgs* outEventArgs = nullptr) { - ASSERT_FALSE(mNotifyMotionArgsQueue.empty()) - << "Expected notifyMotion() to have been called."; - if (outEventArgs) { - *outEventArgs = *mNotifyMotionArgsQueue.begin(); - } - mNotifyMotionArgsQueue.erase(mNotifyMotionArgsQueue.begin()); - } - - void assertNotifyMotionWasNotCalled() { - ASSERT_TRUE(mNotifyMotionArgsQueue.empty()) - << "Expected notifyMotion() to not have been called."; - } - - void assertNotifySwitchWasCalled(NotifySwitchArgs* outEventArgs = nullptr) { - ASSERT_FALSE(mNotifySwitchArgsQueue.empty()) - << "Expected notifySwitch() to have been called."; - if (outEventArgs) { - *outEventArgs = *mNotifySwitchArgsQueue.begin(); - } - mNotifySwitchArgsQueue.erase(mNotifySwitchArgsQueue.begin()); - } - -private: - virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) { - mNotifyConfigurationChangedArgsQueue.push_back(*args); - } - - virtual void notifyDeviceReset(const NotifyDeviceResetArgs* args) { - mNotifyDeviceResetArgsQueue.push_back(*args); - } - - virtual void notifyKey(const NotifyKeyArgs* args) { - mNotifyKeyArgsQueue.push_back(*args); - } - - virtual void notifyMotion(const NotifyMotionArgs* args) { - mNotifyMotionArgsQueue.push_back(*args); - } - - virtual void notifySwitch(const NotifySwitchArgs* args) { - mNotifySwitchArgsQueue.push_back(*args); - } -}; - - // --- FakeEventHub --- class FakeEventHub : public EventHubInterface { @@ -1303,7 +1196,7 @@ TEST_F(InputReaderPolicyTest, Viewports_GetByPort) { class InputReaderTest : public testing::Test { protected: - sp<FakeInputListener> mFakeListener; + sp<TestInputListener> mFakeListener; sp<FakeInputReaderPolicy> mFakePolicy; sp<FakeEventHub> mFakeEventHub; sp<InstrumentedInputReader> mReader; @@ -1311,7 +1204,7 @@ protected: virtual void SetUp() { mFakeEventHub = new FakeEventHub(); mFakePolicy = new FakeInputReaderPolicy(); - mFakeListener = new FakeInputListener(); + mFakeListener = new TestInputListener(); mReader = new InstrumentedInputReader(mFakeEventHub, mFakePolicy, mFakeListener); } @@ -1620,7 +1513,7 @@ protected: sp<FakeEventHub> mFakeEventHub; sp<FakeInputReaderPolicy> mFakePolicy; - sp<FakeInputListener> mFakeListener; + sp<TestInputListener> mFakeListener; FakeInputReaderContext* mFakeContext; InputDevice* mDevice; @@ -1628,7 +1521,7 @@ protected: virtual void SetUp() { mFakeEventHub = new FakeEventHub(); mFakePolicy = new FakeInputReaderPolicy(); - mFakeListener = new FakeInputListener(); + mFakeListener = new TestInputListener(); mFakeContext = new FakeInputReaderContext(mFakeEventHub, mFakePolicy, mFakeListener); mFakeEventHub->addDevice(DEVICE_ID, DEVICE_NAME, 0); @@ -1815,14 +1708,14 @@ protected: sp<FakeEventHub> mFakeEventHub; sp<FakeInputReaderPolicy> mFakePolicy; - sp<FakeInputListener> mFakeListener; + sp<TestInputListener> mFakeListener; FakeInputReaderContext* mFakeContext; InputDevice* mDevice; virtual void SetUp() { mFakeEventHub = new FakeEventHub(); mFakePolicy = new FakeInputReaderPolicy(); - mFakeListener = new FakeInputListener(); + mFakeListener = new TestInputListener(); mFakeContext = new FakeInputReaderContext(mFakeEventHub, mFakePolicy, mFakeListener); InputDeviceIdentifier identifier; identifier.name = DEVICE_NAME; diff --git a/services/inputflinger/tests/TestInputListener.cpp b/services/inputflinger/tests/TestInputListener.cpp new file mode 100644 index 0000000000..3ee33f1472 --- /dev/null +++ b/services/inputflinger/tests/TestInputListener.cpp @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2019 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 <gtest/gtest.h> + +#include "TestInputListener.h" + +namespace android { + +// --- TestInputListener --- + +TestInputListener::TestInputListener() { } + +TestInputListener::~TestInputListener() { } + +void TestInputListener::assertNotifyConfigurationChangedWasCalled( + NotifyConfigurationChangedArgs* outEventArgs) { + ASSERT_FALSE(mNotifyConfigurationChangedArgsQueue.empty()) + << "Expected notifyConfigurationChanged() to have been called."; + if (outEventArgs) { + *outEventArgs = *mNotifyConfigurationChangedArgsQueue.begin(); + } + mNotifyConfigurationChangedArgsQueue.erase(mNotifyConfigurationChangedArgsQueue.begin()); +} + +void TestInputListener::assertNotifyConfigurationChangedWasNotCalled() { + ASSERT_TRUE(mNotifyConfigurationChangedArgsQueue.empty()) + << "Expected notifyConfigurationChanged() to not have been called."; +} + +void TestInputListener::assertNotifyDeviceResetWasCalled( + NotifyDeviceResetArgs* outEventArgs) { + ASSERT_FALSE(mNotifyDeviceResetArgsQueue.empty()) + << "Expected notifyDeviceReset() to have been called."; + if (outEventArgs) { + *outEventArgs = *mNotifyDeviceResetArgsQueue.begin(); + } + mNotifyDeviceResetArgsQueue.erase(mNotifyDeviceResetArgsQueue.begin()); +} + +void TestInputListener::assertNotifyDeviceResetWasNotCalled() { + ASSERT_TRUE(mNotifyDeviceResetArgsQueue.empty()) + << "Expected notifyDeviceReset() to not have been called."; +} + +void TestInputListener::assertNotifyKeyWasCalled(NotifyKeyArgs* outEventArgs) { + ASSERT_FALSE(mNotifyKeyArgsQueue.empty()) << "Expected notifyKey() to have been called."; + if (outEventArgs) { + *outEventArgs = *mNotifyKeyArgsQueue.begin(); + } + mNotifyKeyArgsQueue.erase(mNotifyKeyArgsQueue.begin()); +} + +void TestInputListener::assertNotifyKeyWasNotCalled() { + ASSERT_TRUE(mNotifyKeyArgsQueue.empty()) << "Expected notifyKey() to not have been called."; +} + +void TestInputListener::assertNotifyMotionWasCalled(NotifyMotionArgs* outEventArgs) { + ASSERT_FALSE(mNotifyMotionArgsQueue.empty()) << "Expected notifyMotion() to have been called."; + if (outEventArgs) { + *outEventArgs = *mNotifyMotionArgsQueue.begin(); + } + mNotifyMotionArgsQueue.erase(mNotifyMotionArgsQueue.begin()); +} + +void TestInputListener::assertNotifyMotionWasNotCalled() { + ASSERT_TRUE(mNotifyMotionArgsQueue.empty()) + << "Expected notifyMotion() to not have been called."; +} + +void TestInputListener::assertNotifySwitchWasCalled(NotifySwitchArgs* outEventArgs) { + ASSERT_FALSE(mNotifySwitchArgsQueue.empty()) + << "Expected notifySwitch() to have been called."; + if (outEventArgs) { + *outEventArgs = *mNotifySwitchArgsQueue.begin(); + } + mNotifySwitchArgsQueue.erase(mNotifySwitchArgsQueue.begin()); +} + +void TestInputListener::notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) { + mNotifyConfigurationChangedArgsQueue.push_back(*args); +} + +void TestInputListener::notifyDeviceReset(const NotifyDeviceResetArgs* args) { + mNotifyDeviceResetArgsQueue.push_back(*args); +} + +void TestInputListener::notifyKey(const NotifyKeyArgs* args) { + mNotifyKeyArgsQueue.push_back(*args); +} + +void TestInputListener::notifyMotion(const NotifyMotionArgs* args) { + mNotifyMotionArgsQueue.push_back(*args); +} + +void TestInputListener::notifySwitch(const NotifySwitchArgs* args) { + mNotifySwitchArgsQueue.push_back(*args); + } + + +} // namespace android diff --git a/services/inputflinger/tests/TestInputListener.h b/services/inputflinger/tests/TestInputListener.h new file mode 100644 index 0000000000..085d3437a0 --- /dev/null +++ b/services/inputflinger/tests/TestInputListener.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2019 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. + */ + +#ifndef _UI_TEST_INPUT_LISTENER_H +#define _UI_TEST_INPUT_LISTENER_H + +#include <gtest/gtest.h> +#include "InputListener.h" + +namespace android { + +// --- TestInputListener --- + +class TestInputListener : public InputListenerInterface { +private: + std::vector<NotifyConfigurationChangedArgs> mNotifyConfigurationChangedArgsQueue; + std::vector<NotifyDeviceResetArgs> mNotifyDeviceResetArgsQueue; + std::vector<NotifyKeyArgs> mNotifyKeyArgsQueue; + std::vector<NotifyMotionArgs> mNotifyMotionArgsQueue; + std::vector<NotifySwitchArgs> mNotifySwitchArgsQueue; + +protected: + virtual ~TestInputListener(); + +public: + TestInputListener(); + + void assertNotifyConfigurationChangedWasCalled( + NotifyConfigurationChangedArgs* outEventArgs = nullptr); + + void assertNotifyConfigurationChangedWasNotCalled(); + + void assertNotifyDeviceResetWasCalled(NotifyDeviceResetArgs* outEventArgs = nullptr); + + void assertNotifyDeviceResetWasNotCalled(); + + void assertNotifyKeyWasCalled(NotifyKeyArgs* outEventArgs = nullptr); + + void assertNotifyKeyWasNotCalled(); + + void assertNotifyMotionWasCalled(NotifyMotionArgs* outEventArgs = nullptr); + + void assertNotifyMotionWasNotCalled(); + + void assertNotifySwitchWasCalled(NotifySwitchArgs* outEventArgs = nullptr); + +private: + virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args); + + virtual void notifyDeviceReset(const NotifyDeviceResetArgs* args); + + virtual void notifyKey(const NotifyKeyArgs* args); + + virtual void notifyMotion(const NotifyMotionArgs* args); + + virtual void notifySwitch(const NotifySwitchArgs* args); +}; + +} // namespace android +#endif |