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> |
| 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 | |
| 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> |
| 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 | |
| 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 | |
| 86 | 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 | |
| 109 | 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 | |
| 134 | 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 | |
| 145 | void CapturedTouchpadEventConverter::reset() { |
| 146 | mCursorButtonAccumulator.reset(mDeviceContext); |
| 147 | mDownTime = 0; |
| 148 | mPointerIdsInUse.reset(); |
| 149 | mPointerIdForSlotNumber.clear(); |
| 150 | } |
| 151 | |
| 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 | |
| 164 | std::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 | |
| 250 | NotifyMotionArgs 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 | |
| 265 | PointerCoords 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 | |
| 290 | int32_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 | |
| 297 | void CapturedTouchpadEventConverter::freePointerIdForSlot(size_t slotNumber) { |
| 298 | mPointerIdsInUse.reset(mPointerIdForSlotNumber.at(slotNumber)); |
| 299 | mPointerIdForSlotNumber.erase(slotNumber); |
| 300 | } |
| 301 | |
| 302 | } // namespace android |