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