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