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