InputTracer: Separate the threading logic into a wrapper backend

InputTracer writes to the tracing backend interface. However, since
perfetto expects events to be written from a single thread, InputTracer
would write the events from a separate thread.

To simplify the logic inside InputTracer, remove the threading logic.
Instead, we introduce the ThreadingBackend class, which wraps around an
abitrary backend and forwards events to the inner backend from a new
thread.

Another benefit of this is that we no longer have to use the threaded
backend for tests, which makes the tracing part of InputDispatcher_test
deterministic.

Bug: 210460522
Test: atest inputflinger_tests
Change-Id: If7bbc912c05bc975ec9585f0a0ebce68683925bb
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 71afbcc..d2d3db3 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -54,6 +54,7 @@
 #include "InputDispatcher.h"
 #include "trace/InputTracer.h"
 #include "trace/InputTracingPerfettoBackend.h"
+#include "trace/ThreadedBackend.h"
 
 #define INDENT "  "
 #define INDENT2 "    "
@@ -86,6 +87,15 @@
     return input_flags::enable_input_event_tracing() && isUserdebugOrEng;
 }
 
+// Create the input tracing backend that writes to perfetto from a single thread.
+std::unique_ptr<trace::InputTracingBackendInterface> createInputTracingBackendIfEnabled() {
+    if (!isInputTracingEnabled()) {
+        return nullptr;
+    }
+    return std::make_unique<trace::impl::ThreadedBackend<trace::impl::PerfettoBackend>>(
+            trace::impl::PerfettoBackend());
+}
+
 template <class Entry>
 void ensureEventTraced(const Entry& entry) {
     if (!entry.traceTracker) {
@@ -832,9 +842,7 @@
 // --- InputDispatcher ---
 
 InputDispatcher::InputDispatcher(InputDispatcherPolicyInterface& policy)
-      : InputDispatcher(policy,
-                        isInputTracingEnabled() ? std::make_unique<trace::impl::PerfettoBackend>()
-                                                : nullptr) {}
+      : InputDispatcher(policy, createInputTracingBackendIfEnabled()) {}
 
 InputDispatcher::InputDispatcher(InputDispatcherPolicyInterface& policy,
                                  std::unique_ptr<trace::InputTracingBackendInterface> traceBackend)
diff --git a/services/inputflinger/dispatcher/trace/InputTracer.cpp b/services/inputflinger/dispatcher/trace/InputTracer.cpp
index 8a855c2..be09013 100644
--- a/services/inputflinger/dispatcher/trace/InputTracer.cpp
+++ b/services/inputflinger/dispatcher/trace/InputTracer.cpp
@@ -19,12 +19,17 @@
 #include "InputTracer.h"
 
 #include <android-base/logging.h>
-#include <utils/AndroidThreads.h>
 
 namespace android::inputdispatcher::trace::impl {
 
 namespace {
 
+// Helper to std::visit with lambdas.
+template <typename... V>
+struct Visitor : V... {
+    using V::operator()...;
+};
+
 TracedEvent createTracedEvent(const MotionEntry& e) {
     return TracedMotionEvent{e.id,
                              e.eventTime,
@@ -59,19 +64,9 @@
 // --- 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();
-}
+      : mBackend(std::move(backend)) {}
 
 std::unique_ptr<EventTrackerInterface> InputTracer::traceInboundEvent(const EventEntry& entry) {
-    std::scoped_lock lock(mLock);
     TracedEvent traced;
 
     if (entry.type == EventEntry::Type::MOTION) {
@@ -89,7 +84,6 @@
 
 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()";
@@ -98,127 +92,72 @@
 }
 
 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
+    auto& cookieState = getState(cookie);
+    if (!cookieState) {
+        LOG(FATAL) << "Traced event was already logged. "
+                      "eventProcessingComplete() was likely called more than once.";
+    }
 
-    mThreadWakeCondition.notify_all();
+    std::visit(Visitor{[&](const TracedMotionEvent& e) { mBackend->traceMotionEvent(e); },
+                       [&](const TracedKeyEvent& e) { mBackend->traceKeyEvent(e); }},
+               cookieState->event);
+    cookieState.reset();
 }
 
 void InputTracer::traceEventDispatch(const DispatchEntry& dispatchEntry,
                                      const EventTrackerInterface* cookie) {
-    {
-        std::scoped_lock lock(mLock);
-        const EventEntry& entry = *dispatchEntry.eventEntry;
+    const EventEntry& entry = *dispatchEntry.eventEntry;
 
-        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);
-        }
+    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);
+    }
 
-        if (!cookie) {
-            // This event was not tracked as an inbound event, so trace it now.
-            mTraceQueue.emplace_back(traced);
-        }
+    if (!cookie) {
+        // This event was not tracked as an inbound event, so trace it now.
+        std::visit(Visitor{[&](const TracedMotionEvent& e) { mBackend->traceMotionEvent(e); },
+                           [&](const TracedKeyEvent& e) { mBackend->traceKeyEvent(e); }},
+                   traced);
+    }
 
-        // The vsyncId only has meaning if the event is targeting a window.
-        const int32_t windowId = dispatchEntry.windowId.value_or(0);
-        const int32_t vsyncId = dispatchEntry.windowId.has_value() ? dispatchEntry.vsyncId : 0;
+    // The vsyncId only has meaning if the event is targeting a window.
+    const int32_t windowId = dispatchEntry.windowId.value_or(0);
+    const int32_t vsyncId = dispatchEntry.windowId.has_value() ? dispatchEntry.vsyncId : 0;
 
-        mDispatchTraceQueue.emplace_back(std::move(traced), dispatchEntry.deliveryTime,
-                                         dispatchEntry.resolvedFlags, dispatchEntry.targetUid,
-                                         vsyncId, windowId, dispatchEntry.transform,
-                                         dispatchEntry.rawTransform);
-    } // release lock
-
-    mThreadWakeCondition.notify_all();
+    mBackend->traceWindowDispatch({std::move(traced), dispatchEntry.deliveryTime,
+                                   dispatchEntry.resolvedFlags, dispatchEntry.targetUid, vsyncId,
+                                   windowId, dispatchEntry.transform, dispatchEntry.rawTransform,
+                                   /*hmac=*/{}});
 }
 
 std::optional<InputTracer::EventState>& InputTracer::getState(const EventTrackerInterface& cookie) {
-    return static_cast<const EventTrackerImpl&>(cookie).mLockedState;
-}
-
-void InputTracer::threadLoop() {
-    androidSetThreadName("InputTracer");
-
-    std::vector<const EventState> eventsToTrace;
-    std::vector<const WindowDispatchArgs> dispatchEventsToTrace;
-
-    while (true) {
-        { // acquire lock
-            std::unique_lock lock(mLock);
-            base::ScopedLockAssertion assumeLocked(mLock);
-
-            // Wait until we need to process more events or exit.
-            mThreadWakeCondition.wait(lock, [&]() REQUIRES(mLock) {
-                return mThreadExit || !mTraceQueue.empty() || !mDispatchTraceQueue.empty();
-            });
-            if (mThreadExit) {
-                return;
-            }
-
-            mTraceQueue.swap(eventsToTrace);
-            mDispatchTraceQueue.swap(dispatchEventsToTrace);
-        } // 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, dispatchEventsToTrace);
-        eventsToTrace.clear();
-        dispatchEventsToTrace.clear();
-    }
-}
-
-void InputTracer::writeEventsToBackend(
-        const std::vector<const EventState>& events,
-        const std::vector<const WindowDispatchArgs>& dispatchEvents) {
-    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));
-        }
-    }
-
-    for (const auto& dispatchArgs : dispatchEvents) {
-        mBackend->traceWindowDispatch(dispatchArgs);
-    }
+    return static_cast<const EventTrackerImpl&>(cookie).mState;
 }
 
 // --- InputTracer::EventTrackerImpl ---
 
 InputTracer::EventTrackerImpl::EventTrackerImpl(InputTracer& tracer, TracedEvent&& event)
