1 /*
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
19 #include <optional>
20 #include <sstream>
21
22 #include <android-base/stringprintf.h>
23 #include <com_android_input_flags.h>
24 #include <input/PrintTools.h>
25 #include <linux/input-event-codes.h>
26 #include <log/log_main.h>
27
28 namespace android {
29
30 namespace {
31
32 static constexpr uint32_t SOURCE = AINPUT_SOURCE_TOUCHPAD;
33
actionWithIndex(int32_t action,int32_t index)34 int32_t actionWithIndex(int32_t action, int32_t index) {
35 return action | (index << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
36 }
37
38 template <typename T>
firstUnmarkedBit(T set)39 size_t firstUnmarkedBit(T set) {
40 // TODO: replace with std::countr_one from <bit> when that's available
41 LOG_ALWAYS_FATAL_IF(set.all());
42 size_t i = 0;
43 while (set.test(i)) {
44 i++;
45 }
46 return i;
47 }
48
addRawMotionRange(InputDeviceInfo & deviceInfo,int32_t androidAxis,RawAbsoluteAxisInfo & evdevAxis)49 void addRawMotionRange(InputDeviceInfo& deviceInfo, int32_t androidAxis,
50 RawAbsoluteAxisInfo& evdevAxis) {
51 deviceInfo.addMotionRange(androidAxis, SOURCE, evdevAxis.minValue, evdevAxis.maxValue,
52 evdevAxis.flat, evdevAxis.fuzz, evdevAxis.resolution);
53 }
54
55 } // namespace
56
CapturedTouchpadEventConverter(InputReaderContext & readerContext,const InputDeviceContext & deviceContext,MultiTouchMotionAccumulator & motionAccumulator,int32_t deviceId)57 CapturedTouchpadEventConverter::CapturedTouchpadEventConverter(
58 InputReaderContext& readerContext, const InputDeviceContext& deviceContext,
59 MultiTouchMotionAccumulator& motionAccumulator, int32_t deviceId)
60 : mDeviceId(deviceId),
61 mReaderContext(readerContext),
62 mDeviceContext(deviceContext),
63 mMotionAccumulator(motionAccumulator),
64 mHasTouchMinor(deviceContext.hasAbsoluteAxis(ABS_MT_TOUCH_MINOR)),
65 mHasToolMinor(deviceContext.hasAbsoluteAxis(ABS_MT_WIDTH_MINOR)) {
66 if (std::optional<RawAbsoluteAxisInfo> orientation =
67 deviceContext.getAbsoluteAxisInfo(ABS_MT_ORIENTATION);
68 orientation) {
69 if (orientation->maxValue > 0) {
70 mOrientationScale = M_PI_2 / orientation->maxValue;
71 } else if (orientation->minValue < 0) {
72 mOrientationScale = -M_PI_2 / orientation->minValue;
73 }
74 }
75
76 // TODO(b/275369880): support touch.pressure.calibration and .scale properties when captured.
77 if (std::optional<RawAbsoluteAxisInfo> pressure =
78 deviceContext.getAbsoluteAxisInfo(ABS_MT_PRESSURE);
79 pressure && pressure->maxValue > 0) {
80 mPressureScale = 1.0 / pressure->maxValue;
81 }
82
83 std::optional<RawAbsoluteAxisInfo> touchMajor =
84 deviceContext.getAbsoluteAxisInfo(ABS_MT_TOUCH_MAJOR);
85 std::optional<RawAbsoluteAxisInfo> toolMajor =
86 deviceContext.getAbsoluteAxisInfo(ABS_MT_WIDTH_MAJOR);
87 mHasTouchMajor = touchMajor.has_value();
88 mHasToolMajor = toolMajor.has_value();
89 if (mHasTouchMajor && touchMajor->maxValue != 0) {
90 mSizeScale = 1.0f / touchMajor->maxValue;
91 } else if (mHasToolMajor && toolMajor->maxValue != 0) {
92 mSizeScale = 1.0f / toolMajor->maxValue;
93 }
94 }
95
dump() const96 std::string CapturedTouchpadEventConverter::dump() const {
97 std::stringstream out;
98 out << "Orientation scale: " << mOrientationScale << "\n";
99 out << "Pressure scale: " << mPressureScale << "\n";
100 out << "Size scale: " << mSizeScale << "\n";
101
102 out << "Dimension axes:";
103 if (mHasTouchMajor) out << " touch major";
104 if (mHasTouchMinor) out << ", touch minor";
105 if (mHasToolMajor) out << ", tool major";
106 if (mHasToolMinor) out << ", tool minor";
107 out << "\n";
108
109 out << "Down time: " << mDownTime << "\n";
110 out << StringPrintf("Button state: 0x%08x\n", mButtonState);
111
112 out << StringPrintf("Pointer IDs in use: %s\n", mPointerIdsInUse.to_string().c_str());
113
114 out << "Pointer IDs for slot numbers:\n";
115 out << addLinePrefix(dumpMap(mPointerIdForSlotNumber), " ") << "\n";
116 return out.str();
117 }
118
populateMotionRanges(InputDeviceInfo & info) const119 void CapturedTouchpadEventConverter::populateMotionRanges(InputDeviceInfo& info) const {
120 tryAddRawMotionRangeWithRelative(/*byref*/ info, AMOTION_EVENT_AXIS_X,
121 AMOTION_EVENT_AXIS_RELATIVE_X, ABS_MT_POSITION_X);
122 tryAddRawMotionRangeWithRelative(/*byref*/ info, AMOTION_EVENT_AXIS_Y,
123 AMOTION_EVENT_AXIS_RELATIVE_Y, ABS_MT_POSITION_Y);
124 tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_TOUCH_MAJOR, ABS_MT_TOUCH_MAJOR);
125 tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_TOUCH_MINOR, ABS_MT_TOUCH_MINOR);
126 tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_TOOL_MAJOR, ABS_MT_WIDTH_MAJOR);
127 tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_TOOL_MINOR, ABS_MT_WIDTH_MINOR);
128
129 if (mDeviceContext.hasAbsoluteAxis(ABS_MT_PRESSURE)) {
130 info.addMotionRange(AMOTION_EVENT_AXIS_PRESSURE, SOURCE, 0, 1, 0, 0, 0);
131 }
132
133 if (std::optional<RawAbsoluteAxisInfo> orientation =
134 mDeviceContext.getAbsoluteAxisInfo(ABS_MT_ORIENTATION);
135 orientation && (orientation->maxValue > 0 || orientation->minValue < 0)) {
136 info.addMotionRange(AMOTION_EVENT_AXIS_ORIENTATION, SOURCE, -M_PI_2, M_PI_2, 0, 0, 0);
137 }
138
139 if (mHasTouchMajor || mHasToolMajor) {
140 info.addMotionRange(AMOTION_EVENT_AXIS_SIZE, SOURCE, 0, 1, 0, 0, 0);
141 }
142 }
143
tryAddRawMotionRange(InputDeviceInfo & deviceInfo,int32_t androidAxis,int32_t evdevAxis) const144 void CapturedTouchpadEventConverter::tryAddRawMotionRange(InputDeviceInfo& deviceInfo,
145 int32_t androidAxis,
146 int32_t evdevAxis) const {
147 std::optional<RawAbsoluteAxisInfo> info = mDeviceContext.getAbsoluteAxisInfo(evdevAxis);
148 if (info) {
149 addRawMotionRange(/*byref*/ deviceInfo, androidAxis, *info);
150 }
151 }
152
tryAddRawMotionRangeWithRelative(InputDeviceInfo & deviceInfo,int32_t androidAxis,int32_t androidRelativeAxis,int32_t evdevAxis) const153 void CapturedTouchpadEventConverter::tryAddRawMotionRangeWithRelative(InputDeviceInfo& deviceInfo,
154 int32_t androidAxis,
155 int32_t androidRelativeAxis,
156 int32_t evdevAxis) const {
157 std::optional<RawAbsoluteAxisInfo> axisInfo = mDeviceContext.getAbsoluteAxisInfo(evdevAxis);
158 if (axisInfo) {
159 addRawMotionRange(/*byref*/ deviceInfo, androidAxis, *axisInfo);
160
161 // The largest movement we could possibly report on a relative axis is from the minimum to
162 // the maximum (or vice versa) of the absolute axis.
163 float range = axisInfo->maxValue - axisInfo->minValue;
164 deviceInfo.addMotionRange(androidRelativeAxis, SOURCE, -range, range, axisInfo->flat,
165 axisInfo->fuzz, axisInfo->resolution);
166 }
167 }
168
reset()169 void CapturedTouchpadEventConverter::reset() {
170 mCursorButtonAccumulator.reset(mDeviceContext);
171 mDownTime = 0;
172 mPointerIdsInUse.reset();
173 mPointerIdForSlotNumber.clear();
174 }
175
process(const RawEvent & rawEvent)176 std::list<NotifyArgs> CapturedTouchpadEventConverter::process(const RawEvent& rawEvent) {
177 std::list<NotifyArgs> out;
178 if (rawEvent.type == EV_SYN && rawEvent.code == SYN_REPORT) {
179 out = sync(rawEvent.when, rawEvent.readTime);
180 mMotionAccumulator.finishSync();
181 }
182
183 mCursorButtonAccumulator.process(rawEvent);
184 mMotionAccumulator.process(rawEvent);
185 return out;
186 }
187
sync(nsecs_t when,nsecs_t readTime)188 std::list<NotifyArgs> CapturedTouchpadEventConverter::sync(nsecs_t when, nsecs_t readTime) {
189 std::list<NotifyArgs> out;
190 std::vector<PointerCoords> coords;
191 std::vector<PointerProperties> properties;
192 std::map<size_t /*slotNumber*/, size_t /*coordsIndex*/> coordsIndexForSlotNumber;
193
194 // For all the touches that were already down, send a MOVE event with their updated coordinates.
195 // A convention of the MotionEvent API is that pointer coordinates in UP events match the
196 // pointer's coordinates from the previous MOVE, so we still include touches here even if
197 // they've been lifted in this evdev frame.
198 if (!mPointerIdForSlotNumber.empty()) {
199 for (const auto [slotNumber, pointerId] : mPointerIdForSlotNumber) {
200 // Note that we don't check whether the touch has actually moved — it's rare for a touch
201 // to stay perfectly still between frames, and if it does the worst that can happen is
202 // an extra MOVE event, so it's not worth the overhead of checking for changes.
203 coordsIndexForSlotNumber[slotNumber] = coords.size();
204 coords.push_back(makePointerCoordsForSlot(slotNumber));
205 properties.push_back({.id = pointerId, .toolType = ToolType::FINGER});
206 }
207 out.push_back(
208 makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, coords, properties));
209 // For any further events we send from this sync, the pointers won't have moved relative to
210 // the positions we just reported in this MOVE event, so zero out the relative axes.
211 for (PointerCoords& pointer : coords) {
212 pointer.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, 0);
213 pointer.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, 0);
214 }
215 }
216
217 std::vector<size_t> upSlots, downSlots;
218 for (size_t i = 0; i < mMotionAccumulator.getSlotCount(); i++) {
219 const MultiTouchMotionAccumulator::Slot& slot = mMotionAccumulator.getSlot(i);
220 // Some touchpads continue to report contacts even after they've identified them as palms.
221 // We don't currently have a way to mark these as palms when reporting to apps, so don't
222 // report them at all.
223 const bool isInUse = slot.isInUse() && slot.getToolType() != ToolType::PALM;
224 const bool wasInUse = mPointerIdForSlotNumber.find(i) != mPointerIdForSlotNumber.end();
225 if (isInUse && !wasInUse) {
226 downSlots.push_back(i);
227 } else if (!isInUse && wasInUse) {
228 upSlots.push_back(i);
229 }
230 }
231
232 // Send BUTTON_RELEASE events. (This has to happen before any UP events to avoid sending
233 // BUTTON_RELEASE events without any pointers.)
234 uint32_t newButtonState;
235 if (coords.size() - upSlots.size() + downSlots.size() == 0) {
236 // If there won't be any pointers down after this evdev sync, we won't be able to send
237 // button updates on their own, as motion events without pointers are invalid. To avoid
238 // erroneously reporting buttons being held for long periods, send BUTTON_RELEASE events for
239 // all pressed buttons when the last pointer is lifted.
240 //
241 // This also prevents us from sending BUTTON_PRESS events too early in the case of touchpads
242 // which report a button press one evdev sync before reporting a touch going down.
243 newButtonState = 0;
244 } else {
245 newButtonState = mCursorButtonAccumulator.getButtonState();
246 }
247 for (uint32_t button = 1; button <= AMOTION_EVENT_BUTTON_FORWARD; button <<= 1) {
248 if (!(newButtonState & button) && mButtonState & button) {
249 mButtonState &= ~button;
250 out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_RELEASE,
251 coords, properties, /*actionButton=*/button));
252 }
253 }
254
255 // For any touches that were lifted, send UP or POINTER_UP events.
256 for (size_t slotNumber : upSlots) {
257 const size_t indexToRemove = coordsIndexForSlotNumber.at(slotNumber);
258 const bool cancel = mMotionAccumulator.getSlot(slotNumber).getToolType() == ToolType::PALM;
259 int32_t action;
260 if (coords.size() == 1) {
261 action = cancel ? AMOTION_EVENT_ACTION_CANCEL : AMOTION_EVENT_ACTION_UP;
262 } else {
263 action = actionWithIndex(AMOTION_EVENT_ACTION_POINTER_UP, indexToRemove);
264 }
265 out.push_back(makeMotionArgs(when, readTime, action, coords, properties, /*actionButton=*/0,
266 /*flags=*/cancel ? AMOTION_EVENT_FLAG_CANCELED : 0));
267
268 freePointerIdForSlot(slotNumber);
269 mPreviousCoordsForSlotNumber.erase(slotNumber);
270 coords.erase(coords.begin() + indexToRemove);
271 properties.erase(properties.begin() + indexToRemove);
272 // Now that we've removed some coords and properties, we might have to update the slot
273 // number to coords index mapping.
274 coordsIndexForSlotNumber.erase(slotNumber);
275 for (auto& [_, index] : coordsIndexForSlotNumber) {
276 if (index > indexToRemove) {
277 index--;
278 }
279 }
280 }
281
282 // For new touches, send DOWN or POINTER_DOWN events.
283 for (size_t slotNumber : downSlots) {
284 const size_t coordsIndex = coords.size();
285 const int32_t action = coords.empty()
286 ? AMOTION_EVENT_ACTION_DOWN
287 : actionWithIndex(AMOTION_EVENT_ACTION_POINTER_DOWN, coordsIndex);
288
289 coordsIndexForSlotNumber[slotNumber] = coordsIndex;
290 coords.push_back(makePointerCoordsForSlot(slotNumber));
291 properties.push_back(
292 {.id = allocatePointerIdToSlot(slotNumber), .toolType = ToolType::FINGER});
293
294 out.push_back(makeMotionArgs(when, readTime, action, coords, properties));
295 }
296
297 for (uint32_t button = 1; button <= AMOTION_EVENT_BUTTON_FORWARD; button <<= 1) {
298 if (newButtonState & button && !(mButtonState & button)) {
299 mButtonState |= button;
300 out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_PRESS, coords,
301 properties, /*actionButton=*/button));
302 }
303 }
304 return out;
305 }
306
makeMotionArgs(nsecs_t when,nsecs_t readTime,int32_t action,const std::vector<PointerCoords> & coords,const std::vector<PointerProperties> & properties,int32_t actionButton,int32_t flags)307 NotifyMotionArgs CapturedTouchpadEventConverter::makeMotionArgs(
308 nsecs_t when, nsecs_t readTime, int32_t action, const std::vector<PointerCoords>& coords,
309 const std::vector<PointerProperties>& properties, int32_t actionButton, int32_t flags) {
310 LOG_ALWAYS_FATAL_IF(coords.size() != properties.size(),
311 "Mismatched coords and properties arrays.");
312 return NotifyMotionArgs(mReaderContext.getNextId(), when, readTime, mDeviceId, SOURCE,
313 ui::LogicalDisplayId::INVALID, /*policyFlags=*/POLICY_FLAG_WAKE, action,
314 /*actionButton=*/actionButton, flags,
315 mReaderContext.getGlobalMetaState(), mButtonState,
316 MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, coords.size(),
317 properties.data(), coords.data(), /*xPrecision=*/1.0f,
318 /*yPrecision=*/1.0f, AMOTION_EVENT_INVALID_CURSOR_POSITION,
319 AMOTION_EVENT_INVALID_CURSOR_POSITION, mDownTime, /*videoFrames=*/{});
320 }
321
makePointerCoordsForSlot(size_t slotNumber)322 PointerCoords CapturedTouchpadEventConverter::makePointerCoordsForSlot(size_t slotNumber) {
323 const MultiTouchMotionAccumulator::Slot& slot = mMotionAccumulator.getSlot(slotNumber);
324 PointerCoords coords;
325 coords.clear();
326 coords.setAxisValue(AMOTION_EVENT_AXIS_X, slot.getX());
327 coords.setAxisValue(AMOTION_EVENT_AXIS_Y, slot.getY());
328 if (auto it = mPreviousCoordsForSlotNumber.find(slotNumber);
329 it != mPreviousCoordsForSlotNumber.end()) {
330 auto [oldX, oldY] = it->second;
331 coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, slot.getX() - oldX);
332 coords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, slot.getY() - oldY);
333 }
334 mPreviousCoordsForSlotNumber[slotNumber] = std::make_pair(slot.getX(), slot.getY());
335
336 coords.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, slot.getTouchMajor());
337 coords.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, slot.getTouchMinor());
338 coords.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, slot.getToolMajor());
339 coords.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, slot.getToolMinor());
340 coords.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, slot.getOrientation() * mOrientationScale);
341 coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, slot.getPressure() * mPressureScale);
342 float size = 0;
343 // TODO(b/275369880): support touch.size.calibration and .isSummed properties when captured.
344 if (mHasTouchMajor) {
345 size = mHasTouchMinor ? (slot.getTouchMajor() + slot.getTouchMinor()) / 2
346 : slot.getTouchMajor();
347 } else if (mHasToolMajor) {
348 size = mHasToolMinor ? (slot.getToolMajor() + slot.getToolMinor()) / 2
349 : slot.getToolMajor();
350 }
351 coords.setAxisValue(AMOTION_EVENT_AXIS_SIZE, size * mSizeScale);
352 return coords;
353 }
354
allocatePointerIdToSlot(size_t slotNumber)355 int32_t CapturedTouchpadEventConverter::allocatePointerIdToSlot(size_t slotNumber) {
356 const int32_t pointerId = firstUnmarkedBit(mPointerIdsInUse);
357 mPointerIdsInUse.set(pointerId);
358 mPointerIdForSlotNumber[slotNumber] = pointerId;
359 return pointerId;
360 }
361
freePointerIdForSlot(size_t slotNumber)362 void CapturedTouchpadEventConverter::freePointerIdForSlot(size_t slotNumber) {
363 mPointerIdsInUse.reset(mPointerIdForSlotNumber.at(slotNumber));
364 mPointerIdForSlotNumber.erase(slotNumber);
365 }
366
367 } // namespace android
368