Add a new InputClassifier stage
The new InputClassifier stage could be used for additional processing
of input events prior to sending them to InputDispatcher. The new flow
of events will be InputReader -> InputClassifier -> InputDispatcher.
Here, we are calling the InputClassifier HAL and setting the MotionEvent
classification appropriately.
Bug: 62940136
Test: override notifyMotion to add extra flags to NotifyMotionArgs for
certain types of input events.
Test: atest inputflinger_tests
Change-Id: I2f390dc69f587ea25a3be8e4b8d5a207a5d529bf
diff --git a/include/input/TouchVideoFrame.h b/include/input/TouchVideoFrame.h
index d68f274..640bf1f 100644
--- a/include/input/TouchVideoFrame.h
+++ b/include/input/TouchVideoFrame.h
@@ -35,6 +35,14 @@
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 e3a237e..f73d498 100644
--- a/services/inputflinger/Android.bp
+++ b/services/inputflinger/Android.bp
@@ -16,21 +16,25 @@
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 @@
"-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 0000000..b892120
--- /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 0000000..11427c3
--- /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 0000000..8133623
--- /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 b349c73..e537e09 100644
--- a/services/inputflinger/InputListener.cpp
+++ b/services/inputflinger/InputListener.cpp
@@ -36,6 +36,10 @@
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 @@
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 @@
}
}
+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 @@
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 @@
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 1d7ea00..b3b9e3e 100644
--- a/services/inputflinger/InputManager.cpp
+++ b/services/inputflinger/InputManager.cpp
@@ -34,7 +34,8 @@
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 ab309b1..142ec0c 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 @@
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 53247a0..13ae7dd 100644
--- a/services/inputflinger/include/InputListener.h
+++ b/services/inputflinger/include/InputListener.h
@@ -49,6 +49,8 @@
inline NotifyConfigurationChangedArgs() { }
+ bool operator==(const NotifyConfigurationChangedArgs& rhs) const;
+
NotifyConfigurationChangedArgs(uint32_t sequenceNum, nsecs_t eventTime);
NotifyConfigurationChangedArgs(const NotifyConfigurationChangedArgs& other);
@@ -79,6 +81,8 @@
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 @@
virtual ~NotifyMotionArgs() { }
+ bool operator==(const NotifyMotionArgs& rhs) const;
+
virtual void notify(const sp<InputListenerInterface>& listener) const;
};
@@ -152,6 +158,8 @@
NotifySwitchArgs(const NotifySwitchArgs& other);
+ bool operator==(const NotifySwitchArgs rhs) const;
+
virtual ~NotifySwitchArgs() { }
virtual void notify(const sp<InputListenerInterface>& listener) const;
@@ -170,6 +178,8 @@
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 5b275fb..1835449 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 @@
"-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 0000000..0dea8d7
--- /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 0000000..20699c9
--- /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 de87e7f..0b86555 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 @@
}
};
-
-// --- 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 @@
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 @@
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 @@
sp<FakeEventHub> mFakeEventHub;
sp<FakeInputReaderPolicy> mFakePolicy;
- sp<FakeInputListener> mFakeListener;
+ sp<TestInputListener> mFakeListener;
FakeInputReaderContext* mFakeContext;
InputDevice* mDevice;
@@ -1628,7 +1521,7 @@
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 @@
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 0000000..3ee33f1
--- /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 0000000..085d343
--- /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