| /* | |
| * 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 "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; | |
| // --- MultiTouchMotionAccumulator --- | |
| MultiTouchMotionAccumulator::MultiTouchMotionAccumulator() | |
| : mCurrentSlot(-1), | |
| mSlots(nullptr), | |
| mSlotCount(0), | |
| mUsingSlotsProtocol(false), | |
| mHaveStylus(false) {} | |
| MultiTouchMotionAccumulator::~MultiTouchMotionAccumulator() { | |
| delete[] mSlots; | |
| } | |
| void MultiTouchMotionAccumulator::configure(InputDeviceContext& deviceContext, size_t slotCount, | |
| bool usingSlotsProtocol) { | |
| mSlotCount = slotCount; | |
| mUsingSlotsProtocol = usingSlotsProtocol; | |
| mHaveStylus = deviceContext.hasAbsoluteAxis(ABS_MT_TOOL_TYPE); | |
| delete[] mSlots; | |
| mSlots = new Slot[slotCount]; | |
| } | |
| void MultiTouchMotionAccumulator::reset(InputDeviceContext& deviceContext) { | |
| // Unfortunately there is no way to read the initial contents of the slots. | |
| // So when we reset the accumulator, we must assume they are all zeroes. | |
| if (mUsingSlotsProtocol) { | |
| // Query the driver for the current slot index and use it as the initial slot | |
| // before we start reading events from the device. It is possible that the | |
| // current slot index will not be the same as it was when the first event was | |
| // written into the evdev buffer, which means the input mapper could start | |
| // out of sync with the initial state of the events in the evdev buffer. | |
| // In the extremely unlikely case that this happens, the data from | |
| // two slots will be confused until the next ABS_MT_SLOT event is received. | |
| // This can cause the touch point to "jump", but at least there will be | |
| // no stuck touches. | |
| int32_t initialSlot; | |
| status_t status = deviceContext.getAbsoluteAxisValue(ABS_MT_SLOT, &initialSlot); | |
| if (status) { | |
| ALOGD("Could not retrieve current multitouch slot index. status=%d", status); | |
| initialSlot = -1; | |
| } | |
| clearSlots(initialSlot); | |
| } else { | |
| clearSlots(-1); | |
| } | |
| } | |
| void MultiTouchMotionAccumulator::clearSlots(int32_t initialSlot) { | |
| if (mSlots) { | |
| for (size_t i = 0; i < mSlotCount; i++) { | |
| mSlots[i].clear(); | |
| } | |
| } | |
| mCurrentSlot = initialSlot; | |
| } | |
| void MultiTouchMotionAccumulator::process(const RawEvent* rawEvent) { | |
| if (rawEvent->type == EV_ABS) { | |
| bool newSlot = false; | |
| if (mUsingSlotsProtocol) { | |
| if (rawEvent->code == ABS_MT_SLOT) { | |
| mCurrentSlot = rawEvent->value; | |
| newSlot = true; | |
| } | |
| } else if (mCurrentSlot < 0) { | |
| mCurrentSlot = 0; | |
| } | |
| if (mCurrentSlot < 0 || size_t(mCurrentSlot) >= mSlotCount) { | |
| if (DEBUG_POINTERS) { | |
| if (newSlot) { | |
| ALOGW("MultiTouch device emitted invalid slot index %d but it " | |
| "should be between 0 and %zd; ignoring this slot.", | |
| mCurrentSlot, mSlotCount - 1); | |
| } | |
| } | |
| } else { | |
| Slot* slot = &mSlots[mCurrentSlot]; | |
| // If mUsingSlotsProtocol is true, it means the raw pointer has axis info of | |
| // ABS_MT_TRACKING_ID and ABS_MT_SLOT, so driver should send a valid trackingId while | |
| // updating the slot. | |
| if (!mUsingSlotsProtocol) { | |
| slot->mInUse = true; | |
| } | |
| switch (rawEvent->code) { | |
| case ABS_MT_POSITION_X: | |
| slot->mAbsMTPositionX = rawEvent->value; | |
| warnIfNotInUse(*rawEvent, *slot); | |
| break; | |
| case ABS_MT_POSITION_Y: | |
| slot->mAbsMTPositionY = rawEvent->value; | |
| warnIfNotInUse(*rawEvent, *slot); | |
| break; | |
| case ABS_MT_TOUCH_MAJOR: | |
| slot->mAbsMTTouchMajor = rawEvent->value; | |
| break; | |
| case ABS_MT_TOUCH_MINOR: | |
| slot->mAbsMTTouchMinor = rawEvent->value; | |
| slot->mHaveAbsMTTouchMinor = true; | |
| break; | |
| case ABS_MT_WIDTH_MAJOR: | |
| slot->mAbsMTWidthMajor = rawEvent->value; | |
| break; | |
| case ABS_MT_WIDTH_MINOR: | |
| slot->mAbsMTWidthMinor = rawEvent->value; | |
| slot->mHaveAbsMTWidthMinor = true; | |
| break; | |
| case ABS_MT_ORIENTATION: | |
| slot->mAbsMTOrientation = rawEvent->value; | |
| break; | |
| case ABS_MT_TRACKING_ID: | |
| if (mUsingSlotsProtocol && rawEvent->value < 0) { | |
| // The slot is no longer in use but it retains its previous contents, | |
| // which may be reused for subsequent touches. | |
| slot->mInUse = false; | |
| } else { | |
| slot->mInUse = true; | |
| slot->mAbsMTTrackingId = rawEvent->value; | |
| } | |
| break; | |
| case ABS_MT_PRESSURE: | |
| slot->mAbsMTPressure = rawEvent->value; | |
| break; | |
| case ABS_MT_DISTANCE: | |
| slot->mAbsMTDistance = rawEvent->value; | |
| break; | |
| case ABS_MT_TOOL_TYPE: | |
| slot->mAbsMTToolType = rawEvent->value; | |
| slot->mHaveAbsMTToolType = true; | |
| break; | |
| } | |
| } | |
| } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_MT_REPORT) { | |
| // MultiTouch Sync: The driver has returned all data for *one* of the pointers. | |
| mCurrentSlot += 1; | |
| } | |
| } | |
| void MultiTouchMotionAccumulator::finishSync() { | |
| if (!mUsingSlotsProtocol) { | |
| clearSlots(-1); | |
| } | |
| } | |
| bool MultiTouchMotionAccumulator::hasStylus() const { | |
| return mHaveStylus; | |
| } | |
| void MultiTouchMotionAccumulator::warnIfNotInUse(const RawEvent& event, const Slot& slot) { | |
| if (!slot.mInUse) { | |
| ALOGW("Received unexpected event (0x%0x, 0x%0x) for slot %i with tracking id %i", | |
| event.code, event.value, mCurrentSlot, slot.mAbsMTTrackingId); | |
| } | |
| } | |
| // --- MultiTouchMotionAccumulator::Slot --- | |
| MultiTouchMotionAccumulator::Slot::Slot() { | |
| clear(); | |
| } | |
| void MultiTouchMotionAccumulator::Slot::clear() { | |
| mInUse = false; | |
| mHaveAbsMTTouchMinor = false; | |
| mHaveAbsMTWidthMinor = false; | |
| mHaveAbsMTToolType = false; | |
| mAbsMTPositionX = 0; | |
| mAbsMTPositionY = 0; | |
| mAbsMTTouchMajor = 0; | |
| mAbsMTTouchMinor = 0; | |
| mAbsMTWidthMajor = 0; | |
| mAbsMTWidthMinor = 0; | |
| mAbsMTOrientation = 0; | |
| mAbsMTTrackingId = -1; | |
| mAbsMTPressure = 0; | |
| mAbsMTDistance = 0; | |
| mAbsMTToolType = 0; | |
| } | |
| int32_t MultiTouchMotionAccumulator::Slot::getToolType() const { | |
| if (mHaveAbsMTToolType) { | |
| switch (mAbsMTToolType) { | |
| case MT_TOOL_FINGER: | |
| return AMOTION_EVENT_TOOL_TYPE_FINGER; | |
| case MT_TOOL_PEN: | |
| return AMOTION_EVENT_TOOL_TYPE_STYLUS; | |
| case MT_TOOL_PALM: | |
| return AMOTION_EVENT_TOOL_TYPE_PALM; | |
| } | |
| } | |
| return AMOTION_EVENT_TOOL_TYPE_UNKNOWN; | |
| } | |
| // --- MultiTouchInputMapper --- | |
| MultiTouchInputMapper::MultiTouchInputMapper(InputDeviceContext& deviceContext) | |
| : TouchInputMapper(deviceContext) {} | |
| MultiTouchInputMapper::~MultiTouchInputMapper() {} | |
| void MultiTouchInputMapper::reset(nsecs_t when) { | |
| mMultiTouchMotionAccumulator.reset(getDeviceContext()); | |
| mPointerIdBits.clear(); | |
| TouchInputMapper::reset(when); | |
| } | |
| void MultiTouchInputMapper::process(const RawEvent* rawEvent) { | |
| TouchInputMapper::process(rawEvent); | |
| mMultiTouchMotionAccumulator.process(rawEvent); | |
| } | |
| 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 %d 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; | |
| } | |
| } | |
| 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, | |
| true /*usingSlotsProtocol*/); | |
| } else { | |
| mMultiTouchMotionAccumulator.configure(getDeviceContext(), MAX_POINTERS, | |
| false /*usingSlotsProtocol*/); | |
| } | |
| } | |
| bool MultiTouchInputMapper::hasStylus() const { | |
| return mMultiTouchMotionAccumulator.hasStylus() || mTouchButtonAccumulator.hasStylus(); | |
| } | |
| } // namespace android |