diff options
Diffstat (limited to 'include/input')
-rw-r--r-- | include/input/CoordinateFilter.h | 54 | ||||
-rw-r--r-- | include/input/DisplayTopologyGraph.h | 57 | ||||
-rw-r--r-- | include/input/Input.h | 11 | ||||
-rw-r--r-- | include/input/InputConsumerNoResampling.h | 42 | ||||
-rw-r--r-- | include/input/InputDevice.h | 24 | ||||
-rw-r--r-- | include/input/InputEventBuilders.h | 105 | ||||
-rw-r--r-- | include/input/KeyCharacterMap.h | 5 | ||||
-rw-r--r-- | include/input/OneEuroFilter.h | 101 | ||||
-rw-r--r-- | include/input/Resampler.h | 205 |
9 files changed, 564 insertions, 40 deletions
diff --git a/include/input/CoordinateFilter.h b/include/input/CoordinateFilter.h new file mode 100644 index 0000000000..8f2e605e85 --- /dev/null +++ b/include/input/CoordinateFilter.h @@ -0,0 +1,54 @@ +/** + * Copyright 2024 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. + */ + +#pragma once + +#include <chrono> + +#include <input/Input.h> +#include <input/OneEuroFilter.h> + +namespace android { + +/** + * Pair of OneEuroFilters that independently filter X and Y coordinates. Both filters share the same + * constructor's parameters. The minimum cutoff frequency is the base cutoff frequency, that is, the + * resulting cutoff frequency in the absence of signal's speed. Likewise, beta is a scaling factor + * of the signal's speed that sets how much the signal's speed contributes to the resulting cutoff + * frequency. The adaptive cutoff frequency criterion is f_c = f_c_min + β|̇x_filtered| + */ +class CoordinateFilter { +public: + explicit CoordinateFilter(float minCutoffFreq, float beta); + + /** + * Filters in place only the AXIS_X and AXIS_Y fields from coords. Each call to filter must + * provide a timestamp strictly greater than the timestamp of the previous call. The first time + * this method is invoked no filtering takes place. Subsequent calls do overwrite `coords` with + * filtered data. + * + * @param timestamp The timestamps at which to filter. It must be greater than the one passed in + * the previous call. + * @param coords Coordinates to be overwritten by the corresponding filtered coordinates. + */ + void filter(std::chrono::nanoseconds timestamp, PointerCoords& coords); + +private: + OneEuroFilter mXFilter; + OneEuroFilter mYFilter; +}; + +} // namespace android
\ No newline at end of file diff --git a/include/input/DisplayTopologyGraph.h b/include/input/DisplayTopologyGraph.h new file mode 100644 index 0000000000..90427bd76d --- /dev/null +++ b/include/input/DisplayTopologyGraph.h @@ -0,0 +1,57 @@ +/* + * Copyright 2024 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. + */ + +#pragma once + +#include <ftl/enum.h> +#include <ui/LogicalDisplayId.h> + +#include <cinttypes> +#include <unordered_map> +#include <vector> + +namespace android { + +/** + * The edge of the current display, where adjacent display is attached to. + */ +enum class DisplayTopologyPosition : int32_t { + LEFT = 0, + TOP = 1, + RIGHT = 2, + BOTTOM = 3, + + ftl_last = BOTTOM +}; + +/** + * Directed edge in the graph of adjacent displays. + */ +struct DisplayTopologyAdjacentDisplay { + ui::LogicalDisplayId displayId = ui::LogicalDisplayId::INVALID; + DisplayTopologyPosition position; + float offsetPx; +}; + +/** + * Directed Graph representation of Display Topology. + */ +struct DisplayTopologyGraph { + ui::LogicalDisplayId primaryDisplayId = ui::LogicalDisplayId::INVALID; + std::unordered_map<ui::LogicalDisplayId, std::vector<DisplayTopologyAdjacentDisplay>> graph; +}; + +} // namespace android diff --git a/include/input/Input.h b/include/input/Input.h index a8684bd19b..2cabd56204 100644 --- a/include/input/Input.h +++ b/include/input/Input.h @@ -294,6 +294,8 @@ enum class KeyboardType { NONE = AINPUT_KEYBOARD_TYPE_NONE, NON_ALPHABETIC = AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC, ALPHABETIC = AINPUT_KEYBOARD_TYPE_ALPHABETIC, + ftl_first = NONE, + ftl_last = ALPHABETIC, }; bool isStylusToolType(ToolType toolType); @@ -994,6 +996,15 @@ protected: std::vector<PointerProperties> mPointerProperties; std::vector<nsecs_t> mSampleEventTimes; std::vector<PointerCoords> mSamplePointerCoords; + +private: + /** + * Create a human-readable string representation of the event's data for debugging purposes. + * + * Unlike operator<<, this method does not assume that the event data is valid or consistent, or + * call any accessor methods that might themselves call safeDump in the case of invalid data. + */ + std::string safeDump() const; }; std::ostream& operator<<(std::ostream& out, const MotionEvent& event); diff --git a/include/input/InputConsumerNoResampling.h b/include/input/InputConsumerNoResampling.h index c98b9cf8c1..70d00d16f4 100644 --- a/include/input/InputConsumerNoResampling.h +++ b/include/input/InputConsumerNoResampling.h @@ -16,6 +16,7 @@ #pragma once +#include <functional> #include <map> #include <memory> #include <optional> @@ -75,12 +76,13 @@ public: * the event is ready to consume. * @param looper needs to be sp and not shared_ptr because it inherits from * RefBase - * @param resampler the resampling strategy to use. If null, no resampling will be - * performed. + * @param resamplerCreator callable that returns the resampling strategy to be used. If null, no + * resampling will be performed. resamplerCreator must never return nullptr. */ - explicit InputConsumerNoResampling(const std::shared_ptr<InputChannel>& channel, - sp<Looper> looper, InputConsumerCallbacks& callbacks, - std::unique_ptr<Resampler> resampler); + explicit InputConsumerNoResampling( + const std::shared_ptr<InputChannel>& channel, sp<Looper> looper, + InputConsumerCallbacks& callbacks, + std::function<std::unique_ptr<Resampler>()> resamplerCreator); ~InputConsumerNoResampling(); @@ -117,7 +119,13 @@ private: std::shared_ptr<InputChannel> mChannel; sp<Looper> mLooper; InputConsumerCallbacks& mCallbacks; - std::unique_ptr<Resampler> mResampler; + const std::function<std::unique_ptr<Resampler>()> mResamplerCreator; + + /** + * A map to manage multidevice resampling. Each contained resampler is never null. This map is + * only modified by handleMessages. + */ + std::map<DeviceId, std::unique_ptr<Resampler>> mResamplers; // Looper-related infrastructure /** @@ -133,7 +141,7 @@ private: } private: - std::function<int(int events)> mCallback; + const std::function<int(int events)> mCallback; }; sp<LooperEventCallback> mCallback; /** @@ -190,7 +198,10 @@ private: /** * Batch messages that can be batched. When an unbatchable message is encountered, send it * to the InputConsumerCallbacks immediately. If there are batches remaining, - * notify InputConsumerCallbacks. + * notify InputConsumerCallbacks. If a resampleable ACTION_DOWN message is received, then a + * resampler is inserted for that deviceId in mResamplers. If a resampleable ACTION_UP or + * ACTION_CANCEL message is received then the resampler associated to that deviceId is erased + * from mResamplers. */ void handleMessages(std::vector<InputMessage>&& messages); /** @@ -200,16 +211,17 @@ private: * `consumeBatchedInputEvents`. */ std::map<DeviceId, std::queue<InputMessage>> mBatches; + /** - * Creates a MotionEvent by consuming samples from the provided queue. If one message has - * eventTime > adjustedFrameTime, all subsequent messages in the queue will be skipped. It is - * assumed that messages are queued in chronological order. In other words, only events that - * occurred prior to the adjustedFrameTime will be consumed. - * @param requestedFrameTime the time up to which to consume events. - * @param messages the queue of messages to consume from + * Creates a MotionEvent by consuming samples from the provided queue. Consumes all messages + * with eventTime <= requestedFrameTime - resampleLatency, where `resampleLatency` is latency + * introduced by the resampler. Assumes that messages are queued in chronological order. + * @param requestedFrameTime The time up to which consume messages, as given by the inequality + * above. If std::nullopt, everything in messages will be consumed. + * @param messages the queue of messages to consume from. */ std::pair<std::unique_ptr<MotionEvent>, std::optional<uint32_t>> createBatchedMotionEvent( - const nsecs_t requestedFrameTime, std::queue<InputMessage>& messages); + const std::optional<nsecs_t> requestedFrameTime, std::queue<InputMessage>& messages); /** * Consumes the batched input events, optionally allowing the caller to specify a device id diff --git a/include/input/InputDevice.h b/include/input/InputDevice.h index 1a482396ee..ea1e4aee01 100644 --- a/include/input/InputDevice.h +++ b/include/input/InputDevice.h @@ -111,12 +111,12 @@ enum class InputDeviceSensorType : int32_t { }; enum class InputDeviceSensorAccuracy : int32_t { - ACCURACY_NONE = 0, - ACCURACY_LOW = 1, - ACCURACY_MEDIUM = 2, - ACCURACY_HIGH = 3, + NONE = 0, + LOW = 1, + MEDIUM = 2, + HIGH = 3, - ftl_last = ACCURACY_HIGH, + ftl_last = HIGH, }; enum class InputDeviceSensorReportingMode : int32_t { @@ -131,8 +131,9 @@ enum class InputDeviceLightType : int32_t { PLAYER_ID = 1, KEYBOARD_BACKLIGHT = 2, KEYBOARD_MIC_MUTE = 3, + KEYBOARD_VOLUME_MUTE = 4, - ftl_last = KEYBOARD_MIC_MUTE + ftl_last = KEYBOARD_VOLUME_MUTE }; enum class InputDeviceLightCapability : uint32_t { @@ -266,6 +267,7 @@ class InputDeviceInfo { public: InputDeviceInfo(); InputDeviceInfo(const InputDeviceInfo& other); + InputDeviceInfo& operator=(const InputDeviceInfo& other); ~InputDeviceInfo(); struct MotionRange { @@ -315,13 +317,11 @@ public: inline const InputDeviceViewBehavior& getViewBehavior() const { return mViewBehavior; } - inline void setKeyCharacterMap(const std::shared_ptr<KeyCharacterMap> value) { - mKeyCharacterMap = value; + inline void setKeyCharacterMap(std::unique_ptr<KeyCharacterMap> value) { + mKeyCharacterMap = std::move(value); } - inline const std::shared_ptr<KeyCharacterMap> getKeyCharacterMap() const { - return mKeyCharacterMap; - } + inline const KeyCharacterMap* getKeyCharacterMap() const { return mKeyCharacterMap.get(); } inline void setVibrator(bool hasVibrator) { mHasVibrator = hasVibrator; } inline bool hasVibrator() const { return mHasVibrator; } @@ -364,7 +364,7 @@ private: std::optional<KeyboardLayoutInfo> mKeyboardLayoutInfo; uint32_t mSources; int32_t mKeyboardType; - std::shared_ptr<KeyCharacterMap> mKeyCharacterMap; + std::unique_ptr<KeyCharacterMap> mKeyCharacterMap; std::optional<InputDeviceUsiVersion> mUsiVersion; ui::LogicalDisplayId mAssociatedDisplayId{ui::LogicalDisplayId::INVALID}; bool mEnabled; diff --git a/include/input/InputEventBuilders.h b/include/input/InputEventBuilders.h index 5bd5070488..1696a62693 100644 --- a/include/input/InputEventBuilders.h +++ b/include/input/InputEventBuilders.h @@ -21,6 +21,7 @@ #include <input/Input.h> #include <input/InputTransport.h> #include <ui/LogicalDisplayId.h> +#include <ui/Transform.h> #include <utils/Timers.h> // for nsecs_t, systemTime #include <vector> @@ -94,16 +95,81 @@ public: return *this; } + InputMessageBuilder& hmac(const std::array<uint8_t, 32>& hmac) { + mHmac = hmac; + return *this; + } + InputMessageBuilder& action(int32_t action) { mAction = action; return *this; } + InputMessageBuilder& actionButton(int32_t actionButton) { + mActionButton = actionButton; + return *this; + } + + InputMessageBuilder& flags(int32_t flags) { + mFlags = flags; + return *this; + } + + InputMessageBuilder& metaState(int32_t metaState) { + mMetaState = metaState; + return *this; + } + + InputMessageBuilder& buttonState(int32_t buttonState) { + mButtonState = buttonState; + return *this; + } + + InputMessageBuilder& classification(MotionClassification classification) { + mClassification = classification; + return *this; + } + + InputMessageBuilder& edgeFlags(int32_t edgeFlags) { + mEdgeFlags = edgeFlags; + return *this; + } + InputMessageBuilder& downTime(nsecs_t downTime) { mDownTime = downTime; return *this; } + InputMessageBuilder& transform(const ui::Transform& transform) { + mTransform = transform; + return *this; + } + + InputMessageBuilder& xPrecision(float xPrecision) { + mXPrecision = xPrecision; + return *this; + } + + InputMessageBuilder& yPrecision(float yPrecision) { + mYPrecision = yPrecision; + return *this; + } + + InputMessageBuilder& xCursorPosition(float xCursorPosition) { + mXCursorPosition = xCursorPosition; + return *this; + } + + InputMessageBuilder& yCursorPosition(float yCursorPosition) { + mYCursorPosition = yCursorPosition; + return *this; + } + + InputMessageBuilder& rawTransform(const ui::Transform& rawTransform) { + mRawTransform = rawTransform; + return *this; + } + InputMessageBuilder& pointer(PointerBuilder pointerBuilder) { mPointers.push_back(pointerBuilder); return *this; @@ -121,8 +187,30 @@ public: message.body.motion.deviceId = mDeviceId; message.body.motion.source = mSource; message.body.motion.displayId = mDisplayId.val(); + message.body.motion.hmac = std::move(mHmac); message.body.motion.action = mAction; + message.body.motion.actionButton = mActionButton; + message.body.motion.flags = mFlags; + message.body.motion.metaState = mMetaState; + message.body.motion.buttonState = mButtonState; + message.body.motion.edgeFlags = mEdgeFlags; message.body.motion.downTime = mDownTime; + message.body.motion.dsdx = mTransform.dsdx(); + message.body.motion.dtdx = mTransform.dtdx(); + message.body.motion.dtdy = mTransform.dtdy(); + message.body.motion.dsdy = mTransform.dsdy(); + message.body.motion.tx = mTransform.ty(); + message.body.motion.ty = mTransform.tx(); + message.body.motion.xPrecision = mXPrecision; + message.body.motion.yPrecision = mYPrecision; + message.body.motion.xCursorPosition = mXCursorPosition; + message.body.motion.yCursorPosition = mYCursorPosition; + message.body.motion.dsdxRaw = mRawTransform.dsdx(); + message.body.motion.dtdxRaw = mRawTransform.dtdx(); + message.body.motion.dtdyRaw = mRawTransform.dtdy(); + message.body.motion.dsdyRaw = mRawTransform.dsdy(); + message.body.motion.txRaw = mRawTransform.ty(); + message.body.motion.tyRaw = mRawTransform.tx(); for (size_t i = 0; i < mPointers.size(); ++i) { message.body.motion.pointers[i].properties = mPointers[i].buildProperties(); @@ -140,9 +228,21 @@ private: DeviceId mDeviceId{DEFAULT_DEVICE_ID}; int32_t mSource{AINPUT_SOURCE_TOUCHSCREEN}; ui::LogicalDisplayId mDisplayId{ui::LogicalDisplayId::DEFAULT}; + std::array<uint8_t, 32> mHmac{INVALID_HMAC}; int32_t mAction{AMOTION_EVENT_ACTION_MOVE}; + int32_t mActionButton{0}; + int32_t mFlags{0}; + int32_t mMetaState{AMETA_NONE}; + int32_t mButtonState{0}; + MotionClassification mClassification{MotionClassification::NONE}; + int32_t mEdgeFlags{0}; nsecs_t mDownTime{mEventTime}; - + ui::Transform mTransform{}; + float mXPrecision{1.0f}; + float mYPrecision{1.0f}; + float mXCursorPosition{AMOTION_EVENT_INVALID_CURSOR_POSITION}; + float mYCursorPosition{AMOTION_EVENT_INVALID_CURSOR_POSITION}; + ui::Transform mRawTransform{}; std::vector<PointerBuilder> mPointers; }; @@ -150,6 +250,9 @@ class MotionEventBuilder { public: MotionEventBuilder(int32_t action, int32_t source) { mAction = action; + if (mAction == AMOTION_EVENT_ACTION_CANCEL) { + mFlags |= AMOTION_EVENT_FLAG_CANCELED; + } mSource = source; mEventTime = systemTime(SYSTEM_TIME_MONOTONIC); mDownTime = mEventTime; diff --git a/include/input/KeyCharacterMap.h b/include/input/KeyCharacterMap.h index 67b37b1213..0a9e74f73b 100644 --- a/include/input/KeyCharacterMap.h +++ b/include/input/KeyCharacterMap.h @@ -72,7 +72,7 @@ public: }; /* Loads a key character map from a file. */ - static base::Result<std::shared_ptr<KeyCharacterMap>> load(const std::string& filename, + static base::Result<std::unique_ptr<KeyCharacterMap>> load(const std::string& filename, Format format); /* Loads a key character map from its string contents. */ @@ -137,6 +137,9 @@ public: /* Returns keycode after applying Android key code remapping defined in mKeyRemapping */ int32_t applyKeyRemapping(int32_t fromKeyCode) const; + /** Returns list of keycodes that remap to provided keycode (@see setKeyRemapping()) */ + std::vector<int32_t> findKeyCodesMappedToKeyCode(int32_t toKeyCode) const; + /* Returns the <keyCode, metaState> pair after applying key behavior defined in the kcm file, * that tries to find a replacement key code based on current meta state */ std::pair<int32_t /*keyCode*/, int32_t /*metaState*/> applyKeyBehavior(int32_t keyCode, diff --git a/include/input/OneEuroFilter.h b/include/input/OneEuroFilter.h new file mode 100644 index 0000000000..bdd82b2ee8 --- /dev/null +++ b/include/input/OneEuroFilter.h @@ -0,0 +1,101 @@ +/** + * Copyright 2024 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. + */ + +#pragma once + +#include <chrono> +#include <optional> + +#include <input/Input.h> + +namespace android { + +/** + * Low pass filter with adaptive low pass frequency based on the signal's speed. The signal's cutoff + * frequency is determined by f_c = f_c_min + β|̇x_filtered|. Refer to + * https://dl.acm.org/doi/10.1145/2207676.2208639 for details on how the filter works and how to + * tune it. + */ +class OneEuroFilter { +public: + /** + * Default cutoff frequency of the filtered signal's speed. 1.0 Hz is the value in the filter's + * paper. + */ + static constexpr float kDefaultSpeedCutoffFreq = 1.0; + + OneEuroFilter() = delete; + + explicit OneEuroFilter(float minCutoffFreq, float beta, + float speedCutoffFreq = kDefaultSpeedCutoffFreq); + + OneEuroFilter(const OneEuroFilter&) = delete; + OneEuroFilter& operator=(const OneEuroFilter&) = delete; + OneEuroFilter(OneEuroFilter&&) = delete; + OneEuroFilter& operator=(OneEuroFilter&&) = delete; + + /** + * Returns the filtered value of rawPosition. Each call to filter must provide a timestamp + * strictly greater than the timestamp of the previous call. The first time the method is + * called, it returns the value of rawPosition. Any subsequent calls provide a filtered value. + * + * @param timestamp The timestamp at which to filter. It must be strictly greater than the one + * provided in the previous call. + * @param rawPosition Position to be filtered. + */ + float filter(std::chrono::nanoseconds timestamp, float rawPosition); + +private: + /** + * Minimum cutoff frequency. This is the constant term in the adaptive cutoff frequency + * criterion. Units are Hertz. + */ + const float mMinCutoffFreq; + + /** + * Slope of the cutoff frequency criterion. This is the term scaling the absolute value of the + * filtered signal's speed. Units are 1 / position. + */ + const float mBeta; + + /** + * Cutoff frequency of the signal's speed. This is the cutoff frequency applied to the filtering + * of the signal's speed. Units are Hertz. + */ + const float mSpeedCutoffFreq; + + /** + * The timestamp from the previous call. + */ + std::optional<std::chrono::nanoseconds> mPrevTimestamp; + + /** + * The raw position from the previous call. + */ + std::optional<float> mPrevRawPosition; + + /** + * The filtered velocity from the previous call. Units are position per nanosecond. + */ + std::optional<float> mPrevFilteredVelocity; + + /** + * The filtered position from the previous call. + */ + std::optional<float> mPrevFilteredPosition; +}; + +} // namespace android diff --git a/include/input/Resampler.h b/include/input/Resampler.h index dcb25b729f..155097732c 100644 --- a/include/input/Resampler.h +++ b/include/input/Resampler.h @@ -16,10 +16,16 @@ #pragma once +#include <array> #include <chrono> +#include <iterator> +#include <map> #include <optional> #include <vector> +#include <android-base/logging.h> +#include <ftl/mixins.h> +#include <input/CoordinateFilter.h> #include <input/Input.h> #include <input/InputTransport.h> #include <input/RingBuffer.h> @@ -65,7 +71,8 @@ public: * extrapolation takes place and `resampleTime` is too far in the future. If `futureSample` is * not null, interpolation will occur. If `futureSample` is null and there is enough historical * data, LegacyResampler will extrapolate. Otherwise, no resampling takes place and - * `motionEvent` is unmodified. + * `motionEvent` is unmodified. Furthermore, motionEvent is not resampled if resampleTime equals + * the last sample eventTime of motionEvent. */ void resampleMotionEvent(std::chrono::nanoseconds frameTime, MotionEvent& motionEvent, const InputMessage* futureSample) override; @@ -78,13 +85,127 @@ private: PointerCoords coords; }; + /** + * Container that stores pointers as an associative array, supporting O(1) lookup by pointer id, + * as well as forward iteration in the order in which the pointer or pointers were inserted in + * the container. PointerMap has a maximum capacity equal to MAX_POINTERS. + */ + class PointerMap { + public: + struct PointerId : ftl::DefaultConstructible<PointerId, int32_t>, + ftl::Equatable<PointerId> { + using DefaultConstructible::DefaultConstructible; + }; + + /** + * Custom iterator to enable use of range-based for loops. + */ + template <typename T> + class iterator { + public: + using iterator_category = std::forward_iterator_tag; + using value_type = T; + using difference_type = std::ptrdiff_t; + using pointer = T*; + using reference = T&; + + explicit iterator(pointer element) : mElement{element} {} + + friend bool operator==(const iterator& lhs, const iterator& rhs) { + return lhs.mElement == rhs.mElement; + } + + friend bool operator!=(const iterator& lhs, const iterator& rhs) { + return !(lhs == rhs); + } + + iterator operator++() { + ++mElement; + return *this; + } + + reference operator*() const { return *mElement; } + + private: + pointer mElement; + }; + + PointerMap() { + idToIndex.fill(std::nullopt); + for (Pointer& pointer : pointers) { + pointer.properties.clear(); + pointer.coords.clear(); + } + } + + /** + * Forward iterators to traverse the pointers in `pointers`. The order of the pointers is + * determined by the order in which they were inserted (not by id). + */ + iterator<Pointer> begin() { return iterator<Pointer>{&pointers[0]}; } + + iterator<const Pointer> begin() const { return iterator<const Pointer>{&pointers[0]}; } + + iterator<Pointer> end() { return iterator<Pointer>{&pointers[nextPointerIndex]}; } + + iterator<const Pointer> end() const { + return iterator<const Pointer>{&pointers[nextPointerIndex]}; + } + + /** + * Inserts the given pointer into the PointerMap. Precondition: The current number of + * contained pointers must be less than MAX_POINTERS when this function is called. It + * fatally logs if the user tries to insert more than MAX_POINTERS, or if pointer id is out + * of bounds. + */ + void insert(const Pointer& pointer) { + LOG_IF(FATAL, nextPointerIndex >= pointers.size()) + << "Cannot insert more than " << MAX_POINTERS << " in PointerMap."; + LOG_IF(FATAL, (pointer.properties.id < 0) || (pointer.properties.id > MAX_POINTER_ID)) + << "Invalid pointer id."; + idToIndex[pointer.properties.id] = std::optional<size_t>{nextPointerIndex}; + pointers[nextPointerIndex] = pointer; + ++nextPointerIndex; + } + + /** + * Returns the pointer associated with the provided id if it exists. + * Otherwise, std::nullopt is returned. + */ + std::optional<Pointer> find(PointerId id) const { + const int32_t idValue = ftl::to_underlying(id); + LOG_IF(FATAL, (idValue < 0) || (idValue > MAX_POINTER_ID)) << "Invalid pointer id."; + const std::optional<size_t> index = idToIndex[idValue]; + return index.has_value() ? std::optional{pointers[*index]} : std::nullopt; + } + + private: + /** + * The index at which a pointer is inserted in `pointers`. Likewise, it represents the + * number of pointers in PointerMap. + */ + size_t nextPointerIndex{0}; + + /** + * Sequentially stores pointers. Each pointer's position is determined by the value of + * nextPointerIndex at insertion time. + */ + std::array<Pointer, MAX_POINTERS + 1> pointers; + + /** + * Maps each pointer id to its associated index in pointers. If no pointer with the id + * exists in pointers, the mapped value is std::nullopt. + */ + std::array<std::optional<size_t>, MAX_POINTER_ID + 1> idToIndex; + }; + struct Sample { std::chrono::nanoseconds eventTime; - std::vector<Pointer> pointers; + PointerMap pointerMap; std::vector<PointerCoords> asPointerCoords() const { std::vector<PointerCoords> pointersCoords; - for (const Pointer& pointer : pointers) { + for (const Pointer& pointer : pointerMap) { pointersCoords.push_back(pointer.coords); } return pointersCoords; @@ -92,12 +213,6 @@ private: }; /** - * Keeps track of the previous MotionEvent deviceId to enable comparison between the previous - * and the current deviceId. - */ - std::optional<DeviceId> mPreviousDeviceId; - - /** * Up to two latest samples from MotionEvent. Updated every time resampleMotionEvent is called. * Note: We store up to two samples in order to simplify the implementation. Although, * calculations are possible with only one previous sample. @@ -105,6 +220,16 @@ private: RingBuffer<Sample> mLatestSamples{/*capacity=*/2}; /** + * Latest sample in mLatestSamples after resampling motion event. + */ + std::optional<Sample> mLastRealSample; + + /** + * Latest prediction. That is, the latest extrapolated sample. + */ + std::optional<Sample> mPreviousPrediction; + + /** * Adds up to mLatestSamples.capacity() of motionEvent's latest samples to mLatestSamples. If * motionEvent has fewer samples than mLatestSamples.capacity(), then the available samples are * added to mLatestSamples. @@ -128,12 +253,12 @@ private: bool canInterpolate(const InputMessage& futureSample) const; /** - * Returns a sample interpolated between the latest sample of mLatestSamples and futureSample, + * Returns a sample interpolated between the latest sample of mLatestSamples and futureMessage, * if the conditions from canInterpolate are satisfied. Otherwise, returns nullopt. * mLatestSamples must have at least one sample when attemptInterpolation is called. */ std::optional<Sample> attemptInterpolation(std::chrono::nanoseconds resampleTime, - const InputMessage& futureSample) const; + const InputMessage& futureMessage) const; /** * Checks if there are necessary conditions to extrapolate. That is, there are at least two @@ -149,6 +274,64 @@ private: */ std::optional<Sample> attemptExtrapolation(std::chrono::nanoseconds resampleTime) const; + /** + * Iterates through motion event samples, and replaces real coordinates with resampled + * coordinates to avoid jerkiness in certain conditions. + */ + void overwriteMotionEventSamples(MotionEvent& motionEvent) const; + + /** + * Overwrites with resampled data the pointer coordinates that did not move between motion event + * samples, that is, both x and y values are identical to mLastRealSample. + */ + void overwriteStillPointers(MotionEvent& motionEvent, size_t sampleIndex) const; + + /** + * Overwrites the pointer coordinates of a sample with event time older than + * that of mPreviousPrediction. + */ + void overwriteOldPointers(MotionEvent& motionEvent, size_t sampleIndex) const; + inline static void addSampleToMotionEvent(const Sample& sample, MotionEvent& motionEvent); }; + +/** + * Resampler that first applies the LegacyResampler resampling algorithm, then independently filters + * the X and Y coordinates with a pair of One Euro filters. + */ +class FilteredLegacyResampler final : public Resampler { +public: + /** + * Creates a resampler, using the given minCutoffFreq and beta to instantiate its One Euro + * filters. + */ + explicit FilteredLegacyResampler(float minCutoffFreq, float beta); + + void resampleMotionEvent(std::chrono::nanoseconds requestedFrameTime, MotionEvent& motionEvent, + const InputMessage* futureMessage) override; + + std::chrono::nanoseconds getResampleLatency() const override; + +private: + LegacyResampler mResampler; + + /** + * Minimum cutoff frequency of the value's low pass filter. Refer to OneEuroFilter class for a + * more detailed explanation. + */ + const float mMinCutoffFreq; + + /** + * Scaling factor of the adaptive cutoff frequency criterion. Refer to OneEuroFilter class for a + * more detailed explanation. + */ + const float mBeta; + + /* + * Note: an associative array with constant insertion and lookup times would be more efficient. + * When this was implemented, there was no container with these properties. + */ + std::map<int32_t /*pointerId*/, CoordinateFilter> mFilteredPointers; +}; + } // namespace android |