Harry Cutts | bb24e27 | 2023-03-21 10:49:47 +0000 | [diff] [blame] | 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> |
Harry Cutts | bb24e27 | 2023-03-21 10:49:47 +0000 | [diff] [blame] | 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 | |
| 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> |
| 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 | |
| 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 | |
| 85 | 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 | |
| 108 | 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 | |
| 133 | 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 | |
| 144 | void CapturedTouchpadEventConverter::reset() { |
| 145 | mCursorButtonAccumulator.reset(mDeviceContext); |
| 146 | mDownTime = 0; |
| 147 | mPointerIdsInUse.reset(); |
| 148 | mPointerIdForSlotNumber.clear(); |
| 149 | } |
| 150 | |
| 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 | |
Harry Cutts | 71953c2 | 2024-06-03 12:54:40 +0000 | [diff] [blame] | 158 | mCursorButtonAccumulator.process(rawEvent); |
| 159 | mMotionAccumulator.process(rawEvent); |
Harry Cutts | bb24e27 | 2023-03-21 10:49:47 +0000 | [diff] [blame] | 160 | return out; |
| 161 | } |
| 162 | |
| 163 | std::list<NotifyArgs> CapturedTouchpadEventConverter::sync(nsecs_t when, nsecs_t readTime) { |
Harry Cutts | bb24e27 | 2023-03-21 10:49:47 +0000 | [diff] [blame] | 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++) { |
Harry Cutts | 892bce5 | 2023-04-12 16:30:09 +0000 | [diff] [blame] | 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; |
Harry Cutts | bb24e27 | 2023-03-21 10:49:47 +0000 | [diff] [blame] | 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 | |
Harry Cutts | 23c8dff | 2023-05-10 17:39:59 +0000 | [diff] [blame] | 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 | |
Harry Cutts | bb24e27 | 2023-03-21 10:49:47 +0000 | [diff] [blame] | 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); |
Harry Cutts | 892bce5 | 2023-04-12 16:30:09 +0000 | [diff] [blame] | 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)); |
Harry Cutts | bb24e27 | 2023-03-21 10:49:47 +0000 | [diff] [blame] | 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 | |
Harry Cutts | bb24e27 | 2023-03-21 10:49:47 +0000 | [diff] [blame] | 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)); |
Harry Cutts | bb24e27 | 2023-03-21 10:49:47 +0000 | [diff] [blame] | 270 | } |
| 271 | } |
| 272 | return out; |
| 273 | } |
| 274 | |
| 275 | NotifyMotionArgs CapturedTouchpadEventConverter::makeMotionArgs( |
| 276 | nsecs_t when, nsecs_t readTime, int32_t action, const std::vector<PointerCoords>& coords, |
Harry Cutts | 892bce5 | 2023-04-12 16:30:09 +0000 | [diff] [blame] | 277 | const std::vector<PointerProperties>& properties, int32_t actionButton, int32_t flags) { |
Harry Cutts | bb24e27 | 2023-03-21 10:49:47 +0000 | [diff] [blame] | 278 | LOG_ALWAYS_FATAL_IF(coords.size() != properties.size(), |
| 279 | "Mismatched coords and properties arrays."); |
| 280 | return NotifyMotionArgs(mReaderContext.getNextId(), when, readTime, mDeviceId, SOURCE, |
Siarhei Vishniakou | cfbee53 | 2024-05-10 13:41:35 -0700 | [diff] [blame] | 281 | ui::LogicalDisplayId::INVALID, /*policyFlags=*/POLICY_FLAG_WAKE, action, |
Harry Cutts | 892bce5 | 2023-04-12 16:30:09 +0000 | [diff] [blame] | 282 | /*actionButton=*/actionButton, flags, |
Harry Cutts | bb24e27 | 2023-03-21 10:49:47 +0000 | [diff] [blame] | 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 | |
| 290 | 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 | |
| 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 | |
| 322 | void CapturedTouchpadEventConverter::freePointerIdForSlot(size_t slotNumber) { |
| 323 | mPointerIdsInUse.reset(mPointerIdForSlotNumber.at(slotNumber)); |
| 324 | mPointerIdForSlotNumber.erase(slotNumber); |
| 325 | } |
| 326 | |
| 327 | } // namespace android |