blob: 1eeb4e678cac936c140c383733eb7b3fe0fb36bf [file] [log] [blame] [edit]
/**
* Copyright 2024 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 <cstdint>
#define LOG_TAG "InputTransport"
#define ATRACE_TAG ATRACE_TAG_INPUT
#include <errno.h>
#include <fcntl.h>
#include <inttypes.h>
#include <math.h>
#include <poll.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <android-base/logging.h>
#include <android-base/properties.h>
#include <android-base/stringprintf.h>
#include <binder/Parcel.h>
#include <cutils/properties.h>
#include <ftl/enum.h>
#include <log/log.h>
#include <utils/Trace.h>
#include <com_android_input_flags.h>
#include <input/InputConsumer.h>
#include <input/PrintTools.h>
#include <input/TraceTools.h>
namespace input_flags = com::android::input::flags;
namespace android {
namespace {
/**
* Log debug messages relating to the consumer end of the transport channel.
* Enable this via "adb shell setprop log.tag.InputTransportConsumer DEBUG" (requires restart)
*/
const bool DEBUG_TRANSPORT_CONSUMER =
__android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Consumer", ANDROID_LOG_INFO);
const bool IS_DEBUGGABLE_BUILD =
#if defined(__ANDROID__)
android::base::GetBoolProperty("ro.debuggable", false);
#else
true;
#endif
/**
* Log debug messages about touch event resampling.
*
* Enable this via "adb shell setprop log.tag.InputTransportResampling DEBUG".
* This requires a restart on non-debuggable (e.g. user) builds, but should take effect immediately
* on debuggable builds (e.g. userdebug).
*/
bool debugResampling() {
if (!IS_DEBUGGABLE_BUILD) {
static const bool DEBUG_TRANSPORT_RESAMPLING =
__android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Resampling",
ANDROID_LOG_INFO);
return DEBUG_TRANSPORT_RESAMPLING;
}
return __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG "Resampling", ANDROID_LOG_INFO);
}
void initializeKeyEvent(KeyEvent& event, const InputMessage& msg) {
event.initialize(msg.body.key.eventId, msg.body.key.deviceId, msg.body.key.source,
ui::LogicalDisplayId{msg.body.key.displayId}, msg.body.key.hmac,
msg.body.key.action, msg.body.key.flags, msg.body.key.keyCode,
msg.body.key.scanCode, msg.body.key.metaState, msg.body.key.repeatCount,
msg.body.key.downTime, msg.body.key.eventTime);
}
void initializeFocusEvent(FocusEvent& event, const InputMessage& msg) {
event.initialize(msg.body.focus.eventId, msg.body.focus.hasFocus);
}
void initializeCaptureEvent(CaptureEvent& event, const InputMessage& msg) {
event.initialize(msg.body.capture.eventId, msg.body.capture.pointerCaptureEnabled);
}
void initializeDragEvent(DragEvent& event, const InputMessage& msg) {
event.initialize(msg.body.drag.eventId, msg.body.drag.x, msg.body.drag.y,
msg.body.drag.isExiting);
}
void initializeMotionEvent(MotionEvent& event, const InputMessage& msg) {
uint32_t pointerCount = msg.body.motion.pointerCount;
PointerProperties pointerProperties[pointerCount];
PointerCoords pointerCoords[pointerCount];
for (uint32_t i = 0; i < pointerCount; i++) {
pointerProperties[i] = msg.body.motion.pointers[i].properties;
pointerCoords[i] = msg.body.motion.pointers[i].coords;
}
ui::Transform transform;
transform.set({msg.body.motion.dsdx, msg.body.motion.dtdx, msg.body.motion.tx,
msg.body.motion.dtdy, msg.body.motion.dsdy, msg.body.motion.ty, 0, 0, 1});
ui::Transform displayTransform;
displayTransform.set({msg.body.motion.dsdxRaw, msg.body.motion.dtdxRaw, msg.body.motion.txRaw,
msg.body.motion.dtdyRaw, msg.body.motion.dsdyRaw, msg.body.motion.tyRaw,
0, 0, 1});
event.initialize(msg.body.motion.eventId, msg.body.motion.deviceId, msg.body.motion.source,
ui::LogicalDisplayId{msg.body.motion.displayId}, msg.body.motion.hmac,
msg.body.motion.action, msg.body.motion.actionButton, msg.body.motion.flags,
msg.body.motion.edgeFlags, msg.body.motion.metaState,
msg.body.motion.buttonState, msg.body.motion.classification, transform,
msg.body.motion.xPrecision, msg.body.motion.yPrecision,
msg.body.motion.xCursorPosition, msg.body.motion.yCursorPosition,
displayTransform, msg.body.motion.downTime, msg.body.motion.eventTime,
pointerCount, pointerProperties, pointerCoords);
}
void addSample(MotionEvent& event, const InputMessage& msg) {
uint32_t pointerCount = msg.body.motion.pointerCount;
PointerCoords pointerCoords[pointerCount];
for (uint32_t i = 0; i < pointerCount; i++) {
pointerCoords[i] = msg.body.motion.pointers[i].coords;
}
event.setMetaState(event.getMetaState() | msg.body.motion.metaState);
event.addSample(msg.body.motion.eventTime, pointerCoords, msg.body.motion.eventId);
}
void initializeTouchModeEvent(TouchModeEvent& event, const InputMessage& msg) {
event.initialize(msg.body.touchMode.eventId, msg.body.touchMode.isInTouchMode);
}
// Nanoseconds per milliseconds.
constexpr nsecs_t NANOS_PER_MS = 1000000;
// Latency added during resampling. A few milliseconds doesn't hurt much but
// reduces the impact of mispredicted touch positions.
const std::chrono::duration RESAMPLE_LATENCY = 5ms;
// Minimum time difference between consecutive samples before attempting to resample.
const nsecs_t RESAMPLE_MIN_DELTA = 2 * NANOS_PER_MS;
// Maximum time difference between consecutive samples before attempting to resample
// by extrapolation.
const nsecs_t RESAMPLE_MAX_DELTA = 20 * NANOS_PER_MS;
// Maximum time to predict forward from the last known state, to avoid predicting too
// far into the future. This time is further bounded by 50% of the last time delta.
const nsecs_t RESAMPLE_MAX_PREDICTION = 8 * NANOS_PER_MS;
/**
* System property for enabling / disabling touch resampling.
* Resampling extrapolates / interpolates the reported touch event coordinates to better
* align them to the VSYNC signal, thus resulting in smoother scrolling performance.
* Resampling is not needed (and should be disabled) on hardware that already
* has touch events triggered by VSYNC.
* Set to "1" to enable resampling (default).
* Set to "0" to disable resampling.
* Resampling is enabled by default.
*/
const char* PROPERTY_RESAMPLING_ENABLED = "ro.input.resampling";
inline float lerp(float a, float b, float alpha) {
return a + alpha * (b - a);
}
inline bool isPointerEvent(int32_t source) {
return (source & AINPUT_SOURCE_CLASS_POINTER) == AINPUT_SOURCE_CLASS_POINTER;
}
bool shouldResampleTool(ToolType toolType) {
return toolType == ToolType::FINGER || toolType == ToolType::MOUSE ||
toolType == ToolType::STYLUS || toolType == ToolType::UNKNOWN;
}
} // namespace
using android::base::Result;
using android::base::StringPrintf;
// --- InputConsumer ---
InputConsumer::InputConsumer(const std::shared_ptr<InputChannel>& channel)
: InputConsumer(channel, isTouchResamplingEnabled()) {}
InputConsumer::InputConsumer(const std::shared_ptr<InputChannel>& channel,
bool enableTouchResampling)
: mResampleTouch(enableTouchResampling),
mChannel(channel),
mProcessingTraceTag(StringPrintf("InputConsumer processing on %s (%p)",
mChannel->getName().c_str(), this)),
mLifetimeTraceTag(StringPrintf("InputConsumer lifetime on %s (%p)",
mChannel->getName().c_str(), this)),
mLifetimeTraceCookie(
static_cast<int32_t>(reinterpret_cast<std::uintptr_t>(this) & 0xFFFFFFFF)),
mMsgDeferred(false) {
ATRACE_ASYNC_BEGIN(mLifetimeTraceTag.c_str(), /*cookie=*/mLifetimeTraceCookie);
}
InputConsumer::~InputConsumer() {
ATRACE_ASYNC_END(mLifetimeTraceTag.c_str(), /*cookie=*/mLifetimeTraceCookie);
}
bool InputConsumer::isTouchResamplingEnabled() {
return property_get_bool(PROPERTY_RESAMPLING_ENABLED, true);
}
status_t InputConsumer::consume(InputEventFactoryInterface* factory, bool consumeBatches,
nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent) {
ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
"channel '%s' consumer ~ consume: consumeBatches=%s, frameTime=%" PRId64,
mChannel->getName().c_str(), toString(consumeBatches), frameTime);
*outSeq = 0;
*outEvent = nullptr;
// Fetch the next input message.
// Loop until an event can be returned or no additional events are received.
while (!*outEvent) {
if (mMsgDeferred) {
// mMsg contains a valid input message from the previous call to consume
// that has not yet been processed.
mMsgDeferred = false;
} else {
// Receive a fresh message.
android::base::Result<InputMessage> result = mChannel->receiveMessage();
if (result.ok()) {
mMsg = std::move(result.value());
const auto [_, inserted] =
mConsumeTimes.emplace(mMsg.header.seq, systemTime(SYSTEM_TIME_MONOTONIC));
LOG_ALWAYS_FATAL_IF(!inserted, "Already have a consume time for seq=%" PRIu32,
mMsg.header.seq);
// Trace the event processing timeline - event was just read from the socket
ATRACE_ASYNC_BEGIN(mProcessingTraceTag.c_str(), /*cookie=*/mMsg.header.seq);
} else {
// Consume the next batched event unless batches are being held for later.
if (consumeBatches || result.error().code() != WOULD_BLOCK) {
result = android::base::Error(
consumeBatch(factory, frameTime, outSeq, outEvent));
if (*outEvent) {
ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
"channel '%s' consumer ~ consumed batch event, seq=%u",
mChannel->getName().c_str(), *outSeq);
break;
}
}
return result.error().code();
}
}
switch (mMsg.header.type) {
case InputMessage::Type::KEY: {
KeyEvent* keyEvent = factory->createKeyEvent();
if (!keyEvent) return NO_MEMORY;
initializeKeyEvent(*keyEvent, mMsg);
*outSeq = mMsg.header.seq;
*outEvent = keyEvent;
ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
"channel '%s' consumer ~ consumed key event, seq=%u",
mChannel->getName().c_str(), *outSeq);
break;
}
case InputMessage::Type::MOTION: {
ssize_t batchIndex = findBatch(mMsg.body.motion.deviceId, mMsg.body.motion.source);
if (batchIndex >= 0) {
Batch& batch = mBatches[batchIndex];
if (canAddSample(batch, &mMsg)) {
batch.samples.push_back(mMsg);
ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
"channel '%s' consumer ~ appended to batch event",
mChannel->getName().c_str());
break;
} else if (isPointerEvent(mMsg.body.motion.source) &&
mMsg.body.motion.action == AMOTION_EVENT_ACTION_CANCEL) {
// No need to process events that we are going to cancel anyways
const size_t count = batch.samples.size();
for (size_t i = 0; i < count; i++) {
const InputMessage& msg = batch.samples[i];
sendFinishedSignal(msg.header.seq, false);
}
batch.samples.erase(batch.samples.begin(), batch.samples.begin() + count);
mBatches.erase(mBatches.begin() + batchIndex);
} else {
// We cannot append to the batch in progress, so we need to consume
// the previous batch right now and defer the new message until later.
mMsgDeferred = true;
status_t result = consumeSamples(factory, batch, batch.samples.size(),
outSeq, outEvent);
mBatches.erase(mBatches.begin() + batchIndex);
if (result) {
return result;
}
ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
"channel '%s' consumer ~ consumed batch event and "
"deferred current event, seq=%u",
mChannel->getName().c_str(), *outSeq);
break;
}
}
// Start a new batch if needed.
if (mMsg.body.motion.action == AMOTION_EVENT_ACTION_MOVE ||
mMsg.body.motion.action == AMOTION_EVENT_ACTION_HOVER_MOVE) {
Batch batch;
batch.samples.push_back(mMsg);
mBatches.push_back(batch);
ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
"channel '%s' consumer ~ started batch event",
mChannel->getName().c_str());
break;
}
MotionEvent* motionEvent = factory->createMotionEvent();
if (!motionEvent) return NO_MEMORY;
updateTouchState(mMsg);
initializeMotionEvent(*motionEvent, mMsg);
*outSeq = mMsg.header.seq;
*outEvent = motionEvent;
ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
"channel '%s' consumer ~ consumed motion event, seq=%u",
mChannel->getName().c_str(), *outSeq);
break;
}
case InputMessage::Type::FINISHED:
case InputMessage::Type::TIMELINE: {
LOG(FATAL) << "Consumed a " << ftl::enum_string(mMsg.header.type)
<< " message, which should never be seen by "
"InputConsumer on "
<< mChannel->getName();
break;
}
case InputMessage::Type::FOCUS: {
FocusEvent* focusEvent = factory->createFocusEvent();
if (!focusEvent) return NO_MEMORY;
initializeFocusEvent(*focusEvent, mMsg);
*outSeq = mMsg.header.seq;
*outEvent = focusEvent;
break;
}
case InputMessage::Type::CAPTURE: {
CaptureEvent* captureEvent = factory->createCaptureEvent();
if (!captureEvent) return NO_MEMORY;
initializeCaptureEvent(*captureEvent, mMsg);
*outSeq = mMsg.header.seq;
*outEvent = captureEvent;
break;
}
case InputMessage::Type::DRAG: {
DragEvent* dragEvent = factory->createDragEvent();
if (!dragEvent) return NO_MEMORY;
initializeDragEvent(*dragEvent, mMsg);
*outSeq = mMsg.header.seq;
*outEvent = dragEvent;
break;
}
case InputMessage::Type::TOUCH_MODE: {
TouchModeEvent* touchModeEvent = factory->createTouchModeEvent();
if (!touchModeEvent) return NO_MEMORY;
initializeTouchModeEvent(*touchModeEvent, mMsg);
*outSeq = mMsg.header.seq;
*outEvent = touchModeEvent;
break;
}
}
}
return OK;
}
status_t InputConsumer::consumeBatch(InputEventFactoryInterface* factory, nsecs_t frameTime,
uint32_t* outSeq, InputEvent** outEvent) {
status_t result;
for (size_t i = mBatches.size(); i > 0;) {
i--;
Batch& batch = mBatches[i];
if (frameTime < 0) {
result = consumeSamples(factory, batch, batch.samples.size(), outSeq, outEvent);
mBatches.erase(mBatches.begin() + i);
return result;
}
nsecs_t sampleTime = frameTime;
if (mResampleTouch) {
sampleTime -= std::chrono::nanoseconds(RESAMPLE_LATENCY).count();
}
ssize_t split = findSampleNoLaterThan(batch, sampleTime);
if (split < 0) {
continue;
}
result = consumeSamples(factory, batch, split + 1, outSeq, outEvent);
const InputMessage* next;
if (batch.samples.empty()) {
mBatches.erase(mBatches.begin() + i);
next = nullptr;
} else {
next = &batch.samples[0];
}
if (!result && mResampleTouch) {
resampleTouchState(sampleTime, static_cast<MotionEvent*>(*outEvent), next);
}
return result;
}
return WOULD_BLOCK;
}
status_t InputConsumer::consumeSamples(InputEventFactoryInterface* factory, Batch& batch,
size_t count, uint32_t* outSeq, InputEvent** outEvent) {
MotionEvent* motionEvent = factory->createMotionEvent();
if (!motionEvent) return NO_MEMORY;
uint32_t chain = 0;
for (size_t i = 0; i < count; i++) {
InputMessage& msg = batch.samples[i];
updateTouchState(msg);
if (i) {
SeqChain seqChain;
seqChain.seq = msg.header.seq;
seqChain.chain = chain;
mSeqChains.push_back(seqChain);
addSample(*motionEvent, msg);
} else {
initializeMotionEvent(*motionEvent, msg);
}
chain = msg.header.seq;
}
batch.samples.erase(batch.samples.begin(), batch.samples.begin() + count);
*outSeq = chain;
*outEvent = motionEvent;
return OK;
}
void InputConsumer::updateTouchState(InputMessage& msg) {
if (!mResampleTouch || !isPointerEvent(msg.body.motion.source)) {
return;
}
int32_t deviceId = msg.body.motion.deviceId;
int32_t source = msg.body.motion.source;
// Update the touch state history to incorporate the new input message.
// If the message is in the past relative to the most recently produced resampled
// touch, then use the resampled time and coordinates instead.
switch (msg.body.motion.action & AMOTION_EVENT_ACTION_MASK) {
case AMOTION_EVENT_ACTION_DOWN: {
ssize_t index = findTouchState(deviceId, source);
if (index < 0) {
mTouchStates.push_back({});
index = mTouchStates.size() - 1;
}
TouchState& touchState = mTouchStates[index];
touchState.initialize(deviceId, source);
touchState.addHistory(msg);
break;
}
case AMOTION_EVENT_ACTION_MOVE: {
ssize_t index = findTouchState(deviceId, source);
if (index >= 0) {
TouchState& touchState = mTouchStates[index];
touchState.addHistory(msg);
rewriteMessage(touchState, msg);
}
break;
}
case AMOTION_EVENT_ACTION_POINTER_DOWN: {
ssize_t index = findTouchState(deviceId, source);
if (index >= 0) {
TouchState& touchState = mTouchStates[index];
touchState.lastResample.idBits.clearBit(msg.body.motion.getActionId());
rewriteMessage(touchState, msg);
}
break;
}
case AMOTION_EVENT_ACTION_POINTER_UP: {
ssize_t index = findTouchState(deviceId, source);
if (index >= 0) {
TouchState& touchState = mTouchStates[index];
rewriteMessage(touchState, msg);
touchState.lastResample.idBits.clearBit(msg.body.motion.getActionId());
}
break;
}
case AMOTION_EVENT_ACTION_SCROLL: {
ssize_t index = findTouchState(deviceId, source);
if (index >= 0) {
TouchState& touchState = mTouchStates[index];
rewriteMessage(touchState, msg);
}
break;
}
case AMOTION_EVENT_ACTION_UP:
case AMOTION_EVENT_ACTION_CANCEL: {
ssize_t index = findTouchState(deviceId, source);
if (index >= 0) {
TouchState& touchState = mTouchStates[index];
rewriteMessage(touchState, msg);
mTouchStates.erase(mTouchStates.begin() + index);
}
break;
}
}
}
/**
* Replace the coordinates in msg with the coordinates in lastResample, if necessary.
*
* If lastResample is no longer valid for a specific pointer (i.e. the lastResample time
* is in the past relative to msg and the past two events do not contain identical coordinates),
* then invalidate the lastResample data for that pointer.
* If the two past events have identical coordinates, then lastResample data for that pointer will
* remain valid, and will be used to replace these coordinates. Thus, if a certain coordinate x0 is
* resampled to the new value x1, then x1 will always be used to replace x0 until some new value
* not equal to x0 is received.
*/
void InputConsumer::rewriteMessage(TouchState& state, InputMessage& msg) {
nsecs_t eventTime = msg.body.motion.eventTime;
for (uint32_t i = 0; i < msg.body.motion.pointerCount; i++) {
uint32_t id = msg.body.motion.pointers[i].properties.id;
if (state.lastResample.idBits.hasBit(id)) {
if (eventTime < state.lastResample.eventTime ||
state.recentCoordinatesAreIdentical(id)) {
PointerCoords& msgCoords = msg.body.motion.pointers[i].coords;
const PointerCoords& resampleCoords = state.lastResample.getPointerById(id);
ALOGD_IF(debugResampling(), "[%d] - rewrite (%0.3f, %0.3f), old (%0.3f, %0.3f)", id,
resampleCoords.getX(), resampleCoords.getY(), msgCoords.getX(),
msgCoords.getY());
msgCoords.setAxisValue(AMOTION_EVENT_AXIS_X, resampleCoords.getX());
msgCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, resampleCoords.getY());
msgCoords.isResampled = true;
} else {
state.lastResample.idBits.clearBit(id);
}
}
}
}
void InputConsumer::resampleTouchState(nsecs_t sampleTime, MotionEvent* event,
const InputMessage* next) {
if (!mResampleTouch || !(isPointerEvent(event->getSource())) ||
event->getAction() != AMOTION_EVENT_ACTION_MOVE) {
return;
}
ssize_t index = findTouchState(event->getDeviceId(), event->getSource());
if (index < 0) {
ALOGD_IF(debugResampling(), "Not resampled, no touch state for device.");
return;
}
TouchState& touchState = mTouchStates[index];
if (touchState.historySize < 1) {
ALOGD_IF(debugResampling(), "Not resampled, no history for device.");
return;
}
// Ensure that the current sample has all of the pointers that need to be reported.
const History* current = touchState.getHistory(0);
size_t pointerCount = event->getPointerCount();
for (size_t i = 0; i < pointerCount; i++) {
uint32_t id = event->getPointerId(i);
if (!current->idBits.hasBit(id)) {
ALOGD_IF(debugResampling(), "Not resampled, missing id %d", id);
return;
}
if (!shouldResampleTool(event->getToolType(i))) {
ALOGD_IF(debugResampling(),
"Not resampled, containing unsupported tool type at pointer %d", id);
return;
}
}
// Find the data to use for resampling.
const History* other;
History future;
float alpha;
if (next) {
// Interpolate between current sample and future sample.
// So current->eventTime <= sampleTime <= future.eventTime.
future.initializeFrom(*next);
other = &future;
nsecs_t delta = future.eventTime - current->eventTime;
if (delta < RESAMPLE_MIN_DELTA) {
ALOGD_IF(debugResampling(), "Not resampled, delta time is too small: %" PRId64 " ns.",
delta);
return;
}
alpha = float(sampleTime - current->eventTime) / delta;
} else if (touchState.historySize >= 2) {
// Extrapolate future sample using current sample and past sample.
// So other->eventTime <= current->eventTime <= sampleTime.
other = touchState.getHistory(1);
nsecs_t delta = current->eventTime - other->eventTime;
if (delta < RESAMPLE_MIN_DELTA) {
ALOGD_IF(debugResampling(), "Not resampled, delta time is too small: %" PRId64 " ns.",
delta);
return;
} else if (delta > RESAMPLE_MAX_DELTA) {
ALOGD_IF(debugResampling(), "Not resampled, delta time is too large: %" PRId64 " ns.",
delta);
return;
}
nsecs_t maxPredict = current->eventTime + std::min(delta / 2, RESAMPLE_MAX_PREDICTION);
if (sampleTime > maxPredict) {
ALOGD_IF(debugResampling(),
"Sample time is too far in the future, adjusting prediction "
"from %" PRId64 " to %" PRId64 " ns.",
sampleTime - current->eventTime, maxPredict - current->eventTime);
sampleTime = maxPredict;
}
alpha = float(current->eventTime - sampleTime) / delta;
} else {
ALOGD_IF(debugResampling(), "Not resampled, insufficient data.");
return;
}
if (current->eventTime == sampleTime) {
ALOGD_IF(debugResampling(), "Not resampled, 2 events with identical times.");
return;
}
for (size_t i = 0; i < pointerCount; i++) {
uint32_t id = event->getPointerId(i);
if (!other->idBits.hasBit(id)) {
ALOGD_IF(debugResampling(), "Not resampled, the other doesn't have pointer id %d.", id);
return;
}
}
// Resample touch coordinates.
History oldLastResample;
oldLastResample.initializeFrom(touchState.lastResample);
touchState.lastResample.eventTime = sampleTime;
touchState.lastResample.idBits.clear();
for (size_t i = 0; i < pointerCount; i++) {
uint32_t id = event->getPointerId(i);
touchState.lastResample.idToIndex[id] = i;
touchState.lastResample.idBits.markBit(id);
if (oldLastResample.hasPointerId(id) && touchState.recentCoordinatesAreIdentical(id)) {
// We maintain the previously resampled value for this pointer (stored in
// oldLastResample) when the coordinates for this pointer haven't changed since then.
// This way we don't introduce artificial jitter when pointers haven't actually moved.
// The isResampled flag isn't cleared as the values don't reflect what the device is
// actually reporting.
// We know here that the coordinates for the pointer haven't changed because we
// would've cleared the resampled bit in rewriteMessage if they had. We can't modify
// lastResample in place because the mapping from pointer ID to index may have changed.
touchState.lastResample.pointers[i] = oldLastResample.getPointerById(id);
continue;
}
PointerCoords& resampledCoords = touchState.lastResample.pointers[i];
const PointerCoords& currentCoords = current->getPointerById(id);
resampledCoords = currentCoords;
resampledCoords.isResampled = true;
const PointerCoords& otherCoords = other->getPointerById(id);
resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_X,
lerp(currentCoords.getX(), otherCoords.getX(), alpha));
resampledCoords.setAxisValue(AMOTION_EVENT_AXIS_Y,
lerp(currentCoords.getY(), otherCoords.getY(), alpha));
ALOGD_IF(debugResampling(),
"[%d] - out (%0.3f, %0.3f), cur (%0.3f, %0.3f), "
"other (%0.3f, %0.3f), alpha %0.3f",
id, resampledCoords.getX(), resampledCoords.getY(), currentCoords.getX(),
currentCoords.getY(), otherCoords.getX(), otherCoords.getY(), alpha);
}
event->addSample(sampleTime, touchState.lastResample.pointers, event->getId());
}
status_t InputConsumer::sendFinishedSignal(uint32_t seq, bool handled) {
ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
"channel '%s' consumer ~ sendFinishedSignal: seq=%u, handled=%s",
mChannel->getName().c_str(), seq, toString(handled));
if (!seq) {
ALOGE("Attempted to send a finished signal with sequence number 0.");
return BAD_VALUE;
}
// Send finished signals for the batch sequence chain first.
size_t seqChainCount = mSeqChains.size();
if (seqChainCount) {
uint32_t currentSeq = seq;
uint32_t chainSeqs[seqChainCount];
size_t chainIndex = 0;
for (size_t i = seqChainCount; i > 0;) {
i--;
const SeqChain& seqChain = mSeqChains[i];
if (seqChain.seq == currentSeq) {
currentSeq = seqChain.chain;
chainSeqs[chainIndex++] = currentSeq;
mSeqChains.erase(mSeqChains.begin() + i);
}
}
status_t status = OK;
while (!status && chainIndex > 0) {
chainIndex--;
status = sendUnchainedFinishedSignal(chainSeqs[chainIndex], handled);
}
if (status) {
// An error occurred so at least one signal was not sent, reconstruct the chain.
for (;;) {
SeqChain seqChain;
seqChain.seq = chainIndex != 0 ? chainSeqs[chainIndex - 1] : seq;
seqChain.chain = chainSeqs[chainIndex];
mSeqChains.push_back(seqChain);
if (!chainIndex) break;
chainIndex--;
}
return status;
}
}
// Send finished signal for the last message in the batch.
return sendUnchainedFinishedSignal(seq, handled);
}
status_t InputConsumer::sendTimeline(int32_t inputEventId,
std::array<nsecs_t, GraphicsTimeline::SIZE> graphicsTimeline) {
ALOGD_IF(DEBUG_TRANSPORT_CONSUMER,
"channel '%s' consumer ~ sendTimeline: inputEventId=%" PRId32
", gpuCompletedTime=%" PRId64 ", presentTime=%" PRId64,
mChannel->getName().c_str(), inputEventId,
graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME],
graphicsTimeline[GraphicsTimeline::PRESENT_TIME]);
InputMessage msg;
msg.header.type = InputMessage::Type::TIMELINE;
msg.header.seq = 0;
msg.body.timeline.eventId = inputEventId;
msg.body.timeline.graphicsTimeline = std::move(graphicsTimeline);
return mChannel->sendMessage(&msg);
}
nsecs_t InputConsumer::getConsumeTime(uint32_t seq) const {
auto it = mConsumeTimes.find(seq);
// Consume time will be missing if either 'finishInputEvent' is called twice, or if it was
// called for the wrong (synthetic?) input event. Either way, it is a bug that should be fixed.
LOG_ALWAYS_FATAL_IF(it == mConsumeTimes.end(), "Could not find consume time for seq=%" PRIu32,
seq);
return it->second;
}
void InputConsumer::popConsumeTime(uint32_t seq) {
mConsumeTimes.erase(seq);
}
status_t InputConsumer::sendUnchainedFinishedSignal(uint32_t seq, bool handled) {
InputMessage msg;
msg.header.type = InputMessage::Type::FINISHED;
msg.header.seq = seq;
msg.body.finished.handled = handled;
msg.body.finished.consumeTime = getConsumeTime(seq);
status_t result = mChannel->sendMessage(&msg);
if (result == OK) {
// Remove the consume time if the socket write succeeded. We will not need to ack this
// message anymore. If the socket write did not succeed, we will try again and will still
// need consume time.
popConsumeTime(seq);
// Trace the event processing timeline - event was just finished
ATRACE_ASYNC_END(mProcessingTraceTag.c_str(), /*cookie=*/seq);
}
return result;
}
bool InputConsumer::hasPendingBatch() const {
return !mBatches.empty();
}
int32_t InputConsumer::getPendingBatchSource() const {
if (mBatches.empty()) {
return AINPUT_SOURCE_CLASS_NONE;
}
const Batch& batch = mBatches[0];
const InputMessage& head = batch.samples[0];
return head.body.motion.source;
}
bool InputConsumer::probablyHasInput() const {
return hasPendingBatch() || mChannel->probablyHasInput();
}
ssize_t InputConsumer::findBatch(int32_t deviceId, int32_t source) const {
for (size_t i = 0; i < mBatches.size(); i++) {
const Batch& batch = mBatches[i];
const InputMessage& head = batch.samples[0];
if (head.body.motion.deviceId == deviceId && head.body.motion.source == source) {
return i;
}
}
return -1;
}
ssize_t InputConsumer::findTouchState(int32_t deviceId, int32_t source) const {
for (size_t i = 0; i < mTouchStates.size(); i++) {
const TouchState& touchState = mTouchStates[i];
if (touchState.deviceId == deviceId && touchState.source == source) {
return i;
}
}
return -1;
}
bool InputConsumer::canAddSample(const Batch& batch, const InputMessage* msg) {
const InputMessage& head = batch.samples[0];
uint32_t pointerCount = msg->body.motion.pointerCount;
if (head.body.motion.pointerCount != pointerCount ||
head.body.motion.action != msg->body.motion.action) {
return false;
}
for (size_t i = 0; i < pointerCount; i++) {
if (head.body.motion.pointers[i].properties != msg->body.motion.pointers[i].properties) {
return false;
}
}
return true;
}
ssize_t InputConsumer::findSampleNoLaterThan(const Batch& batch, nsecs_t time) {
size_t numSamples = batch.samples.size();
size_t index = 0;
while (index < numSamples && batch.samples[index].body.motion.eventTime <= time) {
index += 1;
}
return ssize_t(index) - 1;
}
std::string InputConsumer::dump() const {
std::string out;
out = out + "mResampleTouch = " + toString(mResampleTouch) + "\n";
out = out + "mChannel = " + mChannel->getName() + "\n";
out = out + "mMsgDeferred: " + toString(mMsgDeferred) + "\n";
if (mMsgDeferred) {
out = out + "mMsg : " + ftl::enum_string(mMsg.header.type) + "\n";
}
out += "Batches:\n";
for (const Batch& batch : mBatches) {
out += " Batch:\n";
for (const InputMessage& msg : batch.samples) {
out += android::base::StringPrintf(" Message %" PRIu32 ": %s ", msg.header.seq,
ftl::enum_string(msg.header.type).c_str());
switch (msg.header.type) {
case InputMessage::Type::KEY: {
out += android::base::StringPrintf("action=%s keycode=%" PRId32,
KeyEvent::actionToString(
msg.body.key.action),
msg.body.key.keyCode);
break;
}
case InputMessage::Type::MOTION: {
out = out + "action=" + MotionEvent::actionToString(msg.body.motion.action);
for (uint32_t i = 0; i < msg.body.motion.pointerCount; i++) {
const float x = msg.body.motion.pointers[i].coords.getX();
const float y = msg.body.motion.pointers[i].coords.getY();
out += android::base::StringPrintf("\n Pointer %" PRIu32
" : x=%.1f y=%.1f",
i, x, y);
}
break;
}
case InputMessage::Type::FINISHED: {
out += android::base::StringPrintf("handled=%s, consumeTime=%" PRId64,
toString(msg.body.finished.handled),
msg.body.finished.consumeTime);
break;
}
case InputMessage::Type::FOCUS: {
out += android::base::StringPrintf("hasFocus=%s",
toString(msg.body.focus.hasFocus));
break;
}
case InputMessage::Type::CAPTURE: {
out += android::base::StringPrintf("hasCapture=%s",
toString(msg.body.capture
.pointerCaptureEnabled));
break;
}
case InputMessage::Type::DRAG: {
out += android::base::StringPrintf("x=%.1f y=%.1f, isExiting=%s",
msg.body.drag.x, msg.body.drag.y,
toString(msg.body.drag.isExiting));
break;
}
case InputMessage::Type::TIMELINE: {
const nsecs_t gpuCompletedTime =
msg.body.timeline
.graphicsTimeline[GraphicsTimeline::GPU_COMPLETED_TIME];
const nsecs_t presentTime =
msg.body.timeline.graphicsTimeline[GraphicsTimeline::PRESENT_TIME];
out += android::base::StringPrintf("inputEventId=%" PRId32
", gpuCompletedTime=%" PRId64
", presentTime=%" PRId64,
msg.body.timeline.eventId, gpuCompletedTime,
presentTime);
break;
}
case InputMessage::Type::TOUCH_MODE: {
out += android::base::StringPrintf("isInTouchMode=%s",
toString(msg.body.touchMode.isInTouchMode));
break;
}
}
out += "\n";
}
}
if (mBatches.empty()) {
out += " <empty>\n";
}
out += "mSeqChains:\n";
for (const SeqChain& chain : mSeqChains) {
out += android::base::StringPrintf(" chain: seq = %" PRIu32 " chain=%" PRIu32, chain.seq,
chain.chain);
}
if (mSeqChains.empty()) {
out += " <empty>\n";
}
out += "mConsumeTimes:\n";
for (const auto& [seq, consumeTime] : mConsumeTimes) {
out += android::base::StringPrintf(" seq = %" PRIu32 " consumeTime = %" PRId64, seq,
consumeTime);
}
if (mConsumeTimes.empty()) {
out += " <empty>\n";
}
return out;
}
} // namespace android