-      : mTracer(tracer), mLockedState(event) {}
+      : mTracer(tracer), mState(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();
+    if (!mState) {
+        // 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.
+    std::visit(Visitor{[&](const TracedMotionEvent& e) { mTracer.mBackend->traceMotionEvent(e); },
+                       [&](const TracedKeyEvent& e) { mTracer.mBackend->traceKeyEvent(e); }},
+               mState->event);
+    mState.reset();
 }
 
 } // namespace android::inputdispatcher::trace::impl
diff --git a/services/inputflinger/dispatcher/trace/InputTracer.h b/services/inputflinger/dispatcher/trace/InputTracer.h
index 9fe395d..c8b25c9 100644
--- a/services/inputflinger/dispatcher/trace/InputTracer.h
+++ b/services/inputflinger/dispatcher/trace/InputTracer.h
@@ -18,14 +18,7 @@
 
 #include "InputTracerInterface.h"
 
-#include <android-base/thread_annotations.h>
-#include <gui/WindowInfo.h>
-
 #include <memory>
-#include <mutex>
-#include <thread>
-#include <unordered_set>
-#include <vector>
 
 #include "../Entry.h"
 #include "InputTracingBackendInterface.h"
@@ -35,17 +28,16 @@
 /**
  * The tracer implementation for InputDispatcher.
  *
- * InputTracer is thread-safe, so it can be called from any thread. Upon construction, InputTracer
- * will start its own thread that it uses for write events into the tracing backend. That is the
- * one and only thread that will interact with the tracing backend, since the Perfetto backend
- * uses thread-local storage.
+ * InputTracer's responsibility is to keep track of events as they are processed by InputDispatcher,
+ * and to write the events to the tracing backend when enough information is collected. InputTracer
+ * is not thread-safe.
  *
  * See the documentation in InputTracerInterface for the API surface.
  */
 class InputTracer : public InputTracerInterface {
 public:
     explicit InputTracer(std::unique_ptr<InputTracingBackendInterface>);
-    ~InputTracer() override;
+    ~InputTracer() = default;
     InputTracer(const InputTracer&) = delete;
     InputTracer& operator=(const InputTracer&) = delete;
 
@@ -55,10 +47,6 @@
     void traceEventDispatch(const DispatchEntry&, const EventTrackerInterface*) override;
 
 private:
-    std::mutex mLock;
-    std::thread mTracerThread;
-    bool mThreadExit GUARDED_BY(mLock){false};
-    std::condition_variable mThreadWakeCondition;
     std::unique_ptr<InputTracingBackendInterface> mBackend;
 
     // The state of a tracked event.
@@ -67,14 +55,12 @@
         // TODO(b/210460522): Add additional args for tracking event sensitivity and
         //  dispatch target UIDs.
     };
