Merge "InputTracer: Separate the threading logic into a wrapper backend" into main
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index f31884e..8858f0c 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