InputTracer: Trace motion and key events
Bug: 210460522
Test: manual with perfetto
Change-Id: I4bcac0b8bba5132d0e68815923ab8f89b2bede84
diff --git a/services/inputflinger/dispatcher/trace/InputTracer.cpp b/services/inputflinger/dispatcher/trace/InputTracer.cpp
new file mode 100644
index 0000000..60f574d
--- /dev/null
+++ b/services/inputflinger/dispatcher/trace/InputTracer.cpp
@@ -0,0 +1,178 @@
+/*
+ * 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.
+ */
+
+#define LOG_TAG "InputTracer"
+
+#include "InputTracer.h"
+
+#include <android-base/logging.h>
+
+namespace android::inputdispatcher::trace::impl {
+
+namespace {
+
+TracedEvent createTracedEvent(const MotionEntry& e) {
+ return TracedMotionEvent{e.id,
+ e.eventTime,
+ e.policyFlags,
+ e.deviceId,
+ e.source,
+ e.displayId,
+ e.action,
+ e.actionButton,
+ e.flags,
+ e.metaState,
+ e.buttonState,
+ e.classification,
+ e.edgeFlags,
+ e.xPrecision,
+ e.yPrecision,
+ e.xCursorPosition,
+ e.yCursorPosition,
+ e.downTime,
+ e.pointerProperties,
+ e.pointerCoords};
+}
+
+TracedEvent createTracedEvent(const KeyEntry& e) {
+ return TracedKeyEvent{e.id, e.eventTime, e.policyFlags, e.deviceId, e.source,
+ e.displayId, e.action, e.keyCode, e.scanCode, e.metaState,
+ e.downTime, e.flags, e.repeatCount};
+}
+
+} // namespace
+
+// --- InputTracer ---
+
+InputTracer::InputTracer(std::unique_ptr<InputTracingBackendInterface> backend)
+ : mTracerThread(&InputTracer::threadLoop, this), mBackend(std::move(backend)) {}
+
+InputTracer::~InputTracer() {
+ {
+ std::scoped_lock lock(mLock);
+ mThreadExit = true;
+ }
+ mThreadWakeCondition.notify_all();
+ mTracerThread.join();
+}
+
+std::unique_ptr<EventTrackerInterface> InputTracer::traceInboundEvent(const EventEntry& entry) {
+ std::scoped_lock lock(mLock);
+ TracedEvent traced;
+
+ if (entry.type == EventEntry::Type::MOTION) {
+ const auto& motion = static_cast<const MotionEntry&>(entry);
+ traced = createTracedEvent(motion);
+ } else if (entry.type == EventEntry::Type::KEY) {
+ const auto& key = static_cast<const KeyEntry&>(entry);
+ traced = createTracedEvent(key);
+ } else {
+ LOG(FATAL) << "Cannot trace EventEntry of type: " << ftl::enum_string(entry.type);
+ }
+
+ return std::make_unique<EventTrackerImpl>(*this, std::move(traced));
+}
+
+void InputTracer::dispatchToTargetHint(const EventTrackerInterface& cookie,
+ const InputTarget& target) {
+ std::scoped_lock lock(mLock);
+ auto& cookieState = getState(cookie);
+ if (!cookieState) {
+ LOG(FATAL) << "dispatchToTargetHint() should not be called after eventProcessingComplete()";
+ }
+ // TODO(b/210460522): Determine if the event is sensitive based on the target.
+}
+
+void InputTracer::eventProcessingComplete(const EventTrackerInterface& cookie) {
+ {
+ std::scoped_lock lock(mLock);
+ auto& cookieState = getState(cookie);
+ if (!cookieState) {
+ LOG(FATAL) << "Traced event was already logged. "
+ "eventProcessingComplete() was likely called more than once.";
+ }
+ mTraceQueue.emplace_back(std::move(*cookieState));
+ cookieState.reset();
+ } // release lock
+
+ mThreadWakeCondition.notify_all();
+}
+
+void InputTracer::traceEventDispatch(const DispatchEntry& dispatchEntry,
+ const EventTrackerInterface* cookie) {}
+
+std::optional<InputTracer::EventState>& InputTracer::getState(const EventTrackerInterface& cookie) {
+ return static_cast<const EventTrackerImpl&>(cookie).mLockedState;
+}
+
+void InputTracer::threadLoop() {
+ while (true) {
+ std::vector<const EventState> eventsToTrace;
+ {
+ std::unique_lock lock(mLock);
+ base::ScopedLockAssertion assumeLocked(mLock);
+ if (mThreadExit) {
+ return;
+ }
+ if (mTraceQueue.empty()) {
+ // Wait indefinitely until the thread is awoken.
+ mThreadWakeCondition.wait(lock);
+ }
+
+ mTraceQueue.swap(eventsToTrace);
+ } // release lock
+
+ // Trace the events into the backend without holding the lock to reduce the amount of
+ // work performed in the critical section.
+ writeEventsToBackend(eventsToTrace);
+ eventsToTrace.clear();
+ }
+}
+
+void InputTracer::writeEventsToBackend(const std::vector<const EventState>& events) {
+ for (const auto& event : events) {
+ if (auto* motion = std::get_if<TracedMotionEvent>(&event.event); motion != nullptr) {
+ mBackend->traceMotionEvent(*motion);
+ } else {
+ mBackend->traceKeyEvent(std::get<TracedKeyEvent>(event.event));
+ }
+ }
+}
+
+// --- InputTracer::EventTrackerImpl ---
+
+InputTracer::EventTrackerImpl::EventTrackerImpl(InputTracer& tracer, TracedEvent&& event)
+ : mTracer(tracer), mLockedState(event) {}
+
+InputTracer::EventTrackerImpl::~EventTrackerImpl() {
+ {
+ std::scoped_lock lock(mTracer.mLock);
+ if (!mLockedState) {
+ // This event has already been written to the trace as expected.
+ return;
+ }
+ // We're still holding on to the state, which means it hasn't yet been written to the trace.
+ // Write it to the trace now.
+ // TODO(b/210460522): Determine why/where the event is being destroyed before
+ // eventProcessingComplete() is called.
+ mTracer.mTraceQueue.emplace_back(std::move(*mLockedState));
+ mLockedState.reset();
+ } // release lock
+
+ mTracer.mThreadWakeCondition.notify_all();
+}
+
+} // namespace android::inputdispatcher::trace::impl