/* * 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 #include #include #include #include #include #include #include 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; using perfetto::protos::pbzero::WinscopeExtensions; using perfetto::protos::pbzero::WinscopeExtensionsImpl; // 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& 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& event) { return std::visit([&](const auto& e) { return e.getId(); }, event); } std::unique_ptr startTrace( const std::function&)>& configure) { protozero::HeapBuffered 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 tracingSession(perfetto::Tracing::NewTrace()); tracingSession->Setup(config); tracingSession->StartBlocking(); return tracingSession; } std::string stopTrace(std::unique_ptr tracingSession) { tracingSession->StopBlocking(); std::vector 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 tracedMotions; ArrayMap tracedKeys; ArrayMap tracedWindowDispatches; Trace::Decoder trace{rawTrace}; if (trace.has_packet()) { for (auto it = trace.packet(); it; it++) { TracePacket::Decoder packet{it->as_bytes()}; if (!packet.has_winscope_extensions()) { continue; } WinscopeExtensions::Decoder extensions{packet.winscope_extensions()}; const auto& field = extensions.Get(WinscopeExtensionsImpl::kAndroidInputEventFieldNumber); if (!field.valid()) { continue; } EXPECT_TRUE(packet.has_timestamp()); EXPECT_TRUE(packet.has_timestamp_clock_id()); EXPECT_EQ(packet.timestamp_clock_id(), static_cast(perfetto::protos::pbzero::BUILTIN_CLOCK_MONOTONIC)); AndroidInputEvent::Decoder event{field.as_bytes()}; 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); } } } return std::tuple{std::move(tracedMotions), std::move(tracedKeys), std::move(tracedWindowDispatches)}; } bool eventMatches(const MotionEvent& expected, const AndroidMotionEvent::Decoder& traced) { return static_cast(expected.getId()) == traced.event_id(); } bool eventMatches(const KeyEvent& expected, const AndroidKeyEvent::Decoder& traced) { return static_cast(expected.getId()) == traced.event_id(); } bool eventMatches(const InputTraceSession::WindowDispatchEvent& expected, const AndroidWindowInputDispatchEvent::Decoder& traced) { return static_cast(getId(expected.event)) == traced.event_id() && expected.window->getId() == traced.window_id(); } template 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&)> 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