-    std::vector<const EventState> mTraceQueue GUARDED_BY(mLock);
-    using WindowDispatchArgs = InputTracingBackendInterface::WindowDispatchArgs;
-    std::vector<const WindowDispatchArgs> mDispatchTraceQueue GUARDED_BY(mLock);
 
-    // Provides thread-safe access to the state from an event tracker cookie.
-    std::optional<EventState>& getState(const EventTrackerInterface&) REQUIRES(mLock);
+    // Get the event state associated with a tracking cookie.
+    std::optional<EventState>& getState(const EventTrackerInterface&);
 
-    // Implementation of the event tracker cookie.
+    // Implementation of the event tracker cookie. The cookie holds the event state directly for
+    // convenience to avoid the overhead of tracking the state separately in InputTracer.
     class EventTrackerImpl : public EventTrackerInterface {
     public:
         explicit EventTrackerImpl(InputTracer&, TracedEvent&& entry);
@@ -84,16 +70,10 @@
         InputTracer& mTracer;
         // This event tracker cookie will only hold the state as long as it has not been written
         // to the trace. The state is released when the event is written to the trace.
-        mutable std::optional<EventState> mLockedState;
+        mutable std::optional<EventState> mState;
 
-        // Only allow InputTracer access to the locked state through getTrackerState() to ensure
-        // that the InputTracer lock is held when this is accessed.
         friend std::optional<EventState>& InputTracer::getState(const EventTrackerInterface&);
     };
