blob: 1fec9b7599783149d2cd627a9ddab75a693994de [file] [log] [blame]
/*
* 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 <cinttypes>
#include "InputDispatcher.h"
namespace android::inputdispatcher {
InputState::InputState(const IdGenerator& idGenerator) : mIdGenerator(idGenerator) {}
InputState::~InputState() {}
bool InputState::isHovering(DeviceId deviceId, uint32_t source, int32_t 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 (!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::pair<std::vector<PointerProperties>, std::vector<PointerCoords>>>
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 "
<< inputEventSourceToString(lastMemento.displayId) << " and new event is "
<< motionEntry;
return true;
}
return false;
}
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<EventEntry> InputState::cancelConflictingInputStream(
const MotionEntry& motionEntry) {
if (!shouldCancelPreviousStream(motionEntry)) {
return {};
}
const MotionMemento& memento = mMotionMementos.back();
// Cancel the last device stream
std::unique_ptr<MotionEntry> cancelEntry =
createCancelEntryForMemento(memento, motionEntry.eventTime);
if (!trackMotion(*cancelEntry, cancelEntry->flags)) {
LOG(FATAL) << "Generated inconsistent cancel event!";
}
return cancelEntry;
}
std::unique_ptr<MotionEntry> 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<MotionEntry>(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<std::unique_ptr<EventEntry>> InputState::synthesizeCancelationEvents(
nsecs_t currentTime, const CancelationOptions& options) {
std::vector<std::unique_ptr<EventEntry>> events;
for (KeyMemento& memento : mKeyMementos) {
if (shouldCancelKey(memento, options)) {
events.push_back(
std::make_unique<KeyEntry>(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<std::unique_ptr<MotionEntry>> 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<std::unique_ptr<EventEntry>> InputState::synthesizePointerDownEvents(
nsecs_t currentTime) {
std::vector<std::unique_ptr<EventEntry>> events;
for (MotionMemento& memento : mMotionMementos) {
if (!isFromSource(memento.source, AINPUT_SOURCE_CLASS_POINTER)) {
continue;
}
if (memento.firstNewPointerIdx < 0) {
continue;
}
std::vector<PointerProperties> pointerProperties;
std::vector<PointerCoords> pointerCoords;
// We will deliver all pointers the target already knows about
for (uint32_t i = 0; i < static_cast<uint32_t>(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<uint32_t>(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<MotionEntry>(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<std::unique_ptr<MotionEntry>> InputState::synthesizeCancelationEventsForPointers(
const MotionMemento& memento, std::bitset<MAX_POINTER_ID + 1> pointerIds,
nsecs_t currentTime) {
std::vector<std::unique_ptr<MotionEntry>> events;
std::vector<uint32_t> canceledPointerIndices;
std::vector<PointerProperties> pointerProperties(MAX_POINTERS);
std::vector<PointerCoords> pointerCoords(MAX_POINTERS);
for (uint32_t pointerIdx = 0; pointerIdx < memento.getPointerCount(); pointerIdx++) {
uint32_t pointerId = uint32_t(memento.pointerProperties[pointerIdx].id);
pointerProperties[pointerIdx] = memento.pointerProperties[pointerIdx];
pointerCoords[pointerIdx] = memento.pointerCoords[pointerIdx];
if (pointerIds.test(pointerId)) {
canceledPointerIndices.push_back(pointerIdx);
}
}
if (canceledPointerIndices.size() == memento.getPointerCount()) {
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;
}
events.push_back(
std::make_unique<MotionEntry>(mIdGenerator.nextId(), /*injectionState=*/nullptr,
currentTime, 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));
} else {
// 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 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<uint32_t>());
uint32_t pointerCount = memento.getPointerCount();
for (const uint32_t pointerIdx : canceledPointerIndices) {
const int32_t action = pointerCount == 1 ? AMOTION_EVENT_ACTION_CANCEL
: AMOTION_EVENT_ACTION_POINTER_UP |
(pointerIdx << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
events.push_back(
std::make_unique<MotionEntry>(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);
pointerCount--;
}
}
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<int32_t> 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