• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 <gui/constants.h>
23 #include <input/PrintTools.h>
24 #include <linux/input-event-codes.h>
25 #include <log/log_main.h>
26 
27 namespace android {
28 
29 namespace {
30 
actionWithIndex(int32_t action,int32_t index)31 int32_t actionWithIndex(int32_t action, int32_t index) {
32     return action | (index << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
33 }
34 
35 template <typename T>
firstUnmarkedBit(T set)36 size_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 
CapturedTouchpadEventConverter(InputReaderContext & readerContext,const InputDeviceContext & deviceContext,MultiTouchMotionAccumulator & motionAccumulator,int32_t deviceId)48 CapturedTouchpadEventConverter::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 
dump() const86 std::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 
populateMotionRanges(InputDeviceInfo & info) const109 void 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 
tryAddRawMotionRange(InputDeviceInfo & deviceInfo,int32_t androidAxis,int32_t evdevAxis) const134 void 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 
reset()145 void CapturedTouchpadEventConverter::reset() {
146     mCursorButtonAccumulator.reset(mDeviceContext);
147     mDownTime = 0;
148     mPointerIdsInUse.reset();
149     mPointerIdForSlotNumber.clear();
150 }
151 
process(const RawEvent & rawEvent)152 std::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 
sync(nsecs_t when,nsecs_t readTime)164 std::list<NotifyArgs> CapturedTouchpadEventConverter::sync(nsecs_t when, nsecs_t readTime) {
165     std::list<NotifyArgs> out;
166     std::vector<PointerCoords> coords;
167     std::vector<PointerProperties> properties;
168     std::map<size_t, size_t> coordsIndexForSlotNumber;
169 
170     // For all the touches that were already down, send a MOVE event with their updated coordinates.
171     // A convention of the MotionEvent API is that pointer coordinates in UP events match the
172     // pointer's coordinates from the previous MOVE, so we still include touches here even if
173     // they've been lifted in this evdev frame.
174     if (!mPointerIdForSlotNumber.empty()) {
175         for (const auto [slotNumber, pointerId] : mPointerIdForSlotNumber) {
176             // Note that we don't check whether the touch has actually moved — it's rare for a touch
177             // to stay perfectly still between frames, and if it does the worst that can happen is
178             // an extra MOVE event, so it's not worth the overhead of checking for changes.
179             coordsIndexForSlotNumber[slotNumber] = coords.size();
180             coords.push_back(makePointerCoordsForSlot(mMotionAccumulator.getSlot(slotNumber)));
181             properties.push_back({.id = pointerId, .toolType = ToolType::FINGER});
182         }
183         out.push_back(
184                 makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_MOVE, coords, properties));
185     }
186 
187     std::vector<size_t> upSlots, downSlots;
188     for (size_t i = 0; i < mMotionAccumulator.getSlotCount(); i++) {
189         const MultiTouchMotionAccumulator::Slot& slot = mMotionAccumulator.getSlot(i);
190         // Some touchpads continue to report contacts even after they've identified them as palms.
191         // We don't currently have a way to mark these as palms when reporting to apps, so don't
192         // report them at all.
193         const bool isInUse = slot.isInUse() && slot.getToolType() != ToolType::PALM;
194         const bool wasInUse = mPointerIdForSlotNumber.find(i) != mPointerIdForSlotNumber.end();
195         if (isInUse && !wasInUse) {
196             downSlots.push_back(i);
197         } else if (!isInUse && wasInUse) {
198             upSlots.push_back(i);
199         }
200     }
201 
202     // Send BUTTON_RELEASE events. (This has to happen before any UP events to avoid sending
203     // BUTTON_RELEASE events without any pointers.)
204     uint32_t newButtonState;
205     if (coords.size() - upSlots.size() + downSlots.size() == 0) {
206         // If there won't be any pointers down after this evdev sync, we won't be able to send
207         // button updates on their own, as motion events without pointers are invalid. To avoid
208         // erroneously reporting buttons being held for long periods, send BUTTON_RELEASE events for
209         // all pressed buttons when the last pointer is lifted.
210         //
211         // This also prevents us from sending BUTTON_PRESS events too early in the case of touchpads
212         // which report a button press one evdev sync before reporting a touch going down.
213         newButtonState = 0;
214     } else {
215         newButtonState = mCursorButtonAccumulator.getButtonState();
216     }
217     for (uint32_t button = 1; button <= AMOTION_EVENT_BUTTON_FORWARD; button <<= 1) {
218         if (!(newButtonState & button) && mButtonState & button) {
219             mButtonState &= ~button;
220             out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_RELEASE,
221                                          coords, properties, /*actionButton=*/button));
222         }
223     }
224 
225     // For any touches that were lifted, send UP or POINTER_UP events.
226     for (size_t slotNumber : upSlots) {
227         const size_t indexToRemove = coordsIndexForSlotNumber.at(slotNumber);
228         const bool cancel = mMotionAccumulator.getSlot(slotNumber).getToolType() == ToolType::PALM;
229         int32_t action;
230         if (coords.size() == 1) {
231             action = cancel ? AMOTION_EVENT_ACTION_CANCEL : AMOTION_EVENT_ACTION_UP;
232         } else {
233             action = actionWithIndex(AMOTION_EVENT_ACTION_POINTER_UP, indexToRemove);
234         }
235         out.push_back(makeMotionArgs(when, readTime, action, coords, properties, /*actionButton=*/0,
236                                      /*flags=*/cancel ? AMOTION_EVENT_FLAG_CANCELED : 0));
237 
238         freePointerIdForSlot(slotNumber);
239         coords.erase(coords.begin() + indexToRemove);
240         properties.erase(properties.begin() + indexToRemove);
241         // Now that we've removed some coords and properties, we might have to update the slot
242         // number to coords index mapping.
243         coordsIndexForSlotNumber.erase(slotNumber);
244         for (auto& [_, index] : coordsIndexForSlotNumber) {
245             if (index > indexToRemove) {
246                 index--;
247             }
248         }
249     }
250 
251     // For new touches, send DOWN or POINTER_DOWN events.
252     for (size_t slotNumber : downSlots) {
253         const size_t coordsIndex = coords.size();
254         const int32_t action = coords.empty()
255                 ? AMOTION_EVENT_ACTION_DOWN
256                 : actionWithIndex(AMOTION_EVENT_ACTION_POINTER_DOWN, coordsIndex);
257 
258         coordsIndexForSlotNumber[slotNumber] = coordsIndex;
259         coords.push_back(makePointerCoordsForSlot(mMotionAccumulator.getSlot(slotNumber)));
260         properties.push_back(
261                 {.id = allocatePointerIdToSlot(slotNumber), .toolType = ToolType::FINGER});
262 
263         out.push_back(makeMotionArgs(when, readTime, action, coords, properties));
264     }
265 
266     for (uint32_t button = 1; button <= AMOTION_EVENT_BUTTON_FORWARD; button <<= 1) {
267         if (newButtonState & button && !(mButtonState & button)) {
268             mButtonState |= button;
269             out.push_back(makeMotionArgs(when, readTime, AMOTION_EVENT_ACTION_BUTTON_PRESS, coords,
270                                          properties, /*actionButton=*/button));
271         }
272     }
273     return out;
274 }
275 
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)276 NotifyMotionArgs CapturedTouchpadEventConverter::makeMotionArgs(
277         nsecs_t when, nsecs_t readTime, int32_t action, const std::vector<PointerCoords>& coords,
278         const std::vector<PointerProperties>& properties, int32_t actionButton, int32_t flags) {
279     LOG_ALWAYS_FATAL_IF(coords.size() != properties.size(),
280                         "Mismatched coords and properties arrays.");
281     return NotifyMotionArgs(mReaderContext.getNextId(), when, readTime, mDeviceId, SOURCE,
282                             ADISPLAY_ID_NONE, /*policyFlags=*/POLICY_FLAG_WAKE, action,
283                             /*actionButton=*/actionButton, flags,
284                             mReaderContext.getGlobalMetaState(), mButtonState,
285                             MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, coords.size(),
286                             properties.data(), coords.data(), /*xPrecision=*/1.0f,
287                             /*yPrecision=*/1.0f, AMOTION_EVENT_INVALID_CURSOR_POSITION,
288                             AMOTION_EVENT_INVALID_CURSOR_POSITION, mDownTime, /*videoFrames=*/{});
289 }
290 
makePointerCoordsForSlot(const MultiTouchMotionAccumulator::Slot & slot) const291 PointerCoords CapturedTouchpadEventConverter::makePointerCoordsForSlot(
292         const MultiTouchMotionAccumulator::Slot& slot) const {
293     PointerCoords coords;
294     coords.clear();
295     coords.setAxisValue(AMOTION_EVENT_AXIS_X, slot.getX());
296     coords.setAxisValue(AMOTION_EVENT_AXIS_Y, slot.getY());
297     coords.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MAJOR, slot.getTouchMajor());
298     coords.setAxisValue(AMOTION_EVENT_AXIS_TOUCH_MINOR, slot.getTouchMinor());
299     coords.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MAJOR, slot.getToolMajor());
300     coords.setAxisValue(AMOTION_EVENT_AXIS_TOOL_MINOR, slot.getToolMinor());
301     coords.setAxisValue(AMOTION_EVENT_AXIS_ORIENTATION, slot.getOrientation() * mOrientationScale);
302     coords.setAxisValue(AMOTION_EVENT_AXIS_PRESSURE, slot.getPressure() * mPressureScale);
303     float size = 0;
304     // TODO(b/275369880): support touch.size.calibration and .isSummed properties when captured.
305     if (mHasTouchMajor) {
306         size = mHasTouchMinor ? (slot.getTouchMajor() + slot.getTouchMinor()) / 2
307                               : slot.getTouchMajor();
308     } else if (mHasToolMajor) {
309         size = mHasToolMinor ? (slot.getToolMajor() + slot.getToolMinor()) / 2
310                              : slot.getToolMajor();
311     }
312     coords.setAxisValue(AMOTION_EVENT_AXIS_SIZE, size * mSizeScale);
313     return coords;
314 }
315 
allocatePointerIdToSlot(size_t slotNumber)316 int32_t CapturedTouchpadEventConverter::allocatePointerIdToSlot(size_t slotNumber) {
317     const int32_t pointerId = firstUnmarkedBit(mPointerIdsInUse);
318     mPointerIdsInUse.set(pointerId);
319     mPointerIdForSlotNumber[slotNumber] = pointerId;
320     return pointerId;
321 }
322 
freePointerIdForSlot(size_t slotNumber)323 void CapturedTouchpadEventConverter::freePointerIdForSlot(size_t slotNumber) {
324     mPointerIdsInUse.reset(mPointerIdForSlotNumber.at(slotNumber));
325     mPointerIdForSlotNumber.erase(slotNumber);
326 }
327 
328 } // namespace android
329