-
-    void threadLoop();
-    void writeEventsToBackend(const std::vector<const EventState>& events,
-                              const std::vector<const WindowDispatchArgs>& dispatchEvents);
 };
 
 } // namespace android::inputdispatcher::trace::impl
diff --git a/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h b/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h
index bc47817..b0eadfe 100644
--- a/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h
+++ b/services/inputflinger/dispatcher/trace/InputTracingBackendInterface.h
@@ -82,10 +82,10 @@
     virtual ~InputTracingBackendInterface() = default;
 
     /** Trace a KeyEvent. */
-    virtual void traceKeyEvent(const TracedKeyEvent&) const = 0;
+    virtual void traceKeyEvent(const TracedKeyEvent&) = 0;
 
     /** Trace a MotionEvent. */
-    virtual void traceMotionEvent(const TracedMotionEvent&) const = 0;
+    virtual void traceMotionEvent(const TracedMotionEvent&) = 0;
 
     /** Trace an event being sent to a window. */
     struct WindowDispatchArgs {
@@ -99,7 +99,7 @@
         ui::Transform rawTransform;
         std::array<uint8_t, 32> hmac;
     };
-    virtual void traceWindowDispatch(const WindowDispatchArgs&) const = 0;
+    virtual void traceWindowDispatch(const WindowDispatchArgs&) = 0;
 };
 
 } // namespace android::inputdispatcher::trace
diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp
index 4442ad8..46ad9e1 100644
--- a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp
+++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp
@@ -63,7 +63,7 @@
     });
 }
 
-void PerfettoBackend::traceMotionEvent(const TracedMotionEvent& event) const {
+void PerfettoBackend::traceMotionEvent(const TracedMotionEvent& event) {
     InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) {
         auto tracePacket = ctx.NewTracePacket();
         auto* inputEvent = tracePacket->set_android_input_event();
@@ -72,7 +72,7 @@
     });
 }
 
-void PerfettoBackend::traceKeyEvent(const TracedKeyEvent& event) const {
+void PerfettoBackend::traceKeyEvent(const TracedKeyEvent& event) {
     InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) {
         auto tracePacket = ctx.NewTracePacket();
         auto* inputEvent = tracePacket->set_android_input_event();
@@ -81,7 +81,7 @@
     });
 }
 
