summaryrefslogtreecommitdiff
path: root/include/input
diff options
context:
space:
mode:
Diffstat (limited to 'include/input')
-rw-r--r--include/input/CoordinateFilter.h54
-rw-r--r--include/input/DisplayTopologyGraph.h57
-rw-r--r--include/input/Input.h11
-rw-r--r--include/input/InputConsumerNoResampling.h42
-rw-r--r--include/input/InputDevice.h24
-rw-r--r--include/input/InputEventBuilders.h105
-rw-r--r--include/input/KeyCharacterMap.h5
-rw-r--r--include/input/OneEuroFilter.h101
-rw-r--r--include/input/Resampler.h205
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