blob: c8e7790c860d1af4b280dda391a5770bd68db4fd [file] [log] [blame]
Harry Cuttsbb24e272023-03-21 10:49:47 +00001/*
2 * Copyright 2023 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "CapturedTouchpadEventConverter.h"
18
Prabir Pradhan132f21c2024-07-25 16:48:30 +000019#include <optional>
Harry Cuttsbb24e272023-03-21 10:49:47 +000020#include <sstream>
21
22#include <android-base/stringprintf.h>
Harry Cuttsbb24e272023-03-21 10:49:47 +000023#include <input/PrintTools.h>
24#include <linux/input-event-codes.h>
25#include <log/log_main.h>
26
27namespace android {
28
29namespace {
30
31int32_t actionWithIndex(int32_t action, int32_t index) {
32 return action | (index << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
33}
34
35template <typename T>
36size_t firstUnmarkedBit(T set) {
37 // TODO: replace with std::countr_one from <bit> when that's available
38 LOG_ALWAYS_FATAL_IF(set.all());
39 size_t i = 0;
40 while (set.test(i)) {
41 i++;
42 }
43 return i;
44}
45
46} // namespace
47
48CapturedTouchpadEventConverter::CapturedTouchpadEventConverter(
49 InputReaderContext& readerContext, const InputDeviceContext& deviceContext,
50 MultiTouchMotionAccumulator& motionAccumulator, int32_t deviceId)
51 : mDeviceId(deviceId),
52 mReaderContext(readerContext),
53 mDeviceContext(deviceContext),
54 mMotionAccumulator(motionAccumulator),
55 mHasTouchMinor(deviceContext.hasAbsoluteAxis(ABS_MT_TOUCH_MINOR)),
56 mHasToolMinor(deviceContext.hasAbsoluteAxis(ABS_MT_WIDTH_MINOR)) {
Prabir Pradhan132f21c2024-07-25 16:48:30 +000057 if (std::optional<RawAbsoluteAxisInfo> orientation =
58 deviceContext.getAbsoluteAxisInfo(ABS_MT_ORIENTATION);
59 orientation) {
60 if (orientation->maxValue > 0) {
61 mOrientationScale = M_PI_2 / orientation->maxValue;
62 } else if (orientation->minValue < 0) {
63 mOrientationScale = -M_PI_2 / orientation->minValue;
Harry Cuttsbb24e272023-03-21 10:49:47 +000064 }
65 }
66
67 // TODO(b/275369880): support touch.pressure.calibration and .scale properties when captured.
Prabir Pradhan132f21c2024-07-25 16:48:30 +000068 if (std::optional<RawAbsoluteAxisInfo> pressure =
69 deviceContext.getAbsoluteAxisInfo(ABS_MT_PRESSURE);
70 pressure && pressure->maxValue > 0) {
71 mPressureScale = 1.0 / pressure->maxValue;
Harry Cuttsbb24e272023-03-21 10:49:47 +000072 }
73
Prabir Pradhan132f21c2024-07-25 16:48:30 +000074 std::optional<RawAbsoluteAxisInfo> touchMajor =
75 deviceContext.getAbsoluteAxisInfo(ABS_MT_TOUCH_MAJOR);
76 std::optional<RawAbsoluteAxisInfo> toolMajor =
77 deviceContext.getAbsoluteAxisInfo(ABS_MT_WIDTH_MAJOR);
78 mHasTouchMajor = touchMajor.has_value();
79 mHasToolMajor = toolMajor.has_value();
80 if (mHasTouchMajor && touchMajor->maxValue != 0) {
81 mSizeScale = 1.0f / touchMajor->maxValue;
82 } else if (mHasToolMajor && toolMajor->maxValue != 0) {
83 mSizeScale = 1.0f / toolMajor->maxValue;
Harry Cuttsbb24e272023-03-21 10:49:47 +000084 }
85}
86
87std::string CapturedTouchpadEventConverter::dump() const {
88 std::stringstream out;
89 out << "Orientation scale: " << mOrientationScale << "\n";
90 out << "Pressure scale: " << mPressureScale << "\n";
91 out << "Size scale: " << mSizeScale << "\n";
92
93 out << "Dimension axes:";
94 if (mHasTouchMajor) out << " touch major";
95 if (mHasTouchMinor) out << ", touch minor";
96 if (mHasToolMajor) out << ", tool major";
97 if (mHasToolMinor) out << ", tool minor";
98 out << "\n";
99
100 out << "Down time: " << mDownTime << "\n";
101 out << StringPrintf("Button state: 0x%08x\n", mButtonState);
102
103 out << StringPrintf("Pointer IDs in use: %s\n", mPointerIdsInUse.to_string().c_str());
104
105 out << "Pointer IDs for slot numbers:\n";
106 out << addLinePrefix(dumpMap(mPointerIdForSlotNumber), " ") << "\n";
107 return out.str();
108}
109
110void CapturedTouchpadEventConverter::populateMotionRanges(InputDeviceInfo& info) const {
Harry Cuttscdb5a7e2024-09-09 21:55:25 +0000111 tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_X, ABS_MT_POSITION_X);
112 tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_Y, ABS_MT_POSITION_Y);
Harry Cuttsbb24e272023-03-21 10:49:47 +0000113 tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_TOUCH_MAJOR, ABS_MT_TOUCH_MAJOR);
114 tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_TOUCH_MINOR, ABS_MT_TOUCH_MINOR);
115 tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_TOOL_MAJOR, ABS_MT_WIDTH_MAJOR);
116 tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_TOOL_MINOR, ABS_MT_WIDTH_MINOR);
117
Prabir Pradhan132f21c2024-07-25 16:48:30 +0000118 if (mDeviceContext.hasAbsoluteAxis(ABS_MT_PRESSURE)) {
Harry Cuttsbb24e272023-03-21 10:49:47 +0000119 info.addMotionRange(AMOTION_EVENT_AXIS_PRESSURE, SOURCE, 0, 1, 0, 0, 0);
120 }
121
Prabir Pradhan132f21c2024-07-25 16:48:30 +0000122 if (std::optional<RawAbsoluteAxisInfo> orientation =
123 mDeviceContext.getAbsoluteAxisInfo(ABS_MT_ORIENTATION);
124 orientation && (orientation->maxValue > 0 || orientation->minValue < 0)) {
Harry Cuttsbb24e272023-03-21 10:49:47 +0000125 info.addMotionRange(AMOTION_EVENT_AXIS_ORIENTATION, SOURCE, -M_PI_2, M_PI_2, 0, 0, 0);
126 }
127
128 if (mHasTouchMajor || mHasToolMajor) {
129 info.addMotionRange(AMOTION_EVENT_AXIS_SIZE, SOURCE, 0, 1, 0, 0, 0);
130 }
131}
132
133void CapturedTouchpadEventConverter::tryAddRawMotionRange(InputDeviceInfo& deviceInfo,
134 int32_t androidAxis,
135 int32_t evdevAxis) const {
Prabir Pradhan132f21c2024-07-25 16:48:30 +0000136 std::optional<RawAbsoluteAxisInfo> info = mDeviceContext.getAbsoluteAxisInfo(evdevAxis);
137 if (info) {
Harry Cuttscdb5a7e2024-09-09 21:55:25 +0000138 deviceInfo.addMotionRange(androidAxis, SOURCE, info->minValue, info->maxValue, info->flat,
139 info->fuzz, info->resolution);
Harry Cuttsbb24e272023-03-21 10:49:47 +0000140 }
141}
142
143void CapturedTouchpadEventConverter::reset() {
144 mCursorButtonAccumulator.reset(mDeviceContext);
145 mDownTime = 0;
146 mPointerIdsInUse.reset();
147 mPointerIdForSlotNumber.clear();
148}
149
150std::list<NotifyArgs> CapturedTouchpadEventConverter::process(const RawEvent& rawEvent) {
151 std::list<NotifyArgs> out;
152 if (rawEvent.type == EV_SYN && rawEvent.code == SYN_REPORT) {
153 out = sync(rawEvent.when, rawEvent.readTime);
154 mMotionAccumulator.finishSync();
155 }
156
Harry Cutts71953c22024-06-03 12:54:40 +0000157 mCursorButtonAccumulator.process(rawEvent);
158 mMotionAccumulator.process(rawEvent);
Harry Cuttsbb24e272023-03-21 10:49:47 +0000159 return out;
160}
161
162std::list<NotifyArgs> CapturedTouchpadEventConverter::sync(nsecs_t when, nsecs_t readTime) {
Harry Cuttsbb24e272023-03-21 10:49:47 +0000163 std::list<NotifyArgs> out;
164 std::vector<PointerCoords> coords;
165 std::vector<PointerProperties> properties;
Harry Cuttscdb5a7e2024-09-09 21:55:25 +0000166 std::map<size_t, size_t> coordsIndexForSlotNumber;
Harry Cuttsbb24e272023-03-21 10:49:47 +0000167
168 // For all the touches that were already down, send a MOVE event with their updated coordinates.
169 // A convention of the MotionEvent API is that pointer coordinates in UP events match the
170 // pointer's coordinates from the previous MOVE, so we still include touches here even if
171 // they've been lifted in this evdev frame.
172 if (!mPointerIdForSlotNumber.empty()) {
173 for (const auto [slotNumber, pointerId] : mPointerIdForSlotNumber) {
174 // Note that we don't check whether the touch has actually moved — it's rare for a touch
175 // to stay perfectly still between frames, and if it does the worst that can happen is
176 // an extra MOVE event, so it's not worth the overhead of checking for changes.
177 coordsIndexForSlotNumber[slotNumber] = coords.size();
Harry Cuttscdb5a7e2024-09-09 21:55:25 +0000178 coords.push_back(makePointerCoordsForSlot(mMotionAccumulator.getSlot(slotNumber)));
Harry Cuttsbb24e272023-03-21 10:49:47 +0000179 properties.push_back({.id = pointerId, .toolType = ToolType::FINGER});
180 }
181 out.push_back(
182 makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, coords, properties));
183 }
184
185 std::vector<size_t> upSlots, downSlots;
186 for (size_t i = 0; i < mMotionAccumulator.getSlotCount(); i++) {
Harry Cutts892bce52023-04-12 16:30:09 +0000187 const MultiTouchMotionAccumulator::Slot& slot = mMotionAccumulator.getSlot(i);
188 // Some touchpads continue to report contacts even after they've identified them as palms.
189 // We don't currently have a way to mark these as palms when reporting to apps, so don't
190 // report them at all.
191 const bool isInUse = slot.isInUse() && slot.getToolType() != ToolType::PALM;
Harry Cuttsbb24e272023-03-21 10:49:47 +0000192 const bool wasInUse = mPointerIdForSlotNumber.find(i) != mPointerIdForSlotNumber.end();
193 if (isInUse && !wasInUse) {
194 downSlots.push_back(i);
195 } else if (!isInUse && wasInUse) {
196 upSlots.push_back(i);
197 }
198 }
199
Harry Cutts23c8dff2023-05-10 17:39:59 +0000200 // Send BUTTON_RELEASE events. (This has to happen before any UP events to avoid sending
201 // BUTTON_RELEASE events without any pointers.)
202 uint32_t newButtonState;
203 if (coords.size() - upSlots.size() + downSlots.size() == 0) {
204 // If there won't be any pointers down after this evdev sync, we won't be able to send
205 // button updates on their own, as motion events without pointers are invalid. To avoid
206 // erroneously reporting buttons being held for long periods, send BUTTON_RELEASE events for
207 // all pressed buttons when the last pointer is lifted.
208 //
209 // This also prevents us from sending BUTTON_PRESS events too early in the case of touchpads
210 // which report a button press one evdev sync before reporting a touch going down.
211 newButtonState = 0;
212 } else {
213 newButtonState = mCursorButtonAccumulator.getButtonState();
214 }
215 for (uint32_t button = 1; button <= AMOTION_EVENT_BUTTON_FORWARD; button <<= 1) {
216 if (!(newButtonState & button) && mButtonState & button) {
217 mButtonState &= ~button;
218 out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_RELEASE,
219 coords, properties, /*actionButton=*/button));
220 }
221 }
222
Harry Cuttsbb24e272023-03-21 10:49:47 +0000223 // For any touches that were lifted, send UP or POINTER_UP events.
224 for (size_t slotNumber : upSlots) {
225 const size_t indexToRemove = coordsIndexForSlotNumber.at(slotNumber);
Harry Cutts892bce52023-04-12 16:30:09 +0000226 const bool cancel = mMotionAccumulator.getSlot(slotNumber).getToolType() == ToolType::PALM;
227 int32_t action;
228 if (coords.size() == 1) {
229 action = cancel ? AMOTION_EVENT_ACTION_CANCEL : AMOTION_EVENT_ACTION_UP;
230 } else {
231 action = actionWithIndex(AMOTION_EVENT_ACTION_POINTER_UP, indexToRemove);
232 }
233 out.push_back(makeMotionArgs(when, readTime, action, coords, properties, /*actionButton=*/0,
234 /*flags=*/cancel ? AMOTION_EVENT_FLAG_CANCELED : 0));
Harry Cuttsbb24e272023-03-21 10:49:47 +0000235
236 freePointerIdForSlot(slotNumber);
237 coords.erase(coords.begin() + indexToRemove);
238 properties.erase(properties.begin() + indexToRemove);
239 // Now that we've removed some coords and properties, we might have to update the slot
240 // number to coords index mapping.
241 coordsIndexForSlotNumber.erase(slotNumber);
242 for (auto& [_, index] : coordsIndexForSlotNumber) {
243 if (index > indexToRemove) {
244 index--;
245 }
246 }
247 }
248
249 // For new touches, send DOWN or POINTER_DOWN events.
250 for (size_t slotNumber : downSlots) {
251 const size_t coordsIndex = coords.size();
252 const int32_t action = coords.empty()
253 ? AMOTION_EVENT_ACTION_DOWN
254 : actionWithIndex(AMOTION_EVENT_ACTION_POINTER_DOWN, coordsIndex);
255
256 coordsIndexForSlotNumber[slotNumber] = coordsIndex;
Harry Cuttscdb5a7e2024-09-09 21:55:25 +0000257 coords.push_back(makePointerCoordsForSlot(mMotionAccumulator.getSlot(slotNumber)));
Harry Cuttsbb24e272023-03-21 10:49:47 +0000258 properties.push_back(
259 {.id = allocatePointerIdToSlot(slotNumber), .toolType = ToolType::FINGER});
260
261 out.push_back(makeMotionArgs(when, readTime, action, coords, properties));
262 }
263
Harry Cuttsbb24e272023-03-21 10:49:47 +0000264 for (uint32_t button = 1; button <= AMOTION_EVENT_BUTTON_FORWARD; button <<= 1) {
265 if (newButtonState & button && !(mButtonState & button)) {
266 mButtonState |= button;
267 out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_PRESS, coords,
268 properties, /*actionButton=*/button));
Harry Cuttsbb24e272023-03-21 10:49:47 +0000269 }
270 }
271 return out;
272}
273
274NotifyMotionArgs CapturedTouchpadEventConverter::makeMotionArgs(
275 nsecs_t when, nsecs_t readTime, int32_t action, const std::vector<PointerCoords>& coords,
Harry Cutts892bce52023-04-12 16:30:09 +0000276 const std::vector<PointerProperties>& properties, int32_t actionButton, int32_t flags) {
Harry Cuttsbb24e272023-03-21 10:49:47 +0000277 LOG_ALWAYS_FATAL_IF(coords.size() != properties.size(),
278 "Mismatched coords and properties arrays.");
279 return NotifyMotionArgs(mReaderContext.getNextId(), when, readTime, mDeviceId, SOURCE,
Siarhei Vishniakoucfbee532024-05-10 13:41:35 -0700280 ui::LogicalDisplayId::INVALID, /*policyFlags=*/POLICY_FLAG_WAKE, action,
Harry Cutts892bce52023-04-12 16:30:09 +0000281 /*actionButton=*/actionButton, flags,
Harry Cuttsbb24e272023-03-21 10:49:47 +0000282 mReaderContext.getGlobalMetaState(), mButtonState,
283 MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, coords.size(),
284 properties.data(), coords.data(), /*xPrecision=*/1.0f,
285 /*yPrecision=*/1.0f, AMOTION_EVENT_INVALID_CURSOR_POSITION,
286 AMOTION_EVENT_INVALID_CURSOR_POSITION, mDownTime, /*videoFrames=*/{});
287}
288
Harry Cuttscdb5a7e2024-09-09 21:55:25 +0000289PointerCoords CapturedTouchpadEventConverter::makePointerCoordsForSlot(
290 const MultiTouchMotionAccumulator::Slot& slot) const {
Harry Cuttsbb24e272023-03-21 10:49:47 +0000291 PointerCoords coords;
292 coords.clear();
293 coords.setAxisValue(AMOTION_EVENT_AXIS_X, slot.getX());
294 coords.setAxisValue(AMOTION_EVENT_AXIS_Y, slot.getY());
Harry Cuttsbb24e272023-03-21 10:49:47 +0000295 coords.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, slot.getTouchMajor());
296 coords.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, slot.getTouchMinor());
297 coords.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, slot.getToolMajor());
298 coords.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, slot.getToolMinor());
299 coords.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, slot.getOrientation() * mOrientationScale);
300 coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, slot.getPressure() * mPressureScale);
301 float size = 0;
302 // TODO(b/275369880): support touch.size.calibration and .isSummed properties when captured.
303 if (mHasTouchMajor) {
304 size = mHasTouchMinor ? (slot.getTouchMajor() + slot.getTouchMinor()) / 2
305 : slot.getTouchMajor();
306 } else if (mHasToolMajor) {
307 size = mHasToolMinor ? (slot.getToolMajor() + slot.getToolMinor()) / 2
308 : slot.getToolMajor();
309 }
310 coords.setAxisValue(AMOTION_EVENT_AXIS_SIZE, size * mSizeScale);
311 return coords;
312}
313
314int32_t CapturedTouchpadEventConverter::allocatePointerIdToSlot(size_t slotNumber) {
315 const int32_t pointerId = firstUnmarkedBit(mPointerIdsInUse);
316 mPointerIdsInUse.set(pointerId);
317 mPointerIdForSlotNumber[slotNumber] = pointerId;
318 return pointerId;
319}
320
321void CapturedTouchpadEventConverter::freePointerIdForSlot(size_t slotNumber) {
322 mPointerIdsInUse.reset(mPointerIdForSlotNumber.at(slotNumber));
323 mPointerIdForSlotNumber.erase(slotNumber);
324}
325
326} // namespace android