-void PerfettoBackend::traceWindowDispatch(const WindowDispatchArgs& dispatchArgs) const {
+void PerfettoBackend::traceWindowDispatch(const WindowDispatchArgs& dispatchArgs) {
     InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) {
         auto tracePacket = ctx.NewTracePacket();
         auto* inputEventProto = tracePacket->set_android_input_event();
diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h
index 2777cfe..fefcfb3 100644
--- a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h
+++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h
@@ -48,9 +48,9 @@
     PerfettoBackend();
     ~PerfettoBackend() override = default;
 
-    void traceKeyEvent(const TracedKeyEvent&) const override;
-    void traceMotionEvent(const TracedMotionEvent&) const override;
-    void traceWindowDispatch(const WindowDispatchArgs&) const override;
+    void traceKeyEvent(const TracedKeyEvent&) override;
+    void traceMotionEvent(const TracedMotionEvent&) override;
+    void traceWindowDispatch(const WindowDispatchArgs&) override;
 
     class InputEventDataSource : public perfetto::DataSource<InputEventDataSource> {
     public:
diff --git a/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp b/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp
new file mode 100644
index 0000000..a58d52a
--- /dev/null
+++ b/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp
@@ -0,0 +1,114 @@
+/*
+ * 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 "ThreadedBackend.h"
+
+#include "InputTracingPerfettoBackend.h"
+
+#include <android-base/logging.h>
+#include <utils/AndroidThreads.h>
+
+namespace android::inputdispatcher::trace::impl {
+
+namespace {
+
+// Helper to std::visit with lambdas.
+template <typename... V>
+struct Visitor : V... {
+    using V::operator()...;
+};
+
+} // namespace
+
+// --- ThreadedBackend ---
+
+template <typename Backend>
+ThreadedBackend<Backend>::ThreadedBackend(Backend&& innerBackend)
+      : mTracerThread(&ThreadedBackend::threadLoop, this), mBackend(std::move(innerBackend)) {}
+
+template <typename Backend>
+ThreadedBackend<Backend>::~ThreadedBackend() {
+    {
+        std::scoped_lock lock(mLock);
+        mThreadExit = true;
+    }
+    mThreadWakeCondition.notify_all();
+    mTracerThread.join();
+}
+
+template <typename Backend>
+void ThreadedBackend<Backend>::traceMotionEvent(const TracedMotionEvent& event) {
+    std::scoped_lock lock(mLock);
+    mQueue.emplace_back(event);
+    mThreadWakeCondition.notify_all();
+}
+
+template <typename Backend>
+void ThreadedBackend<Backend>::traceKeyEvent(const TracedKeyEvent& event) {
+    std::scoped_lock lock(mLock);
+    mQueue.emplace_back(event);
+    mThreadWakeCondition.notify_all();
+}
+
+template <typename Backend>
+void ThreadedBackend<Backend>::traceWindowDispatch(const WindowDispatchArgs& dispatchArgs) {
+    std::scoped_lock lock(mLock);
+    mQueue.emplace_back(dispatchArgs);
+    mThreadWakeCondition.notify_all();
+}
+
+template <typename Backend>
+void ThreadedBackend<Backend>::threadLoop() {
+    androidSetThreadName("InputTracer");
+
+    std::vector<std::variant<TracedKeyEvent, TracedMotionEvent, WindowDispatchArgs>> events;
+
+    while (true) {
+        { // acquire lock
+            std::unique_lock lock(mLock);
+            base::ScopedLockAssertion assumeLocked(mLock);
+
+            // Wait until we need to process more events or exit.
+            mThreadWakeCondition.wait(lock, [&]() REQUIRES(mLock) {
+                return mThreadExit || !mQueue.empty();
+            });
+            if (mThreadExit) {
+                return;
+            }
+
+            mQueue.swap(events);
+        } // release lock
+
+        // Trace the events into the backend without holding the lock to reduce the amount of
+        // work performed in the critical section.
+        for (const auto& event : events) {
+            std::visit(Visitor{[&](const TracedMotionEvent& e) { mBackend.traceMotionEvent(e); },
+                               [&](const TracedKeyEvent& e) { mBackend.traceKeyEvent(e); },
+                               [&](const WindowDispatchArgs& args) {
+                                   mBackend.traceWindowDispatch(args);
+                               }},
+                       event);
+        }
+        events.clear();
+    }
+}
+
+// Explicit template instantiation for the PerfettoBackend.
+template class ThreadedBackend<PerfettoBackend>;
+
+} // namespace android::inputdispatcher::trace::impl
diff --git a/services/inputflinger/dispatcher/trace/ThreadedBackend.h b/services/inputflinger/dispatcher/trace/ThreadedBackend.h
new file mode 100644
index 0000000..c42f896
--- /dev/null
+++ b/services/inputflinger/dispatcher/trace/ThreadedBackend.h
@@ -0,0 +1,59 @@
+/*
+ * 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.
+ */
+
+#pragma once
+
+#include "InputTracingPerfettoBackend.h"
+
+#include <android-base/thread_annotations.h>
+#include <mutex>
+#include <thread>
+#include <variant>
+#include <vector>
+
+namespace android::inputdispatcher::trace::impl {
+
+/**
+ * A wrapper around an InputTracingBackend implementation that writes to the inner tracing backend
+ * from a single new thread that it creates. The new tracing thread is started when the
+ * ThreadedBackend is created, and is stopped when it is destroyed. The ThreadedBackend is
+ * thread-safe.
+ */
+template <typename Backend>
+class ThreadedBackend : public InputTracingBackendInterface {
+public:
+    ThreadedBackend(Backend&& innerBackend);
+    ~ThreadedBackend() override;
+
+    void traceKeyEvent(const TracedKeyEvent&) override;
+    void traceMotionEvent(const TracedMotionEvent&) override;
+    void traceWindowDispatch(const WindowDispatchArgs&) override;
+
+private:
+    std::mutex mLock;
+    std::thread mTracerThread;
+    bool mThreadExit GUARDED_BY(mLock){false};
+    std::condition_variable mThreadWakeCondition;
+    Backend mBackend;
+    std::vector<std::variant<TracedKeyEvent, TracedMotionEvent, WindowDispatchArgs>> mQueue
+            GUARDED_BY(mLock);
+
+    using WindowDispatchArgs = InputTracingBackendInterface::WindowDispatchArgs;
+
+    void threadLoop();
+};
+
+} // namespace android::inputdispatcher::trace::impl
diff --git a/services/inputflinger/tests/FakeInputTracingBackend.cpp b/services/inputflinger/tests/FakeInputTracingBackend.cpp
index b7af356..4655ee8 100644
--- a/services/inputflinger/tests/FakeInputTracingBackend.cpp
+++ b/services/inputflinger/tests/FakeInputTracingBackend.cpp
@@ -163,7 +163,7 @@
 
 // --- FakeInputTracingBackend ---
 
-void FakeInputTracingBackend::traceKeyEvent(const trace::TracedKeyEvent& event) const {
+void FakeInputTracingBackend::traceKeyEvent(const trace::TracedKeyEvent& event) {
     {
         std::scoped_lock lock(mTrace->mLock);
         mTrace->mTracedEvents.emplace(event.id, event);
@@ -171,7 +171,7 @@
     mTrace->mEventTracedCondition.notify_all();
 }
 
-void FakeInputTracingBackend::traceMotionEvent(const trace::TracedMotionEvent& event) const {
+void FakeInputTracingBackend::traceMotionEvent(const trace::TracedMotionEvent& event) {
     {
         std::scoped_lock lock(mTrace->mLock);
         mTrace->mTracedEvents.emplace(event.id, event);
@@ -179,7 +179,7 @@
     mTrace->mEventTracedCondition.notify_all();
 }
 
-void FakeInputTracingBackend::traceWindowDispatch(const WindowDispatchArgs& args) const {
+void FakeInputTracingBackend::traceWindowDispatch(const WindowDispatchArgs& args) {
     {
         std::scoped_lock lock(mTrace->mLock);
         mTrace->mTracedWindowDispatches.push_back(args);
diff --git a/services/inputflinger/tests/FakeInputTracingBackend.h b/services/inputflinger/tests/FakeInputTracingBackend.h
index 108419a..1b3613d 100644
--- a/services/inputflinger/tests/FakeInputTracingBackend.h
+++ b/services/inputflinger/tests/FakeInputTracingBackend.h
@@ -83,9 +83,9 @@
 private:
     std::shared_ptr<VerifyingTrace> mTrace;
 
-    void traceKeyEvent(const trace::TracedKeyEvent& entry) const override;
-    void traceMotionEvent(const trace::TracedMotionEvent& entry) const override;
-    void traceWindowDispatch(const WindowDispatchArgs& entry) const override;
+    void traceKeyEvent(const trace::TracedKeyEvent& entry) override;
+    void traceMotionEvent(const trace::TracedMotionEvent& entry) override;
+    void traceWindowDispatch(const WindowDispatchArgs& entry) override;
 };
 
 } // namespace android::inputdispatcher