TouchpadInputMapper: support touchpad capture
This allows an app to capture the touchpad and receive "raw" touch
information from it. Since some specifics (such as how size is
calculated) aren't well-defined in documentation or tests currently,
when in doubt I tried to match the behaviour of MultiTouchInputMapper in
captured mode.
Bug: b/259547750
Test: atest inputflinger_tests
Test: use a test app to dump captured events, manually verify and
compare with old behaviour
Change-Id: I482cce6d16ed6f947ce86e8453ddb2c9789d4d00
diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp
index 132c3a1..b0edb57 100644
--- a/services/inputflinger/reader/Android.bp
+++ b/services/inputflinger/reader/Android.bp
@@ -42,6 +42,7 @@
"Macros.cpp",
"TouchVideoDevice.cpp",
"controller/PeripheralController.cpp",
+ "mapper/CapturedTouchpadEventConverter.cpp",
"mapper/CursorInputMapper.cpp",
"mapper/ExternalStylusInputMapper.cpp",
"mapper/InputMapper.cpp",
diff --git a/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp
new file mode 100644
index 0000000..023372a
--- /dev/null
+++ b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.cpp
@@ -0,0 +1,302 @@
+/*
+ * Copyright 2023 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 "CapturedTouchpadEventConverter.h"
+
+#include <sstream>
+
+#include <android-base/stringprintf.h>
+#include <gui/constants.h>
+#include <input/PrintTools.h>
+#include <linux/input-event-codes.h>
+#include <log/log_main.h>
+
+namespace android {
+
+namespace {
+
+int32_t actionWithIndex(int32_t action, int32_t index) {
+ return action | (index << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
+}
+
+template <typename T>
+size_t firstUnmarkedBit(T set) {
+ // TODO: replace with std::countr_one from <bit> when that's available
+ LOG_ALWAYS_FATAL_IF(set.all());
+ size_t i = 0;
+ while (set.test(i)) {
+ i++;
+ }
+ return i;
+}
+
+} // namespace
+
+CapturedTouchpadEventConverter::CapturedTouchpadEventConverter(
+ InputReaderContext& readerContext, const InputDeviceContext& deviceContext,
+ MultiTouchMotionAccumulator& motionAccumulator, int32_t deviceId)
+ : mDeviceId(deviceId),
+ mReaderContext(readerContext),
+ mDeviceContext(deviceContext),
+ mMotionAccumulator(motionAccumulator),
+ mHasTouchMinor(deviceContext.hasAbsoluteAxis(ABS_MT_TOUCH_MINOR)),
+ mHasToolMinor(deviceContext.hasAbsoluteAxis(ABS_MT_WIDTH_MINOR)) {
+ RawAbsoluteAxisInfo orientationInfo;
+ deviceContext.getAbsoluteAxisInfo(ABS_MT_ORIENTATION, &orientationInfo);
+ if (orientationInfo.valid) {
+ if (orientationInfo.maxValue > 0) {
+ mOrientationScale = M_PI_2 / orientationInfo.maxValue;
+ } else if (orientationInfo.minValue < 0) {
+ mOrientationScale = -M_PI_2 / orientationInfo.minValue;
+ }
+ }
+
+ // TODO(b/275369880): support touch.pressure.calibration and .scale properties when captured.
+ RawAbsoluteAxisInfo pressureInfo;
+ deviceContext.getAbsoluteAxisInfo(ABS_MT_PRESSURE, &pressureInfo);
+ if (pressureInfo.valid && pressureInfo.maxValue > 0) {
+ mPressureScale = 1.0 / pressureInfo.maxValue;
+ }
+
+ RawAbsoluteAxisInfo touchMajorInfo, toolMajorInfo;
+ deviceContext.getAbsoluteAxisInfo(ABS_MT_TOUCH_MAJOR, &touchMajorInfo);
+ deviceContext.getAbsoluteAxisInfo(ABS_MT_WIDTH_MAJOR, &toolMajorInfo);
+ mHasTouchMajor = touchMajorInfo.valid;
+ mHasToolMajor = toolMajorInfo.valid;
+ if (mHasTouchMajor && touchMajorInfo.maxValue != 0) {
+ mSizeScale = 1.0f / touchMajorInfo.maxValue;
+ } else if (mHasToolMajor && toolMajorInfo.maxValue != 0) {
+ mSizeScale = 1.0f / toolMajorInfo.maxValue;
+ }
+}
+
+std::string CapturedTouchpadEventConverter::dump() const {
+ std::stringstream out;
+ out << "Orientation scale: " << mOrientationScale << "\n";
+ out << "Pressure scale: " << mPressureScale << "\n";
+ out << "Size scale: " << mSizeScale << "\n";
+
+ out << "Dimension axes:";
+ if (mHasTouchMajor) out << " touch major";
+ if (mHasTouchMinor) out << ", touch minor";
+ if (mHasToolMajor) out << ", tool major";
+ if (mHasToolMinor) out << ", tool minor";
+ out << "\n";
+
+ out << "Down time: " << mDownTime << "\n";
+ out << StringPrintf("Button state: 0x%08x\n", mButtonState);
+
+ out << StringPrintf("Pointer IDs in use: %s\n", mPointerIdsInUse.to_string().c_str());
+
+ out << "Pointer IDs for slot numbers:\n";
+ out << addLinePrefix(dumpMap(mPointerIdForSlotNumber), " ") << "\n";
+ return out.str();
+}
+
+void CapturedTouchpadEventConverter::populateMotionRanges(InputDeviceInfo& info) const {
+ tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_X, ABS_MT_POSITION_X);
+ tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_Y, ABS_MT_POSITION_Y);
+ tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_TOUCH_MAJOR, ABS_MT_TOUCH_MAJOR);
+ tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_TOUCH_MINOR, ABS_MT_TOUCH_MINOR);
+ tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_TOOL_MAJOR, ABS_MT_WIDTH_MAJOR);
+ tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_TOOL_MINOR, ABS_MT_WIDTH_MINOR);
+
+ RawAbsoluteAxisInfo pressureInfo;
+ mDeviceContext.getAbsoluteAxisInfo(ABS_MT_PRESSURE, &pressureInfo);
+ if (pressureInfo.valid) {
+ info.addMotionRange(AMOTION_EVENT_AXIS_PRESSURE, SOURCE, 0, 1, 0, 0, 0);
+ }
+
+ RawAbsoluteAxisInfo orientationInfo;
+ mDeviceContext.getAbsoluteAxisInfo(ABS_MT_ORIENTATION, &orientationInfo);
+ if (orientationInfo.valid && (orientationInfo.maxValue > 0 || orientationInfo.minValue < 0)) {
+ info.addMotionRange(AMOTION_EVENT_AXIS_ORIENTATION, SOURCE, -M_PI_2, M_PI_2, 0, 0, 0);
+ }
+
+ if (mHasTouchMajor || mHasToolMajor) {
+ info.addMotionRange(AMOTION_EVENT_AXIS_SIZE, SOURCE, 0, 1, 0, 0, 0);
+ }
+}
+
+void CapturedTouchpadEventConverter::tryAddRawMotionRange(InputDeviceInfo& deviceInfo,
+ int32_t androidAxis,
+ int32_t evdevAxis) const {
+ RawAbsoluteAxisInfo info;
+ mDeviceContext.getAbsoluteAxisInfo(evdevAxis, &info);
+ if (info.valid) {
+ deviceInfo.addMotionRange(androidAxis, SOURCE, info.minValue, info.maxValue, info.flat,
+ info.fuzz, info.resolution);
+ }
+}
+
+void CapturedTouchpadEventConverter::reset() {
+ mCursorButtonAccumulator.reset(mDeviceContext);
+ mDownTime = 0;
+ mPointerIdsInUse.reset();
+ mPointerIdForSlotNumber.clear();
+}
+
+std::list<NotifyArgs> CapturedTouchpadEventConverter::process(const RawEvent& rawEvent) {
+ std::list<NotifyArgs> out;
+ if (rawEvent.type == EV_SYN && rawEvent.code == SYN_REPORT) {
+ out = sync(rawEvent.when, rawEvent.readTime);
+ mMotionAccumulator.finishSync();
+ }
+
+ mCursorButtonAccumulator.process(&rawEvent);
+ mMotionAccumulator.process(&rawEvent);
+ return out;
+}
+
+std::list<NotifyArgs> CapturedTouchpadEventConverter::sync(nsecs_t when, nsecs_t readTime) {
+ // TODO(b/259547750): filter out touches marked as palms (using MT_TOOL_PALM).
+ std::list<NotifyArgs> out;
+ std::vector<PointerCoords> coords;
+ std::vector<PointerProperties> properties;
+ std::map<size_t, size_t> coordsIndexForSlotNumber;
+
+ // For all the touches that were already down, send a MOVE event with their updated coordinates.
+ // A convention of the MotionEvent API is that pointer coordinates in UP events match the
+ // pointer's coordinates from the previous MOVE, so we still include touches here even if
+ // they've been lifted in this evdev frame.
+ if (!mPointerIdForSlotNumber.empty()) {
+ for (const auto [slotNumber, pointerId] : mPointerIdForSlotNumber) {
+ // Note that we don't check whether the touch has actually moved — it's rare for a touch
+ // to stay perfectly still between frames, and if it does the worst that can happen is
+ // an extra MOVE event, so it's not worth the overhead of checking for changes.
+ coordsIndexForSlotNumber[slotNumber] = coords.size();
+ coords.push_back(makePointerCoordsForSlot(mMotionAccumulator.getSlot(slotNumber)));
+ properties.push_back({.id = pointerId, .toolType = ToolType::FINGER});
+ }
+ out.push_back(
+ makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, coords, properties));
+ }
+
+ std::vector<size_t> upSlots, downSlots;
+ for (size_t i = 0; i < mMotionAccumulator.getSlotCount(); i++) {
+ const bool isInUse = mMotionAccumulator.getSlot(i).isInUse();
+ const bool wasInUse = mPointerIdForSlotNumber.find(i) != mPointerIdForSlotNumber.end();
+ if (isInUse && !wasInUse) {
+ downSlots.push_back(i);
+ } else if (!isInUse && wasInUse) {
+ upSlots.push_back(i);
+ }
+ }
+
+ // For any touches that were lifted, send UP or POINTER_UP events.
+ for (size_t slotNumber : upSlots) {
+ const size_t indexToRemove = coordsIndexForSlotNumber.at(slotNumber);
+ const int32_t action = coords.size() == 1
+ ? AMOTION_EVENT_ACTION_UP
+ : actionWithIndex(AMOTION_EVENT_ACTION_POINTER_UP, indexToRemove);
+ out.push_back(makeMotionArgs(when, readTime, action, coords, properties));
+
+ freePointerIdForSlot(slotNumber);
+ coords.erase(coords.begin() + indexToRemove);
+ properties.erase(properties.begin() + indexToRemove);
+ // Now that we've removed some coords and properties, we might have to update the slot
+ // number to coords index mapping.
+ coordsIndexForSlotNumber.erase(slotNumber);
+ for (auto& [_, index] : coordsIndexForSlotNumber) {
+ if (index > indexToRemove) {
+ index--;
+ }
+ }
+ }
+
+ // For new touches, send DOWN or POINTER_DOWN events.
+ for (size_t slotNumber : downSlots) {
+ const size_t coordsIndex = coords.size();
+ const int32_t action = coords.empty()
+ ? AMOTION_EVENT_ACTION_DOWN
+ : actionWithIndex(AMOTION_EVENT_ACTION_POINTER_DOWN, coordsIndex);
+
+ coordsIndexForSlotNumber[slotNumber] = coordsIndex;
+ coords.push_back(makePointerCoordsForSlot(mMotionAccumulator.getSlot(slotNumber)));
+ properties.push_back(
+ {.id = allocatePointerIdToSlot(slotNumber), .toolType = ToolType::FINGER});
+
+ out.push_back(makeMotionArgs(when, readTime, action, coords, properties));
+ }
+
+ const uint32_t newButtonState = mCursorButtonAccumulator.getButtonState();
+ for (uint32_t button = 1; button <= AMOTION_EVENT_BUTTON_FORWARD; button <<= 1) {
+ if (newButtonState & button && !(mButtonState & button)) {
+ mButtonState |= button;
+ out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_PRESS, coords,
+ properties, /*actionButton=*/button));
+ } else if (!(newButtonState & button) && mButtonState & button) {
+ mButtonState &= ~button;
+ out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_RELEASE,
+ coords, properties, /*actionButton=*/button));
+ }
+ }
+ return out;
+}
+
+NotifyMotionArgs CapturedTouchpadEventConverter::makeMotionArgs(
+ nsecs_t when, nsecs_t readTime, int32_t action, const std::vector<PointerCoords>& coords,
+ const std::vector<PointerProperties>& properties, int32_t actionButton) {
+ LOG_ALWAYS_FATAL_IF(coords.size() != properties.size(),
+ "Mismatched coords and properties arrays.");
+ return NotifyMotionArgs(mReaderContext.getNextId(), when, readTime, mDeviceId, SOURCE,
+ ADISPLAY_ID_NONE, /*policyFlags=*/POLICY_FLAG_WAKE, action,
+ /*actionButton=*/actionButton, /*flags=*/0,
+ mReaderContext.getGlobalMetaState(), mButtonState,
+ MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, coords.size(),
+ properties.data(), coords.data(), /*xPrecision=*/1.0f,
+ /*yPrecision=*/1.0f, AMOTION_EVENT_INVALID_CURSOR_POSITION,
+ AMOTION_EVENT_INVALID_CURSOR_POSITION, mDownTime, /*videoFrames=*/{});
+}
+
+PointerCoords CapturedTouchpadEventConverter::makePointerCoordsForSlot(
+ const MultiTouchMotionAccumulator::Slot& slot) const {
+ PointerCoords coords;
+ coords.clear();
+ coords.setAxisValue(AMOTION_EVENT_AXIS_X, slot.getX());
+ coords.setAxisValue(AMOTION_EVENT_AXIS_Y, slot.getY());
+ coords.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, slot.getTouchMajor());
+ coords.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, slot.getTouchMinor());
+ coords.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, slot.getToolMajor());
+ coords.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, slot.getToolMinor());
+ coords.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, slot.getOrientation() * mOrientationScale);
+ coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, slot.getPressure() * mPressureScale);
+ float size = 0;
+ // TODO(b/275369880): support touch.size.calibration and .isSummed properties when captured.
+ if (mHasTouchMajor) {
+ size = mHasTouchMinor ? (slot.getTouchMajor() + slot.getTouchMinor()) / 2
+ : slot.getTouchMajor();
+ } else if (mHasToolMajor) {
+ size = mHasToolMinor ? (slot.getToolMajor() + slot.getToolMinor()) / 2
+ : slot.getToolMajor();
+ }
+ coords.setAxisValue(AMOTION_EVENT_AXIS_SIZE, size * mSizeScale);
+ return coords;
+}
+
+int32_t CapturedTouchpadEventConverter::allocatePointerIdToSlot(size_t slotNumber) {
+ const int32_t pointerId = firstUnmarkedBit(mPointerIdsInUse);
+ mPointerIdsInUse.set(pointerId);
+ mPointerIdForSlotNumber[slotNumber] = pointerId;
+ return pointerId;
+}
+
+void CapturedTouchpadEventConverter::freePointerIdForSlot(size_t slotNumber) {
+ mPointerIdsInUse.reset(mPointerIdForSlotNumber.at(slotNumber));
+ mPointerIdForSlotNumber.erase(slotNumber);
+}
+
+} // namespace android
diff --git a/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.h b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.h
new file mode 100644
index 0000000..d81692d
--- /dev/null
+++ b/services/inputflinger/reader/mapper/CapturedTouchpadEventConverter.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2023 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 <bitset>
+#include <list>
+#include <map>
+#include <set>
+#include <string>
+#include <vector>
+
+#include <android/input.h>
+#include <input/Input.h>
+#include <utils/Timers.h>
+
+#include "EventHub.h"
+#include "InputDevice.h"
+#include "accumulator/CursorButtonAccumulator.h"
+#include "accumulator/MultiTouchMotionAccumulator.h"
+#include "accumulator/TouchButtonAccumulator.h"
+
+namespace android {
+
+class CapturedTouchpadEventConverter {
+public:
+ explicit CapturedTouchpadEventConverter(InputReaderContext& readerContext,
+ const InputDeviceContext& deviceContext,
+ MultiTouchMotionAccumulator& motionAccumulator,
+ int32_t deviceId);
+ std::string dump() const;
+ void populateMotionRanges(InputDeviceInfo& info) const;
+ void reset();
+ [[nodiscard]] std::list<NotifyArgs> process(const RawEvent& rawEvent);
+
+private:
+ void tryAddRawMotionRange(InputDeviceInfo& deviceInfo, int32_t androidAxis,
+ int32_t evdevAxis) const;
+ [[nodiscard]] std::list<NotifyArgs> sync(nsecs_t when, nsecs_t readTime);
+ [[nodiscard]] NotifyMotionArgs makeMotionArgs(nsecs_t when, nsecs_t readTime, int32_t action,
+ const std::vector<PointerCoords>& coords,
+ const std::vector<PointerProperties>& properties,
+ int32_t actionButton = 0);
+ PointerCoords makePointerCoordsForSlot(const MultiTouchMotionAccumulator::Slot& slot) const;
+ int32_t allocatePointerIdToSlot(size_t slotNumber);
+ void freePointerIdForSlot(size_t slotNumber);
+
+ const int32_t mDeviceId;
+ InputReaderContext& mReaderContext;
+ const InputDeviceContext& mDeviceContext;
+ CursorButtonAccumulator mCursorButtonAccumulator;
+ MultiTouchMotionAccumulator& mMotionAccumulator;
+
+ float mOrientationScale = 0;
+ float mPressureScale = 1;
+ float mSizeScale = 0;
+ bool mHasTouchMajor;
+ const bool mHasTouchMinor;
+ bool mHasToolMajor;
+ const bool mHasToolMinor;
+ nsecs_t mDownTime = 0;
+ uint32_t mButtonState = 0;
+
+ std::bitset<MAX_POINTER_ID + 1> mPointerIdsInUse;
+ std::map<size_t, int32_t> mPointerIdForSlotNumber;
+
+ static constexpr uint32_t SOURCE = AINPUT_SOURCE_TOUCHPAD;
+};
+
+} // namespace android
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
index 8753b48..a5da3cd 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.cpp
@@ -16,9 +16,11 @@
#include "../Macros.h"
+#include <chrono>
#include <limits>
#include <optional>
+#include <android-base/stringprintf.h>
#include <android/input.h>
#include <ftl/enum.h>
#include <input/PrintTools.h>
@@ -174,8 +176,18 @@
: InputMapper(deviceContext, readerConfig),
mGestureInterpreter(NewGestureInterpreter(), DeleteGestureInterpreter),
mPointerController(getContext()->getPointerController(getDeviceId())),
- mStateConverter(deviceContext),
- mGestureConverter(*getContext(), deviceContext, getDeviceId()) {
+ mStateConverter(deviceContext, mMotionAccumulator),
+ mGestureConverter(*getContext(), deviceContext, getDeviceId()),
+ mCapturedEventConverter(*getContext(), deviceContext, mMotionAccumulator, getDeviceId()) {
+ RawAbsoluteAxisInfo slotAxisInfo;
+ deviceContext.getAbsoluteAxisInfo(ABS_MT_SLOT, &slotAxisInfo);
+ if (!slotAxisInfo.valid || slotAxisInfo.maxValue <= 0) {
+ ALOGW("Touchpad \"%s\" doesn't have a valid ABS_MT_SLOT axis, and probably won't work "
+ "properly.",
+ deviceContext.getName().c_str());
+ }
+ mMotionAccumulator.configure(deviceContext, slotAxisInfo.maxValue + 1, true);
+
mGestureInterpreter->Initialize(GESTURES_DEVCLASS_TOUCHPAD);
mGestureInterpreter->SetHardwareProperties(createHardwareProperties(deviceContext));
// Even though we don't explicitly delete copy/move semantics, it's safe to
@@ -209,15 +221,28 @@
void TouchpadInputMapper::populateDeviceInfo(InputDeviceInfo& info) {
InputMapper::populateDeviceInfo(info);
- mGestureConverter.populateMotionRanges(info);
+ if (mPointerCaptured) {
+ mCapturedEventConverter.populateMotionRanges(info);
+ } else {
+ mGestureConverter.populateMotionRanges(info);
+ }
}
void TouchpadInputMapper::dump(std::string& dump) {
dump += INDENT2 "Touchpad Input Mapper:\n";
+ if (mProcessing) {
+ dump += INDENT3 "Currently processing a hardware state\n";
+ }
+ if (mResettingInterpreter) {
+ dump += INDENT3 "Currently resetting gesture interpreter\n";
+ }
+ dump += StringPrintf(INDENT3 "Pointer captured: %s\n", toString(mPointerCaptured));
dump += INDENT3 "Gesture converter:\n";
dump += addLinePrefix(mGestureConverter.dump(), INDENT4);
dump += INDENT3 "Gesture properties:\n";
dump += addLinePrefix(mPropertyProvider.dump(), INDENT4);
+ dump += INDENT3 "Captured event converter:\n";
+ dump += addLinePrefix(mCapturedEventConverter.dump(), INDENT4);
}
std::list<NotifyArgs> TouchpadInputMapper::reconfigure(nsecs_t when,
@@ -252,17 +277,50 @@
mPropertyProvider.getProperty("Button Right Click Zone Enable")
.setBoolValues({config.touchpadRightClickZoneEnabled});
}
- return {};
+ std::list<NotifyArgs> out;
+ if ((!changes.any() && config.pointerCaptureRequest.enable) ||
+ changes.test(InputReaderConfiguration::Change::POINTER_CAPTURE)) {
+ mPointerCaptured = config.pointerCaptureRequest.enable;
+ // The motion ranges are going to change, so bump the generation to clear the cached ones.
+ bumpGeneration();
+ if (mPointerCaptured) {
+ // The touchpad is being captured, so we need to tidy up any fake fingers etc. that are
+ // still being reported for a gesture in progress.
+ out += reset(when);
+ mPointerController->fade(PointerControllerInterface::Transition::IMMEDIATE);
+ } else {
+ // We're transitioning from captured to uncaptured.
+ mCapturedEventConverter.reset();
+ }
+ if (changes.any()) {
+ out.push_back(NotifyDeviceResetArgs(getContext()->getNextId(), when, getDeviceId()));
+ }
+ }
+ return out;
}
std::list<NotifyArgs> TouchpadInputMapper::reset(nsecs_t when) {
mStateConverter.reset();
+ resetGestureInterpreter(when);
std::list<NotifyArgs> out = mGestureConverter.reset(when);
out += InputMapper::reset(when);
return out;
}
+void TouchpadInputMapper::resetGestureInterpreter(nsecs_t when) {
+ // The GestureInterpreter has no official reset method, but sending a HardwareState with no
+ // fingers down or buttons pressed should get it into a clean state.
+ HardwareState state;
+ state.timestamp = std::chrono::duration<stime_t>(std::chrono::nanoseconds(when)).count();
+ mResettingInterpreter = true;
+ mGestureInterpreter->PushHardwareState(&state);
+ mResettingInterpreter = false;
+}
+
std::list<NotifyArgs> TouchpadInputMapper::process(const RawEvent* rawEvent) {
+ if (mPointerCaptured) {
+ return mCapturedEventConverter.process(*rawEvent);
+ }
std::optional<SelfContainedHardwareState> state = mStateConverter.processRawEvent(rawEvent);
if (state) {
return sendHardwareState(rawEvent->when, rawEvent->readTime, *state);
@@ -283,6 +341,11 @@
void TouchpadInputMapper::consumeGesture(const Gesture* gesture) {
ALOGD_IF(DEBUG_TOUCHPAD_GESTURES, "Gesture ready: %s", gesture->String().c_str());
+ if (mResettingInterpreter) {
+ // We already handle tidying up fake fingers etc. in GestureConverter::reset, so we should
+ // ignore any gestures produced from the interpreter while we're resetting it.
+ return;
+ }
if (!mProcessing) {
ALOGE("Received gesture outside of the normal processing flow; ignoring it.");
return;
diff --git a/services/inputflinger/reader/mapper/TouchpadInputMapper.h b/services/inputflinger/reader/mapper/TouchpadInputMapper.h
index 268b275..3128d18 100644
--- a/services/inputflinger/reader/mapper/TouchpadInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchpadInputMapper.h
@@ -21,12 +21,15 @@
#include <vector>
#include <PointerControllerInterface.h>
+#include <utils/Timers.h>
+#include "CapturedTouchpadEventConverter.h"
#include "EventHub.h"
#include "InputDevice.h"
#include "InputMapper.h"
#include "InputReaderBase.h"
#include "NotifyArgs.h"
+#include "accumulator/MultiTouchMotionAccumulator.h"
#include "gestures/GestureConverter.h"
#include "gestures/HardwareStateConverter.h"
#include "gestures/PropertyProvider.h"
@@ -54,6 +57,7 @@
void consumeGesture(const Gesture* gesture);
private:
+ void resetGestureInterpreter(nsecs_t when);
[[nodiscard]] std::list<NotifyArgs> sendHardwareState(nsecs_t when, nsecs_t readTime,
SelfContainedHardwareState schs);
[[nodiscard]] std::list<NotifyArgs> processGestures(nsecs_t when, nsecs_t readTime);
@@ -64,10 +68,19 @@
PropertyProvider mPropertyProvider;
+ // The MultiTouchMotionAccumulator is shared between the HardwareStateConverter and
+ // CapturedTouchpadEventConverter, so that if the touchpad is captured or released while touches
+ // are down, the relevant converter can still benefit from the current axis values stored in the
+ // accumulator.
+ MultiTouchMotionAccumulator mMotionAccumulator;
+
HardwareStateConverter mStateConverter;
GestureConverter mGestureConverter;
+ CapturedTouchpadEventConverter mCapturedEventConverter;
+ bool mPointerCaptured = false;
bool mProcessing = false;
+ bool mResettingInterpreter = false;
std::vector<Gesture> mGesturesToProcess;
};
diff --git a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp
index 8841b6e..6780dce 100644
--- a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp
+++ b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.cpp
@@ -26,16 +26,11 @@
namespace android {
-HardwareStateConverter::HardwareStateConverter(const InputDeviceContext& deviceContext)
- : mDeviceContext(deviceContext), mTouchButtonAccumulator(deviceContext) {
- RawAbsoluteAxisInfo slotAxisInfo;
- deviceContext.getAbsoluteAxisInfo(ABS_MT_SLOT, &slotAxisInfo);
- if (!slotAxisInfo.valid || slotAxisInfo.maxValue <= 0) {
- ALOGW("Touchpad \"%s\" doesn't have a valid ABS_MT_SLOT axis, and probably won't work "
- "properly.",
- deviceContext.getName().c_str());
- }
- mMotionAccumulator.configure(deviceContext, slotAxisInfo.maxValue + 1, true);
+HardwareStateConverter::HardwareStateConverter(const InputDeviceContext& deviceContext,
+ MultiTouchMotionAccumulator& motionAccumulator)
+ : mDeviceContext(deviceContext),
+ mMotionAccumulator(motionAccumulator),
+ mTouchButtonAccumulator(deviceContext) {
mTouchButtonAccumulator.configure();
}
diff --git a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h
index c314b0d..633448e 100644
--- a/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h
+++ b/services/inputflinger/reader/mapper/gestures/HardwareStateConverter.h
@@ -41,7 +41,8 @@
// Converts RawEvents into the HardwareState structs used by the gestures library.
class HardwareStateConverter {
public:
- HardwareStateConverter(const InputDeviceContext& deviceContext);
+ HardwareStateConverter(const InputDeviceContext& deviceContext,
+ MultiTouchMotionAccumulator& motionAccumulator);
std::optional<SelfContainedHardwareState> processRawEvent(const RawEvent* event);
void reset();
@@ -51,7 +52,7 @@
const InputDeviceContext& mDeviceContext;
CursorButtonAccumulator mCursorButtonAccumulator;
- MultiTouchMotionAccumulator mMotionAccumulator;
+ MultiTouchMotionAccumulator& mMotionAccumulator;
TouchButtonAccumulator mTouchButtonAccumulator;
int32_t mMscTimestamp = 0;
};
diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp
index 97138c7..52277ff 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -39,6 +39,7 @@
srcs: [
"AnrTracker_test.cpp",
"BlockingQueue_test.cpp",
+ "CapturedTouchpadEventConverter_test.cpp",
"EventHub_test.cpp",
"FakeEventHub.cpp",
"FakeInputReaderPolicy.cpp",
diff --git a/services/inputflinger/tests/CapturedTouchpadEventConverter_test.cpp b/services/inputflinger/tests/CapturedTouchpadEventConverter_test.cpp
new file mode 100644
index 0000000..694641c
--- /dev/null
+++ b/services/inputflinger/tests/CapturedTouchpadEventConverter_test.cpp
@@ -0,0 +1,556 @@
+/*
+ * Copyright 2023 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 <CapturedTouchpadEventConverter.h>
+
+#include <list>
+#include <memory>
+
+#include <EventHub.h>
+#include <gtest/gtest.h>
+#include <linux/input-event-codes.h>
+#include <utils/StrongPointer.h>
+
+#include "FakeEventHub.h"
+#include "FakeInputReaderPolicy.h"
+#include "InstrumentedInputReader.h"
+#include "TestConstants.h"
+#include "TestInputListener.h"
+#include "TestInputListenerMatchers.h"
+
+namespace android {
+
+using testing::AllOf;
+
+class CapturedTouchpadEventConverterTest : public testing::Test {
+public:
+ CapturedTouchpadEventConverterTest()
+ : mFakeEventHub(std::make_unique<FakeEventHub>()),
+ mFakePolicy(sp<FakeInputReaderPolicy>::make()),
+ mReader(mFakeEventHub, mFakePolicy, mFakeListener),
+ mDevice(newDevice()),
+ mDeviceContext(*mDevice, EVENTHUB_ID) {
+ const size_t slotCount = 8;
+ mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_SLOT, 0, slotCount - 1, 0, 0, 0);
+ mAccumulator.configure(mDeviceContext, slotCount, /*usingSlotsProtocol=*/true);
+ }
+
+protected:
+ static constexpr int32_t DEVICE_ID = END_RESERVED_ID + 1000;
+ static constexpr int32_t EVENTHUB_ID = 1;
+
+ std::shared_ptr<InputDevice> newDevice() {
+ InputDeviceIdentifier identifier;
+ identifier.name = "device";
+ identifier.location = "USB1";
+ identifier.bus = 0;
+ std::shared_ptr<InputDevice> device =
+ std::make_shared<InputDevice>(mReader.getContext(), DEVICE_ID, /*generation=*/2,
+ identifier);
+ mReader.pushNextDevice(device);
+ mFakeEventHub->addDevice(EVENTHUB_ID, identifier.name, InputDeviceClass::TOUCHPAD,
+ identifier.bus);
+ mReader.loopOnce();
+ return device;
+ }
+
+ void addBasicAxesToEventHub() {
+ mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, 0, 4000, 0, 0, 45);
+ mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, 0, 2500, 0, 0, 40);
+ mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_PRESSURE, 0, 256, 0, 0, 0);
+ mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOUCH_MAJOR, 0, 1000, 0, 0, 0);
+ mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOUCH_MINOR, 0, 1000, 0, 0, 0);
+ }
+
+ CapturedTouchpadEventConverter createConverter() {
+ addBasicAxesToEventHub();
+ return CapturedTouchpadEventConverter(*mReader.getContext(), mDeviceContext, mAccumulator,
+ DEVICE_ID);
+ }
+
+ void processAxis(CapturedTouchpadEventConverter& conv, int32_t type, int32_t code,
+ int32_t value) {
+ RawEvent event;
+ event.when = ARBITRARY_TIME;
+ event.readTime = READ_TIME;
+ event.deviceId = EVENTHUB_ID;
+ event.type = type;
+ event.code = code;
+ event.value = value;
+ std::list<NotifyArgs> out = conv.process(event);
+ EXPECT_TRUE(out.empty());
+ }
+
+ std::list<NotifyArgs> processSync(CapturedTouchpadEventConverter& conv) {
+ RawEvent event;
+ event.when = ARBITRARY_TIME;
+ event.readTime = READ_TIME;
+ event.deviceId = EVENTHUB_ID;
+ event.type = EV_SYN;
+ event.code = SYN_REPORT;
+ event.value = 0;
+ return conv.process(event);
+ }
+
+ NotifyMotionArgs processSyncAndExpectSingleMotionArg(CapturedTouchpadEventConverter& conv) {
+ std::list<NotifyArgs> args = processSync(conv);
+ EXPECT_EQ(1u, args.size());
+ return std::get<NotifyMotionArgs>(args.front());
+ }
+
+ std::shared_ptr<FakeEventHub> mFakeEventHub;
+ sp<FakeInputReaderPolicy> mFakePolicy;
+ TestInputListener mFakeListener;
+ InstrumentedInputReader mReader;
+ std::shared_ptr<InputDevice> mDevice;
+ InputDeviceContext mDeviceContext;
+ MultiTouchMotionAccumulator mAccumulator;
+};
+
+TEST_F(CapturedTouchpadEventConverterTest, MotionRanges_allAxesPresent_populatedCorrectly) {
+ mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, 0, 4000, 0, 0, 45);
+ mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, 0, 2500, 0, 0, 40);
+ mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOUCH_MAJOR, 0, 1100, 0, 0, 35);
+ mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOUCH_MINOR, 0, 1000, 0, 0, 30);
+ mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MAJOR, 0, 900, 0, 0, 25);
+ mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MINOR, 0, 800, 0, 0, 20);
+ mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_ORIENTATION, -3, 4, 0, 0, 0);
+ mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_PRESSURE, 0, 256, 0, 0, 0);
+ CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator,
+ DEVICE_ID);
+
+ InputDeviceInfo info;
+ conv.populateMotionRanges(info);
+
+ // Most axes should have min, max, and resolution matching the evdev axes.
+ const InputDeviceInfo::MotionRange* posX =
+ info.getMotionRange(AMOTION_EVENT_AXIS_X, AINPUT_SOURCE_TOUCHPAD);
+ ASSERT_NE(nullptr, posX);
+ EXPECT_NEAR(0, posX->min, EPSILON);
+ EXPECT_NEAR(4000, posX->max, EPSILON);
+ EXPECT_NEAR(45, posX->resolution, EPSILON);
+
+ const InputDeviceInfo::MotionRange* posY =
+ info.getMotionRange(AMOTION_EVENT_AXIS_Y, AINPUT_SOURCE_TOUCHPAD);
+ ASSERT_NE(nullptr, posY);
+ EXPECT_NEAR(0, posY->min, EPSILON);
+ EXPECT_NEAR(2500, posY->max, EPSILON);
+ EXPECT_NEAR(40, posY->resolution, EPSILON);
+
+ const InputDeviceInfo::MotionRange* touchMajor =
+ info.getMotionRange(AMOTION_EVENT_AXIS_TOUCH_MAJOR, AINPUT_SOURCE_TOUCHPAD);
+ ASSERT_NE(nullptr, touchMajor);
+ EXPECT_NEAR(0, touchMajor->min, EPSILON);
+ EXPECT_NEAR(1100, touchMajor->max, EPSILON);
+ EXPECT_NEAR(35, touchMajor->resolution, EPSILON);
+
+ const InputDeviceInfo::MotionRange* touchMinor =
+ info.getMotionRange(AMOTION_EVENT_AXIS_TOUCH_MINOR, AINPUT_SOURCE_TOUCHPAD);
+ ASSERT_NE(nullptr, touchMinor);
+ EXPECT_NEAR(0, touchMinor->min, EPSILON);
+ EXPECT_NEAR(1000, touchMinor->max, EPSILON);
+ EXPECT_NEAR(30, touchMinor->resolution, EPSILON);
+
+ const InputDeviceInfo::MotionRange* toolMajor =
+ info.getMotionRange(AMOTION_EVENT_AXIS_TOOL_MAJOR, AINPUT_SOURCE_TOUCHPAD);
+ ASSERT_NE(nullptr, toolMajor);
+ EXPECT_NEAR(0, toolMajor->min, EPSILON);
+ EXPECT_NEAR(900, toolMajor->max, EPSILON);
+ EXPECT_NEAR(25, toolMajor->resolution, EPSILON);
+
+ const InputDeviceInfo::MotionRange* toolMinor =
+ info.getMotionRange(AMOTION_EVENT_AXIS_TOOL_MINOR, AINPUT_SOURCE_TOUCHPAD);
+ ASSERT_NE(nullptr, toolMinor);
+ EXPECT_NEAR(0, toolMinor->min, EPSILON);
+ EXPECT_NEAR(800, toolMinor->max, EPSILON);
+ EXPECT_NEAR(20, toolMinor->resolution, EPSILON);
+
+ // ...except orientation and pressure, which get scaled, and size, which is generated from other
+ // values.
+ const InputDeviceInfo::MotionRange* orientation =
+ info.getMotionRange(AMOTION_EVENT_AXIS_ORIENTATION, AINPUT_SOURCE_TOUCHPAD);
+ ASSERT_NE(nullptr, orientation);
+ EXPECT_NEAR(-M_PI_2, orientation->min, EPSILON);
+ EXPECT_NEAR(M_PI_2, orientation->max, EPSILON);
+ EXPECT_NEAR(0, orientation->resolution, EPSILON);
+
+ const InputDeviceInfo::MotionRange* pressure =
+ info.getMotionRange(AMOTION_EVENT_AXIS_PRESSURE, AINPUT_SOURCE_TOUCHPAD);
+ ASSERT_NE(nullptr, pressure);
+ EXPECT_NEAR(0, pressure->min, EPSILON);
+ EXPECT_NEAR(1, pressure->max, EPSILON);
+ EXPECT_NEAR(0, pressure->resolution, EPSILON);
+
+ const InputDeviceInfo::MotionRange* size =
+ info.getMotionRange(AMOTION_EVENT_AXIS_SIZE, AINPUT_SOURCE_TOUCHPAD);
+ ASSERT_NE(nullptr, size);
+ EXPECT_NEAR(0, size->min, EPSILON);
+ EXPECT_NEAR(1, size->max, EPSILON);
+ EXPECT_NEAR(0, size->resolution, EPSILON);
+}
+
+TEST_F(CapturedTouchpadEventConverterTest, MotionRanges_bareMinimumAxesPresent_populatedCorrectly) {
+ mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, 0, 4000, 0, 0, 45);
+ mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, 0, 2500, 0, 0, 40);
+ CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator,
+ DEVICE_ID);
+
+ InputDeviceInfo info;
+ conv.populateMotionRanges(info);
+
+ // Only the bare minimum motion ranges should be reported, and no others (e.g. size shouldn't be
+ // present, since it's generated from axes that aren't provided by this device).
+ EXPECT_NE(nullptr, info.getMotionRange(AMOTION_EVENT_AXIS_X, AINPUT_SOURCE_TOUCHPAD));
+ EXPECT_NE(nullptr, info.getMotionRange(AMOTION_EVENT_AXIS_Y, AINPUT_SOURCE_TOUCHPAD));
+ EXPECT_EQ(2u, info.getMotionRanges().size());
+}
+
+TEST_F(CapturedTouchpadEventConverterTest, OneFinger_motionReportedCorrectly) {
+ CapturedTouchpadEventConverter conv = createConverter();
+
+ processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
+ processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1);
+ processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 50);
+ processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 100);
+
+ processAxis(conv, EV_KEY, BTN_TOUCH, 1);
+ processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1);
+
+ EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPointerCount(1u),
+ WithCoords(50, 100), WithToolType(ToolType::FINGER)));
+
+ processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 52);
+ processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 99);
+
+ EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(1u),
+ WithCoords(52, 99), WithToolType(ToolType::FINGER)));
+
+ processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, -1);
+ processAxis(conv, EV_KEY, BTN_TOUCH, 0);
+ processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 0);
+
+ std::list<NotifyArgs> args = processSync(conv);
+ ASSERT_EQ(2u, args.size());
+ EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(1u),
+ WithCoords(52, 99), WithToolType(ToolType::FINGER)));
+ args.pop_front();
+ EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithPointerCount(1u),
+ WithCoords(52, 99), WithToolType(ToolType::FINGER)));
+}
+
+TEST_F(CapturedTouchpadEventConverterTest, OneFinger_touchDimensionsPassedThrough) {
+ addBasicAxesToEventHub();
+ mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MAJOR, 0, 1000, 0, 0, 0);
+ mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MINOR, 0, 1000, 0, 0, 0);
+ CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator,
+ DEVICE_ID);
+
+ processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
+ processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1);
+ processAxis(conv, EV_ABS, ABS_MT_TOUCH_MAJOR, 250);
+ processAxis(conv, EV_ABS, ABS_MT_TOUCH_MINOR, 120);
+ processAxis(conv, EV_ABS, ABS_MT_WIDTH_MAJOR, 400);
+ processAxis(conv, EV_ABS, ABS_MT_WIDTH_MINOR, 200);
+
+ processAxis(conv, EV_KEY, BTN_TOUCH, 1);
+ processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1);
+
+ EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPointerCount(1u),
+ WithTouchDimensions(250, 120), WithToolDimensions(400, 200)));
+}
+
+TEST_F(CapturedTouchpadEventConverterTest, OneFinger_orientationCalculatedCorrectly) {
+ addBasicAxesToEventHub();
+ mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_ORIENTATION, -3, 4, 0, 0, 0);
+ CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator,
+ DEVICE_ID);
+
+ processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
+ processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1);
+ processAxis(conv, EV_ABS, ABS_MT_ORIENTATION, -3);
+ processAxis(conv, EV_KEY, BTN_TOUCH, 1);
+ processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1);
+
+ EXPECT_NEAR(-3 * M_PI / 8,
+ processSyncAndExpectSingleMotionArg(conv).pointerCoords[0].getAxisValue(
+ AMOTION_EVENT_AXIS_ORIENTATION),
+ EPSILON);
+
+ processAxis(conv, EV_ABS, ABS_MT_ORIENTATION, 0);
+
+ EXPECT_NEAR(0,
+ processSyncAndExpectSingleMotionArg(conv).pointerCoords[0].getAxisValue(
+ AMOTION_EVENT_AXIS_ORIENTATION),
+ EPSILON);
+
+ processAxis(conv, EV_ABS, ABS_MT_ORIENTATION, 4);
+
+ EXPECT_NEAR(M_PI / 2,
+ processSyncAndExpectSingleMotionArg(conv).pointerCoords[0].getAxisValue(
+ AMOTION_EVENT_AXIS_ORIENTATION),
+ EPSILON);
+}
+
+TEST_F(CapturedTouchpadEventConverterTest, OneFinger_pressureScaledCorrectly) {
+ mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, 0, 4000, 0, 0, 45);
+ mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, 0, 2500, 0, 0, 40);
+ mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_PRESSURE, 0, 256, 0, 0, 0);
+ CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator,
+ DEVICE_ID);
+
+ processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
+ processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1);
+ processAxis(conv, EV_ABS, ABS_MT_PRESSURE, 128);
+ processAxis(conv, EV_KEY, BTN_TOUCH, 1);
+ processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1);
+
+ EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv), WithPressure(0.5));
+}
+
+TEST_F(CapturedTouchpadEventConverterTest,
+ OneFinger_withAllSizeAxes_sizeCalculatedFromTouchMajorMinorAverage) {
+ mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, 0, 4000, 0, 0, 45);
+ mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, 0, 2500, 0, 0, 40);
+ mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOUCH_MAJOR, 0, 256, 0, 0, 0);
+ mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOUCH_MINOR, 0, 256, 0, 0, 0);
+ mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MAJOR, 0, 256, 0, 0, 0);
+ mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MINOR, 0, 256, 0, 0, 0);
+ CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator,
+ DEVICE_ID);
+
+ processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
+ processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1);
+ processAxis(conv, EV_ABS, ABS_MT_TOUCH_MAJOR, 138);
+ processAxis(conv, EV_ABS, ABS_MT_TOUCH_MINOR, 118);
+ processAxis(conv, EV_ABS, ABS_MT_WIDTH_MAJOR, 200);
+ processAxis(conv, EV_ABS, ABS_MT_WIDTH_MINOR, 210);
+ processAxis(conv, EV_KEY, BTN_TOUCH, 1);
+ processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1);
+
+ EXPECT_NEAR(0.5,
+ processSyncAndExpectSingleMotionArg(conv).pointerCoords[0].getAxisValue(
+ AMOTION_EVENT_AXIS_SIZE),
+ EPSILON);
+}
+
+TEST_F(CapturedTouchpadEventConverterTest,
+ OneFinger_withMajorDimensionsOnly_sizeCalculatedFromTouchMajor) {
+ mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, 0, 4000, 0, 0, 45);
+ mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, 0, 2500, 0, 0, 40);
+ mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_TOUCH_MAJOR, 0, 256, 0, 0, 0);
+ mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MAJOR, 0, 256, 0, 0, 0);
+ CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator,
+ DEVICE_ID);
+
+ processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
+ processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1);
+ processAxis(conv, EV_ABS, ABS_MT_TOUCH_MAJOR, 128);
+ processAxis(conv, EV_ABS, ABS_MT_WIDTH_MAJOR, 200);
+ processAxis(conv, EV_KEY, BTN_TOUCH, 1);
+ processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1);
+
+ EXPECT_NEAR(0.5,
+ processSyncAndExpectSingleMotionArg(conv).pointerCoords[0].getAxisValue(
+ AMOTION_EVENT_AXIS_SIZE),
+ EPSILON);
+}
+
+TEST_F(CapturedTouchpadEventConverterTest,
+ OneFinger_withToolDimensionsOnly_sizeCalculatedFromToolMajorMinorAverage) {
+ mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, 0, 4000, 0, 0, 45);
+ mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, 0, 2500, 0, 0, 40);
+ mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MAJOR, 0, 256, 0, 0, 0);
+ mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MINOR, 0, 256, 0, 0, 0);
+ CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator,
+ DEVICE_ID);
+
+ processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
+ processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1);
+ processAxis(conv, EV_ABS, ABS_MT_WIDTH_MAJOR, 138);
+ processAxis(conv, EV_ABS, ABS_MT_WIDTH_MINOR, 118);
+ processAxis(conv, EV_KEY, BTN_TOUCH, 1);
+ processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1);
+
+ EXPECT_NEAR(0.5,
+ processSyncAndExpectSingleMotionArg(conv).pointerCoords[0].getAxisValue(
+ AMOTION_EVENT_AXIS_SIZE),
+ EPSILON);
+}
+
+TEST_F(CapturedTouchpadEventConverterTest,
+ OneFinger_withToolMajorOnly_sizeCalculatedFromTouchMajor) {
+ mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_X, 0, 4000, 0, 0, 45);
+ mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_POSITION_Y, 0, 2500, 0, 0, 40);
+ mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_WIDTH_MAJOR, 0, 256, 0, 0, 0);
+ CapturedTouchpadEventConverter conv(*mReader.getContext(), mDeviceContext, mAccumulator,
+ DEVICE_ID);
+
+ processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
+ processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1);
+ processAxis(conv, EV_ABS, ABS_MT_WIDTH_MAJOR, 128);
+ processAxis(conv, EV_KEY, BTN_TOUCH, 1);
+ processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1);
+
+ EXPECT_NEAR(0.5,
+ processSyncAndExpectSingleMotionArg(conv).pointerCoords[0].getAxisValue(
+ AMOTION_EVENT_AXIS_SIZE),
+ EPSILON);
+}
+
+TEST_F(CapturedTouchpadEventConverterTest, TwoFingers_motionReportedCorrectly) {
+ CapturedTouchpadEventConverter conv = createConverter();
+
+ processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
+ processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1);
+ processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 50);
+ processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 100);
+
+ processAxis(conv, EV_KEY, BTN_TOUCH, 1);
+ processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1);
+
+ EXPECT_THAT(processSyncAndExpectSingleMotionArg(conv),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPointerCount(1u),
+ WithCoords(50, 100), WithToolType(ToolType::FINGER)));
+
+ processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
+ processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 52);
+ processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 99);
+
+ processAxis(conv, EV_ABS, ABS_MT_SLOT, 1);
+ processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 2);
+ processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 250);
+ processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 200);
+
+ processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 0);
+ processAxis(conv, EV_KEY, BTN_TOOL_DOUBLETAP, 1);
+
+ std::list<NotifyArgs> args = processSync(conv);
+ ASSERT_EQ(2u, args.size());
+ EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(1u),
+ WithCoords(52, 99), WithToolType(ToolType::FINGER)));
+ args.pop_front();
+ EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+ 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithPointerCount(2u), WithPointerCoords(0, 52, 99),
+ WithPointerCoords(1, 250, 200), WithPointerToolType(0, ToolType::FINGER),
+ WithPointerToolType(1, ToolType::FINGER)));
+
+ processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
+ processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, -1);
+ processAxis(conv, EV_ABS, ABS_MT_SLOT, 1);
+ processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 255);
+ processAxis(conv, EV_ABS, ABS_MT_POSITION_Y, 202);
+
+ processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 1);
+ processAxis(conv, EV_KEY, BTN_TOOL_DOUBLETAP, 0);
+
+ args = processSync(conv);
+ ASSERT_EQ(2u, args.size());
+ EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(2u),
+ WithPointerCoords(0, 52, 99), WithPointerCoords(1, 255, 202),
+ WithPointerToolType(1, ToolType::FINGER),
+ WithPointerToolType(0, ToolType::FINGER)));
+ args.pop_front();
+ EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+ 0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithPointerCount(2u), WithPointerCoords(0, 52, 99),
+ WithPointerCoords(1, 255, 202), WithPointerToolType(0, ToolType::FINGER),
+ WithPointerToolType(1, ToolType::FINGER)));
+
+ processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, -1);
+ processAxis(conv, EV_KEY, BTN_TOOL_FINGER, 0);
+ processAxis(conv, EV_KEY, BTN_TOUCH, 0);
+
+ args = processSync(conv);
+ ASSERT_EQ(2u, args.size());
+ EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(1u),
+ WithCoords(255, 202), WithToolType(ToolType::FINGER)));
+ args.pop_front();
+ EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_UP), WithPointerCount(1u),
+ WithCoords(255, 202), WithToolType(ToolType::FINGER)));
+}
+
+// Pointer IDs max out at 31, and so must be reused once a touch is lifted to avoid running out.
+TEST_F(CapturedTouchpadEventConverterTest, PointerIdsReusedAfterLift) {
+ CapturedTouchpadEventConverter conv = createConverter();
+
+ // Put down two fingers, which should get IDs 0 and 1.
+ processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
+ processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 1);
+ processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 10);
+ processAxis(conv, EV_ABS, ABS_MT_SLOT, 1);
+ processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 2);
+ processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 20);
+
+ processAxis(conv, EV_KEY, BTN_TOUCH, 1);
+ processAxis(conv, EV_KEY, BTN_TOOL_DOUBLETAP, 1);
+
+ std::list<NotifyArgs> args = processSync(conv);
+ ASSERT_EQ(2u, args.size());
+ EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_DOWN), WithPointerCount(1u),
+ WithPointerId(/*index=*/0, /*id=*/0)));
+ args.pop_front();
+ EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+ 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithPointerCount(2u), WithPointerId(/*index=*/0, /*id=*/0),
+ WithPointerId(/*index=*/1, /*id=*/1)));
+
+ // Lift the finger in slot 0, freeing up pointer ID 0...
+ processAxis(conv, EV_ABS, ABS_MT_SLOT, 0);
+ processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, -1);
+
+ // ...and simultaneously add a finger in slot 2.
+ processAxis(conv, EV_ABS, ABS_MT_SLOT, 2);
+ processAxis(conv, EV_ABS, ABS_MT_TRACKING_ID, 3);
+ processAxis(conv, EV_ABS, ABS_MT_POSITION_X, 30);
+
+ args = processSync(conv);
+ ASSERT_EQ(3u, args.size());
+ // Slot 1 being present will result in a MOVE event, even though it hasn't actually moved (see
+ // comments in CapturedTouchpadEventConverter::sync).
+ EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_MOVE), WithPointerCount(2u),
+ WithPointerId(/*index=*/0, /*id=*/0), WithPointerId(/*index=*/1, /*id=*/1)));
+ args.pop_front();
+ EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_UP |
+ 0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithPointerCount(2u), WithPointerId(/*index=*/0, /*id=*/0),
+ WithPointerId(/*index=*/1, /*id=*/1)));
+ args.pop_front();
+ // Slot 0 being lifted causes the finger from slot 1 to move up to index 0, but keep its
+ // previous ID. The new finger in slot 2 should take ID 0, which was just freed up.
+ EXPECT_THAT(std::get<NotifyMotionArgs>(args.front()),
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_POINTER_DOWN |
+ 1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT),
+ WithPointerCount(2u), WithPointerId(/*index=*/0, /*id=*/1),
+ WithPointerId(/*index=*/1, /*id=*/0)));
+}
+
+} // namespace android
diff --git a/services/inputflinger/tests/HardwareStateConverter_test.cpp b/services/inputflinger/tests/HardwareStateConverter_test.cpp
index 19d46c8..5bea2ba 100644
--- a/services/inputflinger/tests/HardwareStateConverter_test.cpp
+++ b/services/inputflinger/tests/HardwareStateConverter_test.cpp
@@ -25,6 +25,7 @@
#include "FakeEventHub.h"
#include "FakeInputReaderPolicy.h"
#include "InstrumentedInputReader.h"
+#include "MultiTouchMotionAccumulator.h"
#include "TestConstants.h"
#include "TestInputListener.h"
@@ -38,8 +39,10 @@
mReader(mFakeEventHub, mFakePolicy, mFakeListener),
mDevice(newDevice()),
mDeviceContext(*mDevice, EVENTHUB_ID) {
- mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_SLOT, 0, 7, 0, 0, 0);
- mConverter = std::make_unique<HardwareStateConverter>(mDeviceContext);
+ const size_t slotCount = 8;
+ mFakeEventHub->addAbsoluteAxis(EVENTHUB_ID, ABS_MT_SLOT, 0, slotCount - 1, 0, 0, 0);
+ mAccumulator.configure(mDeviceContext, slotCount, /*usingSlotsProtocol=*/true);
+ mConverter = std::make_unique<HardwareStateConverter>(mDeviceContext, mAccumulator);
}
protected:
@@ -90,6 +93,7 @@
InstrumentedInputReader mReader;
std::shared_ptr<InputDevice> mDevice;
InputDeviceContext mDeviceContext;
+ MultiTouchMotionAccumulator mAccumulator;
std::unique_ptr<HardwareStateConverter> mConverter;
};
diff --git a/services/inputflinger/tests/TestInputListenerMatchers.h b/services/inputflinger/tests/TestInputListenerMatchers.h
index 338b747..db6f254 100644
--- a/services/inputflinger/tests/TestInputListenerMatchers.h
+++ b/services/inputflinger/tests/TestInputListenerMatchers.h
@@ -73,6 +73,12 @@
return arg.pointerCount == count;
}
+MATCHER_P2(WithPointerId, index, id, "MotionEvent with specified pointer ID for pointer index") {
+ const auto argPointerId = arg.pointerProperties[index].id;
+ *result_listener << "expected pointer with index " << index << " to have ID " << argPointerId;
+ return argPointerId == id;
+}
+
MATCHER_P2(WithCoords, x, y, "InputEvent with specified coords") {
const auto argX = arg.pointerCoords[0].getX();
const auto argY = arg.pointerCoords[0].getY();
@@ -136,6 +142,22 @@
return argPressure == pressure;
}
+MATCHER_P2(WithTouchDimensions, maj, min, "InputEvent with specified touch dimensions") {
+ const auto argMajor = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR);
+ const auto argMinor = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR);
+ *result_listener << "expected touch dimensions " << maj << " major x " << min
+ << " minor, but got " << argMajor << " major x " << argMinor << " minor";
+ return argMajor == maj && argMinor == min;
+}
+
+MATCHER_P2(WithToolDimensions, maj, min, "InputEvent with specified tool dimensions") {
+ const auto argMajor = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR);
+ const auto argMinor = arg.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR);
+ *result_listener << "expected tool dimensions " << maj << " major x " << min
+ << " minor, but got " << argMajor << " major x " << argMinor << " minor";
+ return argMajor == maj && argMinor == min;
+}
+
MATCHER_P(WithToolType, toolType, "InputEvent with specified tool type") {
const auto argToolType = arg.pointerProperties[0].toolType;
*result_listener << "expected tool type " << ftl::enum_string(toolType) << ", but got "
@@ -143,6 +165,14 @@
return argToolType == toolType;
}
+MATCHER_P2(WithPointerToolType, pointer, toolType,
+ "InputEvent with specified tool type for pointer") {
+ const auto argToolType = arg.pointerProperties[pointer].toolType;
+ *result_listener << "expected pointer " << pointer << " to have tool type "
+ << ftl::enum_string(toolType) << ", but got " << ftl::enum_string(argToolType);
+ return argToolType == toolType;
+}
+
MATCHER_P(WithFlags, flags, "InputEvent with specified flags") {
*result_listener << "expected flags " << flags << ", but got " << arg.flags;
return arg.flags == static_cast<int32_t>(flags);