blob: 023372aa979205a87df4dd9a76171198a0bf4c41 [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
19#include <sstream>
20
21#include <android-base/stringprintf.h>
22#include <gui/constants.h>
23#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)) {
57 RawAbsoluteAxisInfo orientationInfo;
58 deviceContext.getAbsoluteAxisInfo(ABS_MT_ORIENTATION, &orientationInfo);
59 if (orientationInfo.valid) {
60 if (orientationInfo.maxValue > 0) {
61 mOrientationScale = M_PI_2 / orientationInfo.maxValue;
62 } else if (orientationInfo.minValue < 0) {
63 mOrientationScale = -M_PI_2 / orientationInfo.minValue;
64 }
65 }
66
67 // TODO(b/275369880): support touch.pressure.calibration and .scale properties when captured.
68 RawAbsoluteAxisInfo pressureInfo;
69 deviceContext.getAbsoluteAxisInfo(ABS_MT_PRESSURE, &pressureInfo);
70 if (pressureInfo.valid && pressureInfo.maxValue > 0) {
71 mPressureScale = 1.0 / pressureInfo.maxValue;
72 }
73
74 RawAbsoluteAxisInfo touchMajorInfo, toolMajorInfo;
75 deviceContext.getAbsoluteAxisInfo(ABS_MT_TOUCH_MAJOR, &touchMajorInfo);
76 deviceContext.getAbsoluteAxisInfo(ABS_MT_WIDTH_MAJOR, &toolMajorInfo);
77 mHasTouchMajor = touchMajorInfo.valid;
78 mHasToolMajor = toolMajorInfo.valid;
79 if (mHasTouchMajor && touchMajorInfo.maxValue != 0) {
80 mSizeScale = 1.0f / touchMajorInfo.maxValue;
81 } else if (mHasToolMajor && toolMajorInfo.maxValue != 0) {
82 mSizeScale = 1.0f / toolMajorInfo.maxValue;
83 }
84}
85
86std::string CapturedTouchpadEventConverter::dump() const {
87 std::stringstream out;
88 out << "Orientation scale: " << mOrientationScale << "\n";
89 out << "Pressure scale: " << mPressureScale << "\n";
90 out << "Size scale: " << mSizeScale << "\n";
91
92 out << "Dimension axes:";
93 if (mHasTouchMajor) out << " touch major";
94 if (mHasTouchMinor) out << ", touch minor";
95 if (mHasToolMajor) out << ", tool major";
96 if (mHasToolMinor) out << ", tool minor";
97 out << "\n";
98
99 out << "Down time: " << mDownTime << "\n";
100 out << StringPrintf("Button state: 0x%08x\n", mButtonState);
101
102 out << StringPrintf("Pointer IDs in use: %s\n", mPointerIdsInUse.to_string().c_str());
103
104 out << "Pointer IDs for slot numbers:\n";
105 out << addLinePrefix(dumpMap(mPointerIdForSlotNumber), " ") << "\n";
106 return out.str();
107}
108
109void CapturedTouchpadEventConverter::populateMotionRanges(InputDeviceInfo& info) const {
110 tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_X, ABS_MT_POSITION_X);
111 tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_Y, ABS_MT_POSITION_Y);
112 tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_TOUCH_MAJOR, ABS_MT_TOUCH_MAJOR);
113 tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_TOUCH_MINOR, ABS_MT_TOUCH_MINOR);
114 tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_TOOL_MAJOR, ABS_MT_WIDTH_MAJOR);
115 tryAddRawMotionRange(/*byref*/ info, AMOTION_EVENT_AXIS_TOOL_MINOR, ABS_MT_WIDTH_MINOR);
116
117 RawAbsoluteAxisInfo pressureInfo;
118 mDeviceContext.getAbsoluteAxisInfo(ABS_MT_PRESSURE, &pressureInfo);
119 if (pressureInfo.valid) {
120 info.addMotionRange(AMOTION_EVENT_AXIS_PRESSURE, SOURCE, 0, 1, 0, 0, 0);
121 }
122
123 RawAbsoluteAxisInfo orientationInfo;
124 mDeviceContext.getAbsoluteAxisInfo(ABS_MT_ORIENTATION, &orientationInfo);
125 if (orientationInfo.valid && (orientationInfo.maxValue > 0 || orientationInfo.minValue < 0)) {
126 info.addMotionRange(AMOTION_EVENT_AXIS_ORIENTATION, SOURCE, -M_PI_2, M_PI_2, 0, 0, 0);
127 }
128
129 if (mHasTouchMajor || mHasToolMajor) {
130 info.addMotionRange(AMOTION_EVENT_AXIS_SIZE, SOURCE, 0, 1, 0, 0, 0);
131 }
132}
133
134void CapturedTouchpadEventConverter::tryAddRawMotionRange(InputDeviceInfo& deviceInfo,
135 int32_t androidAxis,
136 int32_t evdevAxis) const {
137 RawAbsoluteAxisInfo info;
138 mDeviceContext.getAbsoluteAxisInfo(evdevAxis, &info);
139 if (info.valid) {
140 deviceInfo.addMotionRange(androidAxis, SOURCE, info.minValue, info.maxValue, info.flat,
141 info.fuzz, info.resolution);
142 }
143}
144
145void CapturedTouchpadEventConverter::reset() {
146 mCursorButtonAccumulator.reset(mDeviceContext);
147 mDownTime = 0;
148 mPointerIdsInUse.reset();
149 mPointerIdForSlotNumber.clear();
150}
151
152std::list<NotifyArgs> CapturedTouchpadEventConverter::process(const RawEvent& rawEvent) {
153 std::list<NotifyArgs> out;
154 if (rawEvent.type == EV_SYN && rawEvent.code == SYN_REPORT) {
155 out = sync(rawEvent.when, rawEvent.readTime);
156 mMotionAccumulator.finishSync();
157 }
158
159 mCursorButtonAccumulator.process(&rawEvent);
160 mMotionAccumulator.process(&rawEvent);
161 return out;
162}
163
164std::list<NotifyArgs> CapturedTouchpadEventConverter::sync(nsecs_t when, nsecs_t readTime) {
165 // TODO(b/259547750): filter out touches marked as palms (using MT_TOOL_PALM).
166 std::list<NotifyArgs> out;
167 std::vector<PointerCoords> coords;
168 std::vector<PointerProperties> properties;
169 std::map<size_t, size_t> coordsIndexForSlotNumber;
170
171 // For all the touches that were already down, send a MOVE event with their updated coordinates.
172 // A convention of the MotionEvent API is that pointer coordinates in UP events match the
173 // pointer's coordinates from the previous MOVE, so we still include touches here even if
174 // they've been lifted in this evdev frame.
175 if (!mPointerIdForSlotNumber.empty()) {
176 for (const auto [slotNumber, pointerId] : mPointerIdForSlotNumber) {
177 // Note that we don't check whether the touch has actually moved — it's rare for a touch
178 // to stay perfectly still between frames, and if it does the worst that can happen is
179 // an extra MOVE event, so it's not worth the overhead of checking for changes.
180 coordsIndexForSlotNumber[slotNumber] = coords.size();
181 coords.push_back(makePointerCoordsForSlot(mMotionAccumulator.getSlot(slotNumber)));
182 properties.push_back({.id = pointerId, .toolType = ToolType::FINGER});
183 }
184 out.push_back(
185 makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, coords, properties));
186 }
187
188 std::vector<size_t> upSlots, downSlots;
189 for (size_t i = 0; i < mMotionAccumulator.getSlotCount(); i++) {
190 const bool isInUse = mMotionAccumulator.getSlot(i).isInUse();
191 const bool wasInUse = mPointerIdForSlotNumber.find(i) != mPointerIdForSlotNumber.end();
192 if (isInUse && !wasInUse) {
193 downSlots.push_back(i);
194 } else if (!isInUse && wasInUse) {
195 upSlots.push_back(i);
196 }
197 }
198
199 // For any touches that were lifted, send UP or POINTER_UP events.
200 for (size_t slotNumber : upSlots) {
201 const size_t indexToRemove = coordsIndexForSlotNumber.at(slotNumber);
202 const int32_t action = coords.size() == 1
203 ? AMOTION_EVENT_ACTION_UP
204 : actionWithIndex(AMOTION_EVENT_ACTION_POINTER_UP, indexToRemove);
205 out.push_back(makeMotionArgs(when, readTime, action, coords, properties));
206
207 freePointerIdForSlot(slotNumber);
208 coords.erase(coords.begin() + indexToRemove);
209 properties.erase(properties.begin() + indexToRemove);
210 // Now that we've removed some coords and properties, we might have to update the slot
211 // number to coords index mapping.
212 coordsIndexForSlotNumber.erase(slotNumber);
213 for (auto& [_, index] : coordsIndexForSlotNumber) {
214 if (index > indexToRemove) {
215 index--;
216 }
217 }
218 }
219
220 // For new touches, send DOWN or POINTER_DOWN events.
221 for (size_t slotNumber : downSlots) {
222 const size_t coordsIndex = coords.size();
223 const int32_t action = coords.empty()
224 ? AMOTION_EVENT_ACTION_DOWN
225 : actionWithIndex(AMOTION_EVENT_ACTION_POINTER_DOWN, coordsIndex);
226
227 coordsIndexForSlotNumber[slotNumber] = coordsIndex;
228 coords.push_back(makePointerCoordsForSlot(mMotionAccumulator.getSlot(slotNumber)));
229 properties.push_back(
230 {.id = allocatePointerIdToSlot(slotNumber), .toolType = ToolType::FINGER});
231
232 out.push_back(makeMotionArgs(when, readTime, action, coords, properties));
233 }
234
235 const uint32_t newButtonState = mCursorButtonAccumulator.getButtonState();
236 for (uint32_t button = 1; button <= AMOTION_EVENT_BUTTON_FORWARD; button <<= 1) {
237 if (newButtonState & button && !(mButtonState & button)) {
238 mButtonState |= button;
239 out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_PRESS, coords,
240 properties, /*actionButton=*/button));
241 } else if (!(newButtonState & button) && mButtonState & button) {
242 mButtonState &= ~button;
243 out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_RELEASE,
244 coords, properties, /*actionButton=*/button));
245 }
246 }
247 return out;
248}
249
250NotifyMotionArgs CapturedTouchpadEventConverter::makeMotionArgs(
251 nsecs_t when, nsecs_t readTime, int32_t action, const std::vector<PointerCoords>& coords,
252 const std::vector<PointerProperties>& properties, int32_t actionButton) {
253 LOG_ALWAYS_FATAL_IF(coords.size() != properties.size(),
254 "Mismatched coords and properties arrays.");
255 return NotifyMotionArgs(mReaderContext.getNextId(), when, readTime, mDeviceId, SOURCE,
256 ADISPLAY_ID_NONE, /*policyFlags=*/POLICY_FLAG_WAKE, action,
257 /*actionButton=*/actionButton, /*flags=*/0,
258 mReaderContext.getGlobalMetaState(), mButtonState,
259 MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, coords.size(),
260 properties.data(), coords.data(), /*xPrecision=*/1.0f,
261 /*yPrecision=*/1.0f, AMOTION_EVENT_INVALID_CURSOR_POSITION,
262 AMOTION_EVENT_INVALID_CURSOR_POSITION, mDownTime, /*videoFrames=*/{});
263}
264
265PointerCoords CapturedTouchpadEventConverter::makePointerCoordsForSlot(
266 const MultiTouchMotionAccumulator::Slot& slot) const {
267 PointerCoords coords;
268 coords.clear();
269 coords.setAxisValue(AMOTION_EVENT_AXIS_X, slot.getX());
270 coords.setAxisValue(AMOTION_EVENT_AXIS_Y, slot.getY());
271 coords.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, slot.getTouchMajor());
272 coords.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, slot.getTouchMinor());
273 coords.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, slot.getToolMajor());
274 coords.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, slot.getToolMinor());
275 coords.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, slot.getOrientation() * mOrientationScale);
276 coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, slot.getPressure() * mPressureScale);
277 float size = 0;
278 // TODO(b/275369880): support touch.size.calibration and .isSummed properties when captured.
279 if (mHasTouchMajor) {
280 size = mHasTouchMinor ? (slot.getTouchMajor() + slot.getTouchMinor()) / 2
281 : slot.getTouchMajor();
282 } else if (mHasToolMajor) {
283 size = mHasToolMinor ? (slot.getToolMajor() + slot.getToolMinor()) / 2
284 : slot.getToolMajor();
285 }
286 coords.setAxisValue(AMOTION_EVENT_AXIS_SIZE, size * mSizeScale);
287 return coords;
288}
289
290int32_t CapturedTouchpadEventConverter::allocatePointerIdToSlot(size_t slotNumber) {
291 const int32_t pointerId = firstUnmarkedBit(mPointerIdsInUse);
292 mPointerIdsInUse.set(pointerId);
293 mPointerIdForSlotNumber[slotNumber] = pointerId;
294 return pointerId;
295}
296
297void CapturedTouchpadEventConverter::freePointerIdForSlot(size_t slotNumber) {
298 mPointerIdsInUse.reset(mPointerIdForSlotNumber.at(slotNumber));
299 mPointerIdForSlotNumber.erase(slotNumber);
300}
301
302} // namespace android