InputTracer: Add tests for perfetto backend

Add a new InputTraceSession class that encapsulates the logic to take
and decode a perfetto input trace for testing.

Then, use it to add new tests to verify the behavior of entire tracing
framework, using InputDispatcher as the interface.

Bug: 210460522
Test: atest inputflinger_tests
Change-Id: I5a9b47e831c5c30e5d7f2b60c9b8075b7d330e9e
diff --git a/services/inputflinger/tests/InputTraceSession.cpp b/services/inputflinger/tests/InputTraceSession.cpp
new file mode 100644
index 0000000..32acb5f
--- /dev/null
+++ b/services/inputflinger/tests/InputTraceSession.cpp
@@ -0,0 +1,209 @@
+/*
+ * 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 "InputTraceSession.h"
+
+#include <NotifyArgsBuilders.h>
+#include <android-base/logging.h>
+#include <gtest/gtest.h>
+#include <input/PrintTools.h>
+
+#include <utility>
+
+namespace android {
+
+using perfetto::protos::pbzero::AndroidInputEvent;
+using perfetto::protos::pbzero::AndroidInputEventConfig;
+using perfetto::protos::pbzero::AndroidKeyEvent;
+using perfetto::protos::pbzero::AndroidMotionEvent;
+using perfetto::protos::pbzero::AndroidWindowInputDispatchEvent;
+
+// These operator<< definitions must be in the global namespace for them to be accessible to the
+// GTEST library. They cannot be in the anonymous namespace.
+static std::ostream& operator<<(std::ostream& out,
+                                const std::variant<KeyEvent, MotionEvent>& event) {
+    std::visit([&](const auto& e) { out << e; }, event);
+    return out;
+}
+
+static std::ostream& operator<<(std::ostream& out,
+                                const InputTraceSession::WindowDispatchEvent& event) {
+    out << "Window dispatch to windowId: " << event.window->getId() << ", event: " << event.event;
+    return out;
+}
+
+namespace {
+
+inline uint32_t getId(const std::variant<KeyEvent, MotionEvent>& event) {
+    return std::visit([&](const auto& e) { return e.getId(); }, event);
+}
+
+std::unique_ptr<perfetto::TracingSession> startTrace(
+        const std::function<void(protozero::HeapBuffered<AndroidInputEventConfig>&)>& configure) {
+    protozero::HeapBuffered<AndroidInputEventConfig> inputEventConfig{};
+    configure(inputEventConfig);
+
+    perfetto::TraceConfig config;
+    config.add_buffers()->set_size_kb(1024); // Record up to 1 MiB.
+    auto* dataSourceConfig = config.add_data_sources()->mutable_config();
+    dataSourceConfig->set_name("android.input.inputevent");
+    dataSourceConfig->set_android_input_event_config_raw(inputEventConfig.SerializeAsString());
+
+    std::unique_ptr<perfetto::TracingSession> tracingSession(perfetto::Tracing::NewTrace());
+    tracingSession->Setup(config);
+    tracingSession->StartBlocking();
+    return tracingSession;
+}
+
+std::string stopTrace(std::unique_ptr<perfetto::TracingSession> tracingSession) {
+    tracingSession->StopBlocking();
+    std::vector<char> traceChars(tracingSession->ReadTraceBlocking());
+    return {traceChars.data(), traceChars.size()};
+}
+
+// Decodes the trace, and returns all of the traced input events, and whether they were each
+// traced as a redacted event.
+auto decodeTrace(const std::string& rawTrace) {
+    using namespace perfetto::protos::pbzero;
+
+    ArrayMap<AndroidMotionEvent::Decoder, bool /*redacted*/> tracedMotions;
+    ArrayMap<AndroidKeyEvent::Decoder, bool /*redacted*/> tracedKeys;
+    ArrayMap<AndroidWindowInputDispatchEvent::Decoder, bool /*redacted*/> tracedWindowDispatches;
+
+    Trace::Decoder trace{rawTrace};
+    if (trace.has_packet()) {
+        auto it = trace.packet();
+        while (it) {
+            TracePacket::Decoder packet{it->as_bytes()};
+            if (packet.has_android_input_event()) {
+                AndroidInputEvent::Decoder event{packet.android_input_event()};
+                if (event.has_dispatcher_motion_event()) {
+                    tracedMotions.emplace_back(event.dispatcher_motion_event(),
+                                               /*redacted=*/false);
+                }
+                if (event.has_dispatcher_motion_event_redacted()) {
+                    tracedMotions.emplace_back(event.dispatcher_motion_event_redacted(),
+                                               /*redacted=*/true);
+                }
+                if (event.has_dispatcher_key_event()) {
+                    tracedKeys.emplace_back(event.dispatcher_key_event(),
+                                            /*redacted=*/false);
+                }
+                if (event.has_dispatcher_key_event_redacted()) {
+                    tracedKeys.emplace_back(event.dispatcher_key_event_redacted(),
+                                            /*redacted=*/true);
+                }
+                if (event.has_dispatcher_window_dispatch_event()) {
+                    tracedWindowDispatches.emplace_back(event.dispatcher_window_dispatch_event(),
+                                                        /*redacted=*/false);
+                }
+                if (event.has_dispatcher_window_dispatch_event_redacted()) {
+                    tracedWindowDispatches
+                            .emplace_back(event.dispatcher_window_dispatch_event_redacted(),
+                                          /*redacted=*/true);
+                }
+            }
+            it++;
+        }
+    }
+    return std::tuple{std::move(tracedMotions), std::move(tracedKeys),
+                      std::move(tracedWindowDispatches)};
+}
+
+bool eventMatches(const MotionEvent& expected, const AndroidMotionEvent::Decoder& traced) {
+    return static_cast<uint32_t>(expected.getId()) == traced.event_id();
+}
+
+bool eventMatches(const KeyEvent& expected, const AndroidKeyEvent::Decoder& traced) {
+    return static_cast<uint32_t>(expected.getId()) == traced.event_id();
+}
+
+bool eventMatches(const InputTraceSession::WindowDispatchEvent& expected,
+                  const AndroidWindowInputDispatchEvent::Decoder& traced) {
+    return static_cast<uint32_t>(getId(expected.event)) == traced.event_id() &&
+            expected.window->getId() == traced.window_id();
+}
+
+template <typename ExpectedEvents, typename TracedEvents>
+void verifyExpectedEventsTraced(const ExpectedEvents& expectedEvents,
+                                const TracedEvents& tracedEvents, std::string_view name) {
+    uint32_t totalExpectedCount = 0;
+
+    for (const auto& [expectedEvent, expectedLevel] : expectedEvents) {
+        int32_t totalMatchCount = 0;
+        int32_t redactedMatchCount = 0;
+        for (const auto& [tracedEvent, isRedacted] : tracedEvents) {
+            if (eventMatches(expectedEvent, tracedEvent)) {
+                totalMatchCount++;
+                if (isRedacted) {
+                    redactedMatchCount++;
+                }
+            }
+        }
+        switch (expectedLevel) {
+            case Level::NONE:
+                ASSERT_EQ(totalMatchCount, 0) << "Event should not be traced, but it was traced"
+                                              << "\n\tExpected event: " << expectedEvent;
+                break;
+            case Level::REDACTED:
+            case Level::COMPLETE:
+                ASSERT_EQ(totalMatchCount, 1)
+                        << "Event should match exactly one traced event, but it matched: "
+                        << totalMatchCount << "\n\tExpected event: " << expectedEvent;
+                ASSERT_EQ(redactedMatchCount, expectedLevel == Level::REDACTED ? 1 : 0);
+                totalExpectedCount++;
+                break;
+        }
+    }
+
+    ASSERT_EQ(tracedEvents.size(), totalExpectedCount)
+            << "The number of traced " << name
+            << " events does not exactly match the number of expected events";
+}
+
+} // namespace
+
+InputTraceSession::InputTraceSession(
+        std::function<void(protozero::HeapBuffered<AndroidInputEventConfig>&)> configure)
+      : mPerfettoSession(startTrace(std::move(configure))) {}
+
+InputTraceSession::~InputTraceSession() {
+    const auto rawTrace = stopTrace(std::move(mPerfettoSession));
+    verifyExpectations(rawTrace);
+}
+
+void InputTraceSession::expectMotionTraced(Level level, const MotionEvent& event) {
+    mExpectedMotions.emplace_back(event, level);
+}
+
+void InputTraceSession::expectKeyTraced(Level level, const KeyEvent& event) {
+    mExpectedKeys.emplace_back(event, level);
+}
+
+void InputTraceSession::expectDispatchTraced(Level level, const WindowDispatchEvent& event) {
+    mExpectedWindowDispatches.emplace_back(event, level);
+}
+
+void InputTraceSession::verifyExpectations(const std::string& rawTrace) {
+    auto [tracedMotions, tracedKeys, tracedWindowDispatches] = decodeTrace(rawTrace);
+
+    verifyExpectedEventsTraced(mExpectedMotions, tracedMotions, "motion");
+    verifyExpectedEventsTraced(mExpectedKeys, tracedKeys, "key");
+    verifyExpectedEventsTraced(mExpectedWindowDispatches, tracedWindowDispatches,
+                               "window dispatch");
+}
+
+} // namespace android