| /* | |
| * Copyright (C) 2019 The Android Open Source Project | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| #include "../Macros.h" | |
| #include <android/sysprop/InputProperties.sysprop.h> | |
| #include "MultiTouchInputMapper.h" | |
| namespace android { | |
| // --- Constants --- | |
| // Maximum number of slots supported when using the slot-based Multitouch Protocol B. | |
| static constexpr size_t MAX_SLOTS = 32; | |
| // --- MultiTouchInputMapper --- | |
| MultiTouchInputMapper::MultiTouchInputMapper(InputDeviceContext& deviceContext) | |
| : TouchInputMapper(deviceContext) {} | |
| MultiTouchInputMapper::~MultiTouchInputMapper() {} | |
| std::list<NotifyArgs> MultiTouchInputMapper::reset(nsecs_t when) { | |
| // The evdev multi-touch protocol does not allow userspace applications to query the initial or | |
| // current state of the pointers at any time. This means if we clear our accumulated state when | |
| // resetting the input mapper, there's no way to rebuild the full initial state of the pointers. | |
| // We can only wait for updates to all the pointers and axes. Rather than clearing the state and | |
| // rebuilding the state from scratch, we work around this kernel API limitation by never | |
| // fully clearing any state specific to the multi-touch protocol. | |
| return TouchInputMapper::reset(when); | |
| } | |
| std::list<NotifyArgs> MultiTouchInputMapper::process(const RawEvent* rawEvent) { | |
| std::list<NotifyArgs> out = TouchInputMapper::process(rawEvent); | |
| mMultiTouchMotionAccumulator.process(rawEvent); | |
| return out; | |
| } | |
| std::optional<int32_t> MultiTouchInputMapper::getActiveBitId( | |
| const MultiTouchMotionAccumulator::Slot& inSlot) { | |
| if (mHavePointerIds) { | |
| int32_t trackingId = inSlot.getTrackingId(); | |
| for (BitSet32 idBits(mPointerIdBits); !idBits.isEmpty();) { | |
| int32_t n = idBits.clearFirstMarkedBit(); | |
| if (mPointerTrackingIdMap[n] == trackingId) { | |
| return std::make_optional(n); | |
| } | |
| } | |
| } | |
| return std::nullopt; | |
| } | |
| void MultiTouchInputMapper::syncTouch(nsecs_t when, RawState* outState) { | |
| size_t inCount = mMultiTouchMotionAccumulator.getSlotCount(); | |
| size_t outCount = 0; | |
| BitSet32 newPointerIdBits; | |
| mHavePointerIds = true; | |
| for (size_t inIndex = 0; inIndex < inCount; inIndex++) { | |
| const MultiTouchMotionAccumulator::Slot& inSlot = | |
| mMultiTouchMotionAccumulator.getSlot(inIndex); | |
| if (!inSlot.isInUse()) { | |
| continue; | |
| } | |
| if (inSlot.getToolType() == AMOTION_EVENT_TOOL_TYPE_PALM) { | |
| std::optional<int32_t> id = getActiveBitId(inSlot); | |
| if (id) { | |
| outState->rawPointerData.canceledIdBits.markBit(id.value()); | |
| } | |
| if (DEBUG_POINTERS) { | |
| ALOGI("Stop processing slot %zu for it received a palm event from device %s", | |
| inIndex, getDeviceName().c_str()); | |
| } | |
| continue; | |
| } | |
| if (outCount >= MAX_POINTERS) { | |
| if (DEBUG_POINTERS) { | |
| ALOGD("MultiTouch device %s emitted more than maximum of %zu pointers; " | |
| "ignoring the rest.", | |
| getDeviceName().c_str(), MAX_POINTERS); | |
| } | |
| break; // too many fingers! | |
| } | |
| RawPointerData::Pointer& outPointer = outState->rawPointerData.pointers[outCount]; | |
| outPointer.x = inSlot.getX(); | |
| outPointer.y = inSlot.getY(); | |
| outPointer.pressure = inSlot.getPressure(); | |
| outPointer.touchMajor = inSlot.getTouchMajor(); | |
| outPointer.touchMinor = inSlot.getTouchMinor(); | |
| outPointer.toolMajor = inSlot.getToolMajor(); | |
| outPointer.toolMinor = inSlot.getToolMinor(); | |
| outPointer.orientation = inSlot.getOrientation(); | |
| outPointer.distance = inSlot.getDistance(); | |
| outPointer.tiltX = 0; | |
| outPointer.tiltY = 0; | |
| outPointer.toolType = inSlot.getToolType(); | |
| if (outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) { | |
| outPointer.toolType = mTouchButtonAccumulator.getToolType(); | |
| if (outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) { | |
| outPointer.toolType = AMOTION_EVENT_TOOL_TYPE_FINGER; | |
| } | |
| } else if (outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS && !mStylusMtToolSeen) { | |
| mStylusMtToolSeen = true; | |
| // The multi-touch device produced a stylus event with MT_TOOL_PEN. Dynamically | |
| // re-configure this input device so that we add SOURCE_STYLUS if we haven't already. | |
| // This is to cover the case where we cannot reliably detect whether a multi-touch | |
| // device will ever produce stylus events when it is initially being configured. | |
| if (!isFromSource(mSource, AINPUT_SOURCE_STYLUS)) { | |
| // Add the stylus source immediately so that it is included in any events generated | |
| // before we have a chance to re-configure the device. | |
| mSource |= AINPUT_SOURCE_STYLUS; | |
| bumpGeneration(); | |
| } | |
| } | |
| if (shouldSimulateStylusWithTouch() && | |
| outPointer.toolType == AMOTION_EVENT_TOOL_TYPE_FINGER) { | |
| outPointer.toolType = AMOTION_EVENT_TOOL_TYPE_STYLUS; | |
| } | |
| bool isHovering = mTouchButtonAccumulator.getToolType() != AMOTION_EVENT_TOOL_TYPE_MOUSE && | |
| (mTouchButtonAccumulator.isHovering() || | |
| (mRawPointerAxes.pressure.valid && inSlot.getPressure() <= 0)); | |
| outPointer.isHovering = isHovering; | |
| // Assign pointer id using tracking id if available. | |
| if (mHavePointerIds) { | |
| int32_t trackingId = inSlot.getTrackingId(); | |
| int32_t id = -1; | |
| if (trackingId >= 0) { | |
| for (BitSet32 idBits(mPointerIdBits); !idBits.isEmpty();) { | |
| uint32_t n = idBits.clearFirstMarkedBit(); | |
| if (mPointerTrackingIdMap[n] == trackingId) { | |
| id = n; | |
| } | |
| } | |
| if (id < 0 && !mPointerIdBits.isFull()) { | |
| id = mPointerIdBits.markFirstUnmarkedBit(); | |
| mPointerTrackingIdMap[id] = trackingId; | |
| } | |
| } | |
| if (id < 0) { | |
| mHavePointerIds = false; | |
| outState->rawPointerData.clearIdBits(); | |
| newPointerIdBits.clear(); | |
| } else { | |
| outPointer.id = id; | |
| outState->rawPointerData.idToIndex[id] = outCount; | |
| outState->rawPointerData.markIdBit(id, isHovering); | |
| newPointerIdBits.markBit(id); | |
| } | |
| } | |
| outCount += 1; | |
| } | |
| outState->rawPointerData.pointerCount = outCount; | |
| mPointerIdBits = newPointerIdBits; | |
| mMultiTouchMotionAccumulator.finishSync(); | |
| } | |
| void MultiTouchInputMapper::configureRawPointerAxes() { | |
| TouchInputMapper::configureRawPointerAxes(); | |
| getAbsoluteAxisInfo(ABS_MT_POSITION_X, &mRawPointerAxes.x); | |
| getAbsoluteAxisInfo(ABS_MT_POSITION_Y, &mRawPointerAxes.y); | |
| getAbsoluteAxisInfo(ABS_MT_TOUCH_MAJOR, &mRawPointerAxes.touchMajor); | |
| getAbsoluteAxisInfo(ABS_MT_TOUCH_MINOR, &mRawPointerAxes.touchMinor); | |
| getAbsoluteAxisInfo(ABS_MT_WIDTH_MAJOR, &mRawPointerAxes.toolMajor); | |
| getAbsoluteAxisInfo(ABS_MT_WIDTH_MINOR, &mRawPointerAxes.toolMinor); | |
| getAbsoluteAxisInfo(ABS_MT_ORIENTATION, &mRawPointerAxes.orientation); | |
| getAbsoluteAxisInfo(ABS_MT_PRESSURE, &mRawPointerAxes.pressure); | |
| getAbsoluteAxisInfo(ABS_MT_DISTANCE, &mRawPointerAxes.distance); | |
| getAbsoluteAxisInfo(ABS_MT_TRACKING_ID, &mRawPointerAxes.trackingId); | |
| getAbsoluteAxisInfo(ABS_MT_SLOT, &mRawPointerAxes.slot); | |
| if (mRawPointerAxes.trackingId.valid && mRawPointerAxes.slot.valid && | |
| mRawPointerAxes.slot.minValue == 0 && mRawPointerAxes.slot.maxValue > 0) { | |
| size_t slotCount = mRawPointerAxes.slot.maxValue + 1; | |
| if (slotCount > MAX_SLOTS) { | |
| ALOGW("MultiTouch Device %s reported %zu slots but the framework " | |
| "only supports a maximum of %zu slots at this time.", | |
| getDeviceName().c_str(), slotCount, MAX_SLOTS); | |
| slotCount = MAX_SLOTS; | |
| } | |
| mMultiTouchMotionAccumulator.configure(getDeviceContext(), slotCount, | |
| /*usingSlotsProtocol=*/true); | |
| } else { | |
| mMultiTouchMotionAccumulator.configure(getDeviceContext(), MAX_POINTERS, | |
| /*usingSlotsProtocol=*/false); | |
| } | |
| } | |
| bool MultiTouchInputMapper::hasStylus() const { | |
| return mStylusMtToolSeen || mTouchButtonAccumulator.hasStylus() || | |
| shouldSimulateStylusWithTouch(); | |
| } | |
| bool MultiTouchInputMapper::shouldSimulateStylusWithTouch() const { | |
| static const bool SIMULATE_STYLUS_WITH_TOUCH = | |
| sysprop::InputProperties::simulate_stylus_with_touch().value_or(false); | |
| return SIMULATE_STYLUS_WITH_TOUCH && | |
| mParameters.deviceType == Parameters::DeviceType::TOUCH_SCREEN; | |
| } | |
| } // namespace android |