/* * 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 "DebugConfig.h" #include "input/InputDevice.h" #include "InputState.h" #include #include "InputDispatcher.h" namespace android::inputdispatcher { InputState::InputState(const IdGenerator& idGenerator) : mIdGenerator(idGenerator) {} InputState::~InputState() {} bool InputState::isHovering(DeviceId deviceId, uint32_t source, ui::LogicalDisplayId displayId) const { for (const MotionMemento& memento : mMotionMementos) { if (memento.deviceId == deviceId && memento.source == source && memento.displayId == displayId && memento.hovering) { return true; } } return false; } bool InputState::trackKey(const KeyEntry& entry, int32_t flags) { switch (entry.action) { case AKEY_EVENT_ACTION_UP: { if (entry.flags & AKEY_EVENT_FLAG_FALLBACK) { std::erase_if(mFallbackKeys, [&entry](const auto& item) { return item.second == entry.keyCode; }); } ssize_t index = findKeyMemento(entry); if (index >= 0) { mKeyMementos.erase(mKeyMementos.begin() + index); return true; } /* FIXME: We can't just drop the key up event because that prevents creating * popup windows that are automatically shown when a key is held and then * dismissed when the key is released. The problem is that the popup will * not have received the original key down, so the key up will be considered * to be inconsistent with its observed state. We could perhaps handle this * by synthesizing a key down but that will cause other problems. * * So for now, allow inconsistent key up events to be dispatched. * #if DEBUG_OUTBOUND_EVENT_DETAILS ALOGD("Dropping inconsistent key up event: deviceId=%d, source=%08x, " "keyCode=%d, scanCode=%d", entry.deviceId, entry.source, entry.keyCode, entry.scanCode); #endif return false; */ return true; } case AKEY_EVENT_ACTION_DOWN: { ssize_t index = findKeyMemento(entry); if (index >= 0) { mKeyMementos.erase(mKeyMementos.begin() + index); } addKeyMemento(entry, flags); return true; } default: return true; } } /** * Return: * true if the incoming event was correctly tracked, * false if the incoming event should be dropped. */ bool InputState::trackMotion(const MotionEntry& entry, int32_t flags) { // Don't track non-pointer events if (!isFromSource(entry.source, AINPUT_SOURCE_CLASS_POINTER)) { // This is a focus-dispatched event; we don't track its state. return true; } if (!input_flags::enable_multi_device_same_window_stream()) { if (!mMotionMementos.empty()) { const MotionMemento& lastMemento = mMotionMementos.back(); if (isStylusEvent(lastMemento.source, lastMemento.pointerProperties) && !isStylusEvent(entry.source, entry.pointerProperties)) { // We already have a stylus stream, and the new event is not from stylus. return false; } } } int32_t actionMasked = entry.action & AMOTION_EVENT_ACTION_MASK; switch (actionMasked) { case AMOTION_EVENT_ACTION_UP: case AMOTION_EVENT_ACTION_CANCEL: { ssize_t index = findMotionMemento(entry, /*hovering=*/false); if (index >= 0) { mMotionMementos.erase(mMotionMementos.begin() + index); return true; } return false; } case AMOTION_EVENT_ACTION_DOWN: { ssize_t index = findMotionMemento(entry, /*hovering=*/false); if (index >= 0) { mMotionMementos.erase(mMotionMementos.begin() + index); } addMotionMemento(entry, flags, /*hovering=*/false); return true; } case AMOTION_EVENT_ACTION_POINTER_UP: case AMOTION_EVENT_ACTION_POINTER_DOWN: case AMOTION_EVENT_ACTION_MOVE: { if (entry.source & AINPUT_SOURCE_CLASS_NAVIGATION) { // Trackballs can send MOVE events with a corresponding DOWN or UP. There's no need // to generate cancellation events for these since they're based in relative rather // than absolute units. return true; } ssize_t index = findMotionMemento(entry, /*hovering=*/false); if (entry.source & AINPUT_SOURCE_CLASS_JOYSTICK) { // Joysticks can send MOVE events without a corresponding DOWN or UP. Since all // joystick axes are normalized to [-1, 1] we can trust that 0 means it's neutral. // Any other value and we need to track the motion so we can send cancellation // events for anything generating fallback events (e.g. DPad keys for joystick // movements). if (index >= 0) { if (entry.pointerCoords[0].isEmpty()) { mMotionMementos.erase(mMotionMementos.begin() + index); } else { MotionMemento& memento = mMotionMementos[index]; memento.setPointers(entry); } } else if (!entry.pointerCoords[0].isEmpty()) { addMotionMemento(entry, flags, /*hovering=*/false); } // Joysticks and trackballs can send MOVE events without corresponding DOWN or UP. return true; } if (index >= 0) { MotionMemento& memento = mMotionMementos[index]; if (memento.firstNewPointerIdx < 0) { memento.setPointers(entry); return true; } } return false; } case AMOTION_EVENT_ACTION_HOVER_EXIT: { ssize_t index = findMotionMemento(entry, /*hovering=*/true); if (index >= 0) { mMotionMementos.erase(mMotionMementos.begin() + index); return true; } return false; } case AMOTION_EVENT_ACTION_HOVER_ENTER: case AMOTION_EVENT_ACTION_HOVER_MOVE: { ssize_t index = findMotionMemento(entry, /*hovering=*/true); if (index >= 0) { mMotionMementos.erase(mMotionMementos.begin() + index); } addMotionMemento(entry, flags, /*hovering=*/true); return true; } default: return true; } } std::optional, std::vector>> InputState::getPointersOfLastEvent(const MotionEntry& entry, bool hovering) const { ssize_t index = findMotionMemento(entry, hovering); if (index == -1) { return std::nullopt; } return std::make_pair(mMotionMementos[index].pointerProperties, mMotionMementos[index].pointerCoords); } ssize_t InputState::findKeyMemento(const KeyEntry& entry) const { for (size_t i = 0; i < mKeyMementos.size(); i++) { const KeyMemento& memento = mKeyMementos[i]; if (memento.deviceId == entry.deviceId && memento.source == entry.source && memento.displayId == entry.displayId && memento.keyCode == entry.keyCode && memento.scanCode == entry.scanCode) { return i; } } return -1; } ssize_t InputState::findMotionMemento(const MotionEntry& entry, bool hovering) const { for (size_t i = 0; i < mMotionMementos.size(); i++) { const MotionMemento& memento = mMotionMementos[i]; if (memento.deviceId == entry.deviceId && memento.source == entry.source && memento.displayId == entry.displayId && memento.hovering == hovering) { return i; } } return -1; } void InputState::addKeyMemento(const KeyEntry& entry, int32_t flags) { KeyMemento memento; memento.deviceId = entry.deviceId; memento.source = entry.source; memento.displayId = entry.displayId; memento.keyCode = entry.keyCode; memento.scanCode = entry.scanCode; memento.metaState = entry.metaState; memento.flags = flags; memento.downTime = entry.downTime; memento.policyFlags = entry.policyFlags; mKeyMementos.push_back(memento); } void InputState::addMotionMemento(const MotionEntry& entry, int32_t flags, bool hovering) { MotionMemento memento; memento.deviceId = entry.deviceId; memento.source = entry.source; memento.displayId = entry.displayId; memento.flags = flags; memento.xPrecision = entry.xPrecision; memento.yPrecision = entry.yPrecision; memento.xCursorPosition = entry.xCursorPosition; memento.yCursorPosition = entry.yCursorPosition; memento.downTime = entry.downTime; memento.setPointers(entry); memento.hovering = hovering; memento.policyFlags = entry.policyFlags; mMotionMementos.push_back(memento); } void InputState::MotionMemento::setPointers(const MotionEntry& entry) { pointerProperties.clear(); pointerCoords.clear(); for (uint32_t i = 0; i < entry.getPointerCount(); i++) { if (MotionEvent::getActionMasked(entry.action) == AMOTION_EVENT_ACTION_POINTER_UP) { // In POINTER_UP events, the pointer is leaving. Since the action is not stored, // this departing pointer should not be recorded. const uint8_t actionIndex = MotionEvent::getActionIndex(entry.action); if (i == actionIndex) { continue; } } pointerProperties.push_back(entry.pointerProperties[i]); pointerCoords.push_back(entry.pointerCoords[i]); } } void InputState::MotionMemento::mergePointerStateTo(MotionMemento& other) const { for (uint32_t i = 0; i < getPointerCount(); i++) { if (other.firstNewPointerIdx < 0) { other.firstNewPointerIdx = other.getPointerCount(); } other.pointerProperties.push_back(pointerProperties[i]); other.pointerCoords.push_back(pointerCoords[i]); } } size_t InputState::MotionMemento::getPointerCount() const { return pointerProperties.size(); } bool InputState::shouldCancelPreviousStream(const MotionEntry& motionEntry) const { if (!isFromSource(motionEntry.source, AINPUT_SOURCE_CLASS_POINTER)) { // This is a focus-dispatched event that should not affect the previous stream. return false; } // New MotionEntry pointer event is coming in. // If this is a new gesture, and it's from a different device, then, in general, we will cancel // the current gesture. // However, because stylus should be preferred over touch, we need to treat some cases in a // special way. if (mMotionMementos.empty()) { // There is no ongoing pointer gesture, so there is nothing to cancel return false; } const MotionMemento& lastMemento = mMotionMementos.back(); const int32_t actionMasked = MotionEvent::getActionMasked(motionEntry.action); // For compatibility, only one input device can be active at a time in the same window. if (lastMemento.deviceId == motionEntry.deviceId) { // In general, the same device should produce self-consistent streams so nothing needs to // be canceled. But there is one exception: // Sometimes ACTION_DOWN is received without a corresponding HOVER_EXIT. To account for // that, cancel the previous hovering stream if (actionMasked == AMOTION_EVENT_ACTION_DOWN && lastMemento.hovering) { return true; } // If the stream changes its source, just cancel the current gesture to be safe. It's // possible that the app isn't handling source changes properly if (motionEntry.source != lastMemento.source) { LOG(INFO) << "Canceling stream: last source was " << inputEventSourceToString(lastMemento.source) << " and new event is " << motionEntry; return true; } // If the injection is happening into two different displays, the same injected device id // could be going into both. And at this time, if mirroring is active, the same connection // would receive different events from each display. Since the TouchStates are per-display, // it's unlikely that those two streams would be consistent with each other. Therefore, // cancel the previous gesture if the display id changes. if (motionEntry.displayId != lastMemento.displayId) { LOG(INFO) << "Canceling stream: last displayId was " << lastMemento.displayId << " and new event is " << motionEntry; return true; } return false; } if (!input_flags::enable_multi_device_same_window_stream()) { if (isStylusEvent(lastMemento.source, lastMemento.pointerProperties)) { // A stylus is already active. if (isStylusEvent(motionEntry.source, motionEntry.pointerProperties) && actionMasked == AMOTION_EVENT_ACTION_DOWN) { // If this new event is from a different device, then cancel the old // stylus and allow the new stylus to take over, but only if it's going down. // Otherwise, they will start to race each other. return true; } // Keep the current stylus gesture. return false; } // Cancel the current gesture if this is a start of a new gesture from a new device. if (actionMasked == AMOTION_EVENT_ACTION_DOWN || actionMasked == AMOTION_EVENT_ACTION_HOVER_ENTER) { return true; } } // By default, don't cancel any events. return false; } std::unique_ptr InputState::cancelConflictingInputStream( const MotionEntry& motionEntry) { if (!shouldCancelPreviousStream(motionEntry)) { return {}; } const MotionMemento& memento = mMotionMementos.back(); // Cancel the last device stream std::unique_ptr cancelEntry = createCancelEntryForMemento(memento, motionEntry.eventTime); if (!trackMotion(*cancelEntry, cancelEntry->flags)) { LOG(FATAL) << "Generated inconsistent cancel event!"; } return cancelEntry; } std::unique_ptr InputState::createCancelEntryForMemento(const MotionMemento& memento, nsecs_t eventTime) const { const int32_t action = memento.hovering ? AMOTION_EVENT_ACTION_HOVER_EXIT : AMOTION_EVENT_ACTION_CANCEL; int32_t flags = memento.flags; if (action == AMOTION_EVENT_ACTION_CANCEL) { flags |= AMOTION_EVENT_FLAG_CANCELED; } return std::make_unique(mIdGenerator.nextId(), /*injectionState=*/nullptr, eventTime, memento.deviceId, memento.source, memento.displayId, memento.policyFlags, action, /*actionButton=*/0, flags, AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, memento.xPrecision, memento.yPrecision, memento.xCursorPosition, memento.yCursorPosition, memento.downTime, memento.pointerProperties, memento.pointerCoords); } std::vector> InputState::synthesizeCancelationEvents( nsecs_t currentTime, const CancelationOptions& options) { std::vector> events; for (KeyMemento& memento : mKeyMementos) { if (shouldCancelKey(memento, options)) { events.push_back( std::make_unique(mIdGenerator.nextId(), /*injectionState=*/nullptr, currentTime, memento.deviceId, memento.source, memento.displayId, memento.policyFlags, AKEY_EVENT_ACTION_UP, memento.flags | AKEY_EVENT_FLAG_CANCELED, memento.keyCode, memento.scanCode, memento.metaState, /*repeatCount=*/0, memento.downTime)); } } for (const MotionMemento& memento : mMotionMementos) { if (shouldCancelMotion(memento, options)) { if (options.pointerIds == std::nullopt) { events.push_back(createCancelEntryForMemento(memento, currentTime)); } else { std::vector> pointerCancelEvents = synthesizeCancelationEventsForPointers(memento, options.pointerIds.value(), currentTime); events.insert(events.end(), std::make_move_iterator(pointerCancelEvents.begin()), std::make_move_iterator(pointerCancelEvents.end())); } } } return events; } std::vector> InputState::synthesizePointerDownEvents( nsecs_t currentTime) { std::vector> events; for (MotionMemento& memento : mMotionMementos) { if (!isFromSource(memento.source, AINPUT_SOURCE_CLASS_POINTER)) { continue; } if (memento.firstNewPointerIdx < 0) { continue; } std::vector pointerProperties; std::vector pointerCoords; // We will deliver all pointers the target already knows about for (uint32_t i = 0; i < static_cast(memento.firstNewPointerIdx); i++) { pointerProperties.push_back(memento.pointerProperties[i]); pointerCoords.push_back(memento.pointerCoords[i]); } // We will send explicit events for all pointers the target doesn't know about for (uint32_t i = static_cast(memento.firstNewPointerIdx); i < memento.getPointerCount(); i++) { pointerProperties.push_back(memento.pointerProperties[i]); pointerCoords.push_back(memento.pointerCoords[i]); const size_t pointerCount = pointerProperties.size(); // Down only if the first pointer, pointer down otherwise const int32_t action = (pointerCount <= 1) ? AMOTION_EVENT_ACTION_DOWN : AMOTION_EVENT_ACTION_POINTER_DOWN | (i << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); events.push_back( std::make_unique(mIdGenerator.nextId(), /*injectionState=*/nullptr, currentTime, memento.deviceId, memento.source, memento.displayId, memento.policyFlags, action, /*actionButton=*/0, memento.flags, AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, memento.xPrecision, memento.yPrecision, memento.xCursorPosition, memento.yCursorPosition, memento.downTime, pointerProperties, pointerCoords)); } memento.firstNewPointerIdx = INVALID_POINTER_INDEX; } return events; } std::vector> InputState::synthesizeCancelationEventsForPointers( const MotionMemento& memento, std::bitset pointerIds, nsecs_t currentTime) { std::vector> events; std::vector canceledPointerIndices; for (uint32_t pointerIdx = 0; pointerIdx < memento.getPointerCount(); pointerIdx++) { uint32_t pointerId = uint32_t(memento.pointerProperties[pointerIdx].id); if (pointerIds.test(pointerId)) { canceledPointerIndices.push_back(pointerIdx); } } if (canceledPointerIndices.size() == memento.getPointerCount()) { // We are cancelling all pointers. events.emplace_back(createCancelEntryForMemento(memento, currentTime)); return events; } // If we aren't canceling all pointers, we need to generate ACTION_POINTER_UP with // FLAG_CANCELED for each of the canceled pointers. For each event, we must remove the // previously canceled pointers from PointerProperties and PointerCoords, and update // pointerCount appropriately. For convenience, sort the canceled pointer indices in // descending order so that we can just slide the remaining pointers to the beginning of // the array when a pointer is canceled. std::sort(canceledPointerIndices.begin(), canceledPointerIndices.end(), std::greater()); std::vector pointerProperties = memento.pointerProperties; std::vector pointerCoords = memento.pointerCoords; for (const uint32_t pointerIdx : canceledPointerIndices) { if (pointerProperties.size() <= 1) { LOG(FATAL) << "Unexpected code path for canceling all pointers!"; } const int32_t action = AMOTION_EVENT_ACTION_POINTER_UP | (pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT); events.push_back( std::make_unique(mIdGenerator.nextId(), /*injectionState=*/nullptr, currentTime, memento.deviceId, memento.source, memento.displayId, memento.policyFlags, action, /*actionButton=*/0, memento.flags | AMOTION_EVENT_FLAG_CANCELED, AMETA_NONE, /*buttonState=*/0, MotionClassification::NONE, AMOTION_EVENT_EDGE_FLAG_NONE, memento.xPrecision, memento.yPrecision, memento.xCursorPosition, memento.yCursorPosition, memento.downTime, pointerProperties, pointerCoords)); // Cleanup pointer information pointerProperties.erase(pointerProperties.begin() + pointerIdx); pointerCoords.erase(pointerCoords.begin() + pointerIdx); } return events; } void InputState::clear() { mKeyMementos.clear(); mMotionMementos.clear(); mFallbackKeys.clear(); } void InputState::mergePointerStateTo(InputState& other) { for (size_t i = 0; i < mMotionMementos.size(); i++) { MotionMemento& memento = mMotionMementos[i]; // Since we support split pointers we need to merge touch events // from the same source + device + screen. if (isFromSource(memento.source, AINPUT_SOURCE_CLASS_POINTER)) { bool merged = false; for (size_t j = 0; j < other.mMotionMementos.size(); j++) { MotionMemento& otherMemento = other.mMotionMementos[j]; if (memento.deviceId == otherMemento.deviceId && memento.source == otherMemento.source && memento.displayId == otherMemento.displayId) { memento.mergePointerStateTo(otherMemento); merged = true; break; } } if (!merged) { memento.firstNewPointerIdx = 0; other.mMotionMementos.push_back(memento); } } } } std::optional InputState::getFallbackKey(int32_t originalKeyCode) { auto it = mFallbackKeys.find(originalKeyCode); if (it == mFallbackKeys.end()) { return {}; } return it->second; } void InputState::setFallbackKey(int32_t originalKeyCode, int32_t fallbackKeyCode) { mFallbackKeys.insert_or_assign(originalKeyCode, fallbackKeyCode); } void InputState::removeFallbackKey(int32_t originalKeyCode) { mFallbackKeys.erase(originalKeyCode); } bool InputState::shouldCancelKey(const KeyMemento& memento, const CancelationOptions& options) { if (options.keyCode && memento.keyCode != options.keyCode.value()) { return false; } if (options.deviceId && memento.deviceId != options.deviceId.value()) { return false; } if (options.displayId && memento.displayId != options.displayId.value()) { return false; } switch (options.mode) { case CancelationOptions::Mode::CANCEL_ALL_EVENTS: case CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS: return true; case CancelationOptions::Mode::CANCEL_FALLBACK_EVENTS: return memento.flags & AKEY_EVENT_FLAG_FALLBACK; default: return false; } } bool InputState::shouldCancelMotion(const MotionMemento& memento, const CancelationOptions& options) { if (options.deviceId && memento.deviceId != options.deviceId.value()) { return false; } if (options.displayId && memento.displayId != options.displayId.value()) { return false; } switch (options.mode) { case CancelationOptions::Mode::CANCEL_ALL_EVENTS: return true; case CancelationOptions::Mode::CANCEL_POINTER_EVENTS: return memento.source & AINPUT_SOURCE_CLASS_POINTER; case CancelationOptions::Mode::CANCEL_NON_POINTER_EVENTS: return !(memento.source & AINPUT_SOURCE_CLASS_POINTER); default: return false; } } std::ostream& operator<<(std::ostream& out, const InputState& state) { if (!state.mMotionMementos.empty()) { out << "mMotionMementos: "; for (const InputState::MotionMemento& memento : state.mMotionMementos) { out << "{deviceId= " << memento.deviceId << ", hovering=" << memento.hovering << "}, "; } } return out; } } // namespace android::inputdispatcher