diff options
author | 2024-03-21 21:52:57 +0000 | |
---|---|---|
committer | 2024-04-03 21:20:46 +0000 | |
commit | 9a9897d154f9230325113d18c8d6f4cee7ca72d7 (patch) | |
tree | 538baa0c027f85f610dfe27e772a27960ed8b48e | |
parent | efd55b1a397b8a87cbed4d87cda878640f3e29eb (diff) |
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
14 files changed, 1179 insertions, 23 deletions
diff --git a/services/inputflinger/InputCommonConverter.cpp b/services/inputflinger/InputCommonConverter.cpp index 6ccd9e7697..417c1f333b 100644 --- a/services/inputflinger/InputCommonConverter.cpp +++ b/services/inputflinger/InputCommonConverter.cpp @@ -20,6 +20,9 @@ using namespace ::aidl::android::hardware::input; namespace android { +const static ui::Transform kIdentityTransform; +const static std::array<uint8_t, 32> kInvalidHmac{}; + static common::Source getSource(uint32_t source) { static_assert(static_cast<common::Source>(AINPUT_SOURCE_UNKNOWN) == common::Source::UNKNOWN, "SOURCE_UNKNOWN mismatch"); @@ -337,4 +340,31 @@ common::MotionEvent notifyMotionArgsToHalMotionEvent(const NotifyMotionArgs& arg return event; } +MotionEvent toMotionEvent(const NotifyMotionArgs& args, const ui::Transform* transform, + const ui::Transform* rawTransform, const std::array<uint8_t, 32>* hmac) { + if (transform == nullptr) transform = &kIdentityTransform; + if (rawTransform == nullptr) rawTransform = &kIdentityTransform; + if (hmac == nullptr) hmac = &kInvalidHmac; + + MotionEvent event; + event.initialize(args.id, args.deviceId, args.source, args.displayId, *hmac, args.action, + args.actionButton, args.flags, args.edgeFlags, args.metaState, + args.buttonState, args.classification, *transform, args.xPrecision, + args.yPrecision, args.xCursorPosition, args.yCursorPosition, *rawTransform, + args.downTime, args.eventTime, args.getPointerCount(), + args.pointerProperties.data(), args.pointerCoords.data()); + return event; +} + +KeyEvent toKeyEvent(const NotifyKeyArgs& args, int32_t repeatCount, + const std::array<uint8_t, 32>* hmac) { + if (hmac == nullptr) hmac = &kInvalidHmac; + + KeyEvent event; + event.initialize(args.id, args.deviceId, args.source, args.displayId, *hmac, args.action, + args.flags, args.keyCode, args.scanCode, args.metaState, repeatCount, + args.downTime, args.eventTime); + return event; +} + } // namespace android diff --git a/services/inputflinger/InputCommonConverter.h b/services/inputflinger/InputCommonConverter.h index 4d3b76885f..0d4cbb0c96 100644 --- a/services/inputflinger/InputCommonConverter.h +++ b/services/inputflinger/InputCommonConverter.h @@ -16,16 +16,25 @@ #pragma once +#include "InputListener.h" + #include <aidl/android/hardware/input/common/Axis.h> #include <aidl/android/hardware/input/common/MotionEvent.h> -#include "InputListener.h" +#include <input/Input.h> namespace android { -/** - * Convert from framework's NotifyMotionArgs to hidl's common::MotionEvent - */ +/** Convert from framework's NotifyMotionArgs to hidl's common::MotionEvent. */ ::aidl::android::hardware::input::common::MotionEvent notifyMotionArgsToHalMotionEvent( const NotifyMotionArgs& args); +/** Convert from NotifyMotionArgs to MotionEvent. */ +MotionEvent toMotionEvent(const NotifyMotionArgs&, const ui::Transform* transform = nullptr, + const ui::Transform* rawTransform = nullptr, + const std::array<uint8_t, 32>* hmac = nullptr); + +/** Convert from NotifyKeyArgs to KeyEvent. */ +KeyEvent toKeyEvent(const NotifyKeyArgs&, int32_t repeatCount = 0, + const std::array<uint8_t, 32>* hmac = nullptr); + } // namespace android diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp index 9c39743569..91ebe9bb5f 100644 --- a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp +++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp @@ -149,6 +149,8 @@ bool PerfettoBackend::InputEventDataSource::ruleMatches(const TraceRule& rule, // --- PerfettoBackend --- +bool PerfettoBackend::sUseInProcessBackendForTest{false}; + std::once_flag PerfettoBackend::sDataSourceRegistrationFlag{}; std::atomic<int32_t> PerfettoBackend::sNextInstanceId{1}; @@ -159,7 +161,8 @@ PerfettoBackend::PerfettoBackend(GetPackageUid getPackagesForUid) // we never unregister the InputEventDataSource. std::call_once(sDataSourceRegistrationFlag, []() { perfetto::TracingInitArgs args; - args.backends = perfetto::kSystemBackend; + args.backends = sUseInProcessBackendForTest ? perfetto::kInProcessBackend + : perfetto::kSystemBackend; perfetto::Tracing::Initialize(args); // Register our custom data source for input event tracing. @@ -175,6 +178,9 @@ void PerfettoBackend::traceMotionEvent(const TracedMotionEvent& event, const TracedEventMetadata& metadata) { InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) { auto dataSource = ctx.GetDataSourceLocked(); + if (!dataSource.valid()) { + return; + } dataSource->initializeUidMap(mGetPackageUid); if (dataSource->shouldIgnoreTracedInputEvent(event.eventType)) { return; @@ -196,6 +202,9 @@ void PerfettoBackend::traceKeyEvent(const TracedKeyEvent& event, const TracedEventMetadata& metadata) { InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) { auto dataSource = ctx.GetDataSourceLocked(); + if (!dataSource.valid()) { + return; + } dataSource->initializeUidMap(mGetPackageUid); if (dataSource->shouldIgnoreTracedInputEvent(event.eventType)) { return; @@ -217,6 +226,9 @@ void PerfettoBackend::traceWindowDispatch(const WindowDispatchArgs& dispatchArgs const TracedEventMetadata& metadata) { InputEventDataSource::Trace([&](InputEventDataSource::TraceContext ctx) { auto dataSource = ctx.GetDataSourceLocked(); + if (!dataSource.valid()) { + return; + } dataSource->initializeUidMap(mGetPackageUid); if (!dataSource->getFlags().test(TraceFlag::TRACE_DISPATCHER_WINDOW_DISPATCH)) { return; diff --git a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h index e945066dff..fdfe495c45 100644 --- a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h +++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h @@ -51,6 +51,8 @@ class PerfettoBackend : public InputTracingBackendInterface { public: using GetPackageUid = std::function<gui::Uid(std::string)>; + static bool sUseInProcessBackendForTest; + explicit PerfettoBackend(GetPackageUid); ~PerfettoBackend() override = default; @@ -61,6 +63,7 @@ public: private: // Implementation of the perfetto data source. // Each instance of the InputEventDataSource represents a different tracing session. + // Its lifecycle is controlled by perfetto. class InputEventDataSource : public perfetto::DataSource<InputEventDataSource> { public: explicit InputEventDataSource(); diff --git a/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp b/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp index 77d09cbb4b..c0a98f5e5d 100644 --- a/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp +++ b/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp @@ -84,13 +84,18 @@ void ThreadedBackend<Backend>::threadLoop() { std::unique_lock lock(mLock); base::ScopedLockAssertion assumeLocked(mLock); + setIdleStatus(true); + // Wait until we need to process more events or exit. mThreadWakeCondition.wait(lock, [&]() REQUIRES(mLock) { return mThreadExit || !mQueue.empty(); }); if (mThreadExit) { + setIdleStatus(true); return; } + setIdleStatus(false); + mQueue.swap(entries); } // release lock @@ -109,6 +114,36 @@ void ThreadedBackend<Backend>::threadLoop() { entries.clear(); } +template <typename Backend> +std::function<void()> ThreadedBackend<Backend>::getIdleWaiterForTesting() { + std::scoped_lock lock(mLock); + if (!mIdleWaiter) { + mIdleWaiter = std::make_shared<IdleWaiter>(); + } + + // Return a lambda that holds a strong reference to the idle waiter, whose lifetime can extend + // beyond this threaded backend object. + return [idleWaiter = mIdleWaiter]() { + std::unique_lock idleLock(idleWaiter->idleLock); + base::ScopedLockAssertion assumeLocked(idleWaiter->idleLock); + idleWaiter->threadIdleCondition.wait(idleLock, [&]() REQUIRES(idleWaiter->idleLock) { + return idleWaiter->isIdle; + }); + }; +} + +template <typename Backend> +void ThreadedBackend<Backend>::setIdleStatus(bool isIdle) { + if (!mIdleWaiter) { + return; + } + std::scoped_lock idleLock(mIdleWaiter->idleLock); + mIdleWaiter->isIdle = isIdle; + if (isIdle) { + mIdleWaiter->threadIdleCondition.notify_all(); + } +} + // Explicit template instantiation for the PerfettoBackend. template class ThreadedBackend<PerfettoBackend>; diff --git a/services/inputflinger/dispatcher/trace/ThreadedBackend.h b/services/inputflinger/dispatcher/trace/ThreadedBackend.h index 650a87e452..52a84c470c 100644 --- a/services/inputflinger/dispatcher/trace/ThreadedBackend.h +++ b/services/inputflinger/dispatcher/trace/ThreadedBackend.h @@ -42,6 +42,9 @@ public: void traceMotionEvent(const TracedMotionEvent&, const TracedEventMetadata&) override; void traceWindowDispatch(const WindowDispatchArgs&, const TracedEventMetadata&) override; + /** Returns a function that, when called, will block until the tracing thread is idle. */ + std::function<void()> getIdleWaiterForTesting(); + private: std::mutex mLock; bool mThreadExit GUARDED_BY(mLock){false}; @@ -52,12 +55,21 @@ private: TracedEventMetadata>; std::vector<TraceEntry> mQueue GUARDED_BY(mLock); + struct IdleWaiter { + std::mutex idleLock; + std::condition_variable threadIdleCondition; + bool isIdle GUARDED_BY(idleLock){false}; + }; + // The lazy-initialized object used to wait for the tracing thread to idle. + std::shared_ptr<IdleWaiter> mIdleWaiter GUARDED_BY(mLock); + // InputThread stops when its destructor is called. Initialize it last so that it is the // first thing to be destructed. This will guarantee the thread will not access other // members that have already been destructed. InputThread mTracerThread; void threadLoop(); + void setIdleStatus(bool isIdle) REQUIRES(mLock); }; } // namespace android::inputdispatcher::trace::impl diff --git a/services/inputflinger/include/NotifyArgsBuilders.h b/services/inputflinger/include/NotifyArgsBuilders.h index 8ffbc11a13..1bd55958d9 100644 --- a/services/inputflinger/include/NotifyArgsBuilders.h +++ b/services/inputflinger/include/NotifyArgsBuilders.h @@ -30,8 +30,11 @@ namespace android { class MotionArgsBuilder { public: - MotionArgsBuilder(int32_t action, int32_t source) { + MotionArgsBuilder(int32_t action, int32_t source) : mEventId(InputEvent::nextId()) { mAction = action; + if (mAction == AMOTION_EVENT_ACTION_CANCEL) { + addFlag(AMOTION_EVENT_FLAG_CANCELED); + } mSource = source; mEventTime = systemTime(SYSTEM_TIME_MONOTONIC); mDownTime = mEventTime; @@ -97,7 +100,7 @@ public: return *this; } - NotifyMotionArgs build() { + NotifyMotionArgs build() const { std::vector<PointerProperties> pointerProperties; std::vector<PointerCoords> pointerCoords; for (const PointerBuilder& pointer : mPointers) { @@ -106,19 +109,17 @@ public: } // Set mouse cursor position for the most common cases to avoid boilerplate. + float resolvedCursorX = mRawXCursorPosition; + float resolvedCursorY = mRawYCursorPosition; if (mSource == AINPUT_SOURCE_MOUSE && !MotionEvent::isValidCursorPosition(mRawXCursorPosition, mRawYCursorPosition) && BitSet64::hasBit(pointerCoords[0].bits, AMOTION_EVENT_AXIS_X) && BitSet64::hasBit(pointerCoords[0].bits, AMOTION_EVENT_AXIS_Y)) { - mRawXCursorPosition = pointerCoords[0].getX(); - mRawYCursorPosition = pointerCoords[0].getY(); - } - - if (mAction == AMOTION_EVENT_ACTION_CANCEL) { - addFlag(AMOTION_EVENT_FLAG_CANCELED); + resolvedCursorX = pointerCoords[0].getX(); + resolvedCursorY = pointerCoords[0].getY(); } - return {InputEvent::nextId(), + return {mEventId, mEventTime, /*readTime=*/mEventTime, mDeviceId, @@ -137,13 +138,14 @@ public: pointerCoords.data(), /*xPrecision=*/0, /*yPrecision=*/0, - mRawXCursorPosition, - mRawYCursorPosition, + resolvedCursorX, + resolvedCursorY, mDownTime, /*videoFrames=*/{}}; } private: + const int32_t mEventId; int32_t mAction; int32_t mDeviceId{DEFAULT_DEVICE_ID}; uint32_t mSource; @@ -163,7 +165,7 @@ private: class KeyArgsBuilder { public: - KeyArgsBuilder(int32_t action, int32_t source) { + KeyArgsBuilder(int32_t action, int32_t source) : mEventId(InputEvent::nextId()) { mAction = action; mSource = source; mEventTime = systemTime(SYSTEM_TIME_MONOTONIC); @@ -206,7 +208,7 @@ public: } NotifyKeyArgs build() const { - return {InputEvent::nextId(), + return {mEventId, mEventTime, /*readTime=*/mEventTime, mDeviceId, @@ -222,6 +224,7 @@ public: } private: + const int32_t mEventId; int32_t mAction; int32_t mDeviceId = DEFAULT_DEVICE_ID; uint32_t mSource; diff --git a/services/inputflinger/tests/Android.bp b/services/inputflinger/tests/Android.bp index 6ae9790aac..9b5db23151 100644 --- a/services/inputflinger/tests/Android.bp +++ b/services/inputflinger/tests/Android.bp @@ -67,6 +67,8 @@ cc_test { "InputProcessorConverter_test.cpp", "InputDispatcher_test.cpp", "InputReader_test.cpp", + "InputTraceSession.cpp", + "InputTracingTest.cpp", "InstrumentedInputReader.cpp", "LatencyTracker_test.cpp", "MultiTouchMotionAccumulator_test.cpp", diff --git a/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp b/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp index e231bccb79..1360cd0208 100644 --- a/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp +++ b/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp @@ -466,8 +466,15 @@ void FakeInputDispatcherPolicy::assertFilterInputEventWasCalledInternal( mFilteredEvent = nullptr; } -gui::Uid FakeInputDispatcherPolicy::getPackageUid(std::string) { - return gui::Uid::INVALID; +gui::Uid FakeInputDispatcherPolicy::getPackageUid(std::string pkg) { + std::scoped_lock lock(mLock); + auto it = mPackageUidMap.find(pkg); + return it != mPackageUidMap.end() ? it->second : gui::Uid::INVALID; +} + +void FakeInputDispatcherPolicy::addPackageUidMapping(std::string package, gui::Uid uid) { + std::scoped_lock lock(mLock); + mPackageUidMap.insert_or_assign(std::move(package), uid); } } // namespace android diff --git a/services/inputflinger/tests/FakeInputDispatcherPolicy.h b/services/inputflinger/tests/FakeInputDispatcherPolicy.h index d83924f202..2cc018ef05 100644 --- a/services/inputflinger/tests/FakeInputDispatcherPolicy.h +++ b/services/inputflinger/tests/FakeInputDispatcherPolicy.h @@ -115,6 +115,7 @@ public: void setUnhandledKeyHandler(std::function<std::optional<KeyEvent>(const KeyEvent&)> handler); void assertUnhandledKeyReported(int32_t keycode); void assertUnhandledKeyNotReported(); + void addPackageUidMapping(std::string package, gui::Uid uid); private: std::mutex mLock; @@ -150,6 +151,8 @@ private: std::queue<int32_t> mReportedUnhandledKeycodes GUARDED_BY(mLock); std::function<std::optional<KeyEvent>(const KeyEvent&)> mUnhandledKeyHandler GUARDED_BY(mLock); + std::map<std::string, gui::Uid> mPackageUidMap GUARDED_BY(mLock); + /** * All three ANR-related callbacks behave the same way, so we use this generic function to wait * for a specific container to become non-empty. When the container is non-empty, return the diff --git a/services/inputflinger/tests/FakeWindows.h b/services/inputflinger/tests/FakeWindows.h index c0c8975e76..26c2b4b1e7 100644 --- a/services/inputflinger/tests/FakeWindows.h +++ b/services/inputflinger/tests/FakeWindows.h @@ -157,6 +157,16 @@ public: inline void setSpy(bool spy) { mInfo.setInputConfig(InputConfig::SPY, spy); } + inline void setSecure(bool secure) { + if (secure) { + mInfo.layoutParamsFlags |= gui::WindowInfo::Flag::SECURE; + } else { + using namespace ftl::flag_operators; + mInfo.layoutParamsFlags &= ~gui::WindowInfo::Flag::SECURE; + } + mInfo.setInputConfig(InputConfig::SENSITIVE_FOR_TRACING, secure); + } + inline void setInterceptsStylus(bool interceptsStylus) { mInfo.setInputConfig(InputConfig::INTERCEPTS_STYLUS, interceptsStylus); } @@ -229,10 +239,14 @@ public: std::unique_ptr<KeyEvent> consumeKey(bool handled = true); - inline void consumeKeyEvent(const ::testing::Matcher<KeyEvent>& matcher) { + inline std::unique_ptr<KeyEvent> consumeKeyEvent(const ::testing::Matcher<KeyEvent>& matcher) { std::unique_ptr<KeyEvent> keyEvent = consumeKey(); - ASSERT_NE(nullptr, keyEvent); - ASSERT_THAT(*keyEvent, matcher); + EXPECT_NE(nullptr, keyEvent); + if (!keyEvent) { + return nullptr; + } + EXPECT_THAT(*keyEvent, matcher); + return keyEvent; } inline void consumeKeyDown(int32_t expectedDisplayId, int32_t expectedFlags = 0) { diff --git a/services/inputflinger/tests/InputTraceSession.cpp b/services/inputflinger/tests/InputTraceSession.cpp new file mode 100644 index 0000000000..32acb5f288 --- /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 diff --git a/services/inputflinger/tests/InputTraceSession.h b/services/inputflinger/tests/InputTraceSession.h new file mode 100644 index 0000000000..ed20bc8343 --- /dev/null +++ b/services/inputflinger/tests/InputTraceSession.h @@ -0,0 +1,85 @@ +/* + * 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 "FakeWindows.h" + +#include <android-base/logging.h> +#include <gtest/gtest.h> +#include <input/Input.h> +#include <perfetto/config/android/android_input_event_config.pbzero.h> +#include <perfetto/trace/android/android_input_event.pbzero.h> +#include <perfetto/trace/trace.pbzero.h> +#include <perfetto/tracing.h> +#include <variant> +#include <vector> + +namespace android { + +/** + * Tracing level constants used for adding expectations to the InputTraceSession. + */ +enum class Level { + NONE, + REDACTED, + COMPLETE, +}; + +template <typename K, typename V> +using ArrayMap = std::vector<std::pair<K, V>>; + +/** + * A scoped representation of a tracing session that is used to make assertions on the trace. + * + * When the trace session is created, an "android.input.inputevent" trace will be started + * synchronously with the given configuration. While the trace is ongoing, the caller must + * specify the events that are expected to be in the trace using the expect* methods. + * + * When the session is destroyed, the trace is stopped synchronously, and all expectations will + * be verified using the gtest framework. This acts as a strict verifier, where the verification + * will fail both if an expected event does not show up in the trace and if there is an extra + * event in the trace that was not expected. Ordering is NOT verified for any events. + */ +class InputTraceSession { +public: + explicit InputTraceSession( + std::function<void( + protozero::HeapBuffered<perfetto::protos::pbzero::AndroidInputEventConfig>&)> + configure); + + ~InputTraceSession(); + + void expectMotionTraced(Level level, const MotionEvent& event); + + void expectKeyTraced(Level level, const KeyEvent& event); + + struct WindowDispatchEvent { + std::variant<KeyEvent, MotionEvent> event; + sp<FakeWindowHandle> window; + }; + void expectDispatchTraced(Level level, const WindowDispatchEvent& event); + +private: + std::unique_ptr<perfetto::TracingSession> mPerfettoSession; + ArrayMap<WindowDispatchEvent, Level> mExpectedWindowDispatches; + ArrayMap<MotionEvent, Level> mExpectedMotions; + ArrayMap<KeyEvent, Level> mExpectedKeys; + + void verifyExpectations(const std::string& rawTrace); +}; + +} // namespace android diff --git a/services/inputflinger/tests/InputTracingTest.cpp b/services/inputflinger/tests/InputTracingTest.cpp new file mode 100644 index 0000000000..fe4d6d9a8f --- /dev/null +++ b/services/inputflinger/tests/InputTracingTest.cpp @@ -0,0 +1,732 @@ +/* + * 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 "../InputCommonConverter.h" +#include "../dispatcher/InputDispatcher.h" +#include "../dispatcher/trace/InputTracingPerfettoBackend.h" +#include "../dispatcher/trace/ThreadedBackend.h" +#include "FakeApplicationHandle.h" +#include "FakeInputDispatcherPolicy.h" +#include "FakeWindows.h" +#include "InputTraceSession.h" +#include "TestEventMatchers.h" + +#include <NotifyArgsBuilders.h> +#include <android-base/logging.h> +#include <gtest/gtest.h> +#include <input/Input.h> +#include <perfetto/trace/android/android_input_event.pbzero.h> +#include <perfetto/trace/trace.pbzero.h> +#include <private/android_filesystem_config.h> +#include <map> +#include <vector> + +namespace android::inputdispatcher::trace { + +using perfetto::protos::pbzero::AndroidInputEventConfig; + +namespace { + +constexpr int32_t DISPLAY_ID = ADISPLAY_ID_DEFAULT; + +// Ensure common actions are interchangeable between keys and motions for convenience. +static_assert(static_cast<int32_t>(AMOTION_EVENT_ACTION_DOWN) == + static_cast<int32_t>(AKEY_EVENT_ACTION_DOWN)); +static_assert(static_cast<int32_t>(AMOTION_EVENT_ACTION_UP) == + static_cast<int32_t>(AKEY_EVENT_ACTION_UP)); +constexpr int32_t ACTION_DOWN = AMOTION_EVENT_ACTION_DOWN; +constexpr int32_t ACTION_MOVE = AMOTION_EVENT_ACTION_MOVE; +constexpr int32_t ACTION_UP = AMOTION_EVENT_ACTION_UP; +constexpr int32_t ACTION_CANCEL = AMOTION_EVENT_ACTION_CANCEL; + +constexpr gui::Pid PID{1}; + +constexpr gui::Uid ALLOWED_UID_1{10012}; +constexpr gui::Uid ALLOWED_UID_2{10013}; +constexpr gui::Uid DISALLOWED_UID_1{1}; +constexpr gui::Uid DISALLOWED_UID_2{99}; +constexpr gui::Uid UNLISTED_UID{12345}; + +const std::string ALLOWED_PKG_1{"allowed.pkg.1"}; +const std::string ALLOWED_PKG_2{"allowed.pkg.2"}; +const std::string DISALLOWED_PKG_1{"disallowed.pkg.1"}; +const std::string DISALLOWED_PKG_2{"disallowed.pkg.2"}; + +const std::shared_ptr<FakeApplicationHandle> APP = std::make_shared<FakeApplicationHandle>(); + +} // namespace + +// --- InputTracingTest --- + +class InputTracingTest : public testing::Test { +protected: + std::unique_ptr<FakeInputDispatcherPolicy> mFakePolicy; + std::unique_ptr<InputDispatcher> mDispatcher; + + void SetUp() override { + impl::PerfettoBackend::sUseInProcessBackendForTest = true; + + mFakePolicy = std::make_unique<FakeInputDispatcherPolicy>(); + mFakePolicy->addPackageUidMapping(ALLOWED_PKG_1, ALLOWED_UID_1); + mFakePolicy->addPackageUidMapping(ALLOWED_PKG_2, ALLOWED_UID_2); + mFakePolicy->addPackageUidMapping(DISALLOWED_PKG_1, DISALLOWED_UID_1); + mFakePolicy->addPackageUidMapping(DISALLOWED_PKG_2, DISALLOWED_UID_2); + + auto tracingBackend = std::make_unique<impl::ThreadedBackend<impl::PerfettoBackend>>( + impl::PerfettoBackend([this](const auto& pkg) { + return static_cast<InputDispatcherPolicyInterface&>(*mFakePolicy) + .getPackageUid(pkg); + })); + mRequestTracerIdle = tracingBackend->getIdleWaiterForTesting(); + mDispatcher = std::make_unique<InputDispatcher>(*mFakePolicy, std::move(tracingBackend)); + + mDispatcher->setInputDispatchMode(/*enabled=*/true, /*frozen=*/false); + ASSERT_EQ(OK, mDispatcher->start()); + } + + void TearDown() override { + ASSERT_EQ(OK, mDispatcher->stop()); + mDispatcher.reset(); + mFakePolicy.reset(); + } + + void waitForTracerIdle() { + mDispatcher->waitForIdle(); + mRequestTracerIdle(); + } + + void setFocusedWindow(const sp<gui::WindowInfoHandle>& window) { + gui::FocusRequest request; + request.token = window->getToken(); + request.windowName = window->getName(); + request.timestamp = systemTime(SYSTEM_TIME_MONOTONIC); + request.displayId = window->getInfo()->displayId; + mDispatcher->setFocusedWindow(request); + } + + void tapAndExpect(const std::vector<const sp<FakeWindowHandle>>& windows, + Level inboundTraceLevel, Level dispatchTraceLevel, InputTraceSession& s) { + const auto down = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110)) + .build(); + mDispatcher->notifyMotion(down); + s.expectMotionTraced(inboundTraceLevel, toMotionEvent(down)); + for (const auto& window : windows) { + auto consumed = window->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + s.expectDispatchTraced(dispatchTraceLevel, {*consumed, window}); + } + + const auto up = MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110)) + .build(); + mDispatcher->notifyMotion(up); + s.expectMotionTraced(inboundTraceLevel, toMotionEvent(up)); + for (const auto& window : windows) { + auto consumed = window->consumeMotionEvent(WithMotionAction(ACTION_UP)); + s.expectDispatchTraced(dispatchTraceLevel, {*consumed, window}); + } + } + + void keypressAndExpect(const std::vector<const sp<FakeWindowHandle>>& windows, + Level inboundTraceLevel, Level dispatchTraceLevel, + InputTraceSession& s) { + const auto down = KeyArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_KEYBOARD).build(); + mDispatcher->notifyKey(down); + s.expectKeyTraced(inboundTraceLevel, toKeyEvent(down)); + for (const auto& window : windows) { + auto consumed = window->consumeKeyEvent(WithKeyAction(ACTION_DOWN)); + s.expectDispatchTraced(dispatchTraceLevel, {*consumed, window}); + } + + const auto up = KeyArgsBuilder(ACTION_UP, AINPUT_SOURCE_KEYBOARD).build(); + mDispatcher->notifyKey(up); + s.expectKeyTraced(inboundTraceLevel, toKeyEvent(up)); + for (const auto& window : windows) { + auto consumed = window->consumeKeyEvent(WithKeyAction(ACTION_UP)); + s.expectDispatchTraced(dispatchTraceLevel, {*consumed, window}); + } + } + +private: + std::function<void()> mRequestTracerIdle; +}; + +TEST_F(InputTracingTest, EmptyConfigTracesNothing) { + InputTraceSession s{[](auto& config) {}}; + + auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID); + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + setFocusedWindow(window); + window->consumeFocusEvent(true); + + tapAndExpect({window}, Level::NONE, Level::NONE, s); + keypressAndExpect({window}, Level::NONE, Level::NONE, s); + + waitForTracerIdle(); +} + +TEST_F(InputTracingTest, TraceAll) { + InputTraceSession s{ + [](auto& config) { config->set_mode(AndroidInputEventConfig::TRACE_MODE_TRACE_ALL); }}; + + auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID); + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + setFocusedWindow(window); + window->consumeFocusEvent(true); + + tapAndExpect({window}, Level::COMPLETE, Level::COMPLETE, s); + keypressAndExpect({window}, Level::COMPLETE, Level::COMPLETE, s); + + waitForTracerIdle(); +} + +TEST_F(InputTracingTest, NoRulesTracesNothing) { + InputTraceSession s{[](auto& config) { + config->set_trace_dispatcher_input_events(true); + config->set_trace_dispatcher_window_dispatch(true); + config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES); + }}; + + auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID); + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + setFocusedWindow(window); + window->consumeFocusEvent(true); + + tapAndExpect({window}, Level::NONE, Level::NONE, s); + keypressAndExpect({window}, Level::NONE, Level::NONE, s); + + waitForTracerIdle(); +} + +TEST_F(InputTracingTest, EmptyRuleMatchesEverything) { + InputTraceSession s{[](auto& config) { + config->set_trace_dispatcher_input_events(true); + config->set_trace_dispatcher_window_dispatch(true); + config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES); + // Rule: Match everything as COMPLETE + auto rule = config->add_rules(); + rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE); + }}; + + auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID); + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + setFocusedWindow(window); + window->consumeFocusEvent(true); + + tapAndExpect({window}, Level::COMPLETE, Level::COMPLETE, s); + keypressAndExpect({window}, Level::COMPLETE, Level::COMPLETE, s); + + waitForTracerIdle(); +} + +TEST_F(InputTracingTest, UnspecifiedTracelLevel) { + InputTraceSession s{[](auto& config) { + config->set_trace_dispatcher_input_events(true); + config->set_trace_dispatcher_window_dispatch(true); + config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES); + // Rule: Match everything, trace level unspecified + auto rule = config->add_rules(); + }}; + + auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID); + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + setFocusedWindow(window); + window->consumeFocusEvent(true); + + // Event is not traced by default if trace level is unspecified + tapAndExpect({window}, Level::NONE, Level::NONE, s); + keypressAndExpect({window}, Level::NONE, Level::NONE, s); + + waitForTracerIdle(); +} + +TEST_F(InputTracingTest, MatchSecureWindow) { + InputTraceSession s{[](auto& config) { + config->set_trace_dispatcher_input_events(true); + config->set_trace_dispatcher_window_dispatch(true); + config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES); + // Rule: Match secure windows as COMPLETE + auto rule = config->add_rules(); + rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE); + rule->set_match_secure(true); + }}; + + // Add a normal window and a spy window. + auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID); + auto spy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID); + spy->setSpy(true); + spy->setTrustedOverlay(true); + mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0}); + + // Since neither are secure windows, events should not be traced. + tapAndExpect({spy, window}, Level::NONE, Level::NONE, s); + + // Events should be matched as secure if any of the target windows is marked as secure. + spy->setSecure(true); + mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0}); + tapAndExpect({spy, window}, Level::COMPLETE, Level::COMPLETE, s); + + spy->setSecure(false); + window->setSecure(true); + mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0}); + tapAndExpect({spy, window}, Level::COMPLETE, Level::COMPLETE, s); + + spy->setSecure(true); + window->setSecure(true); + mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0}); + tapAndExpect({spy, window}, Level::COMPLETE, Level::COMPLETE, s); + + spy->setSecure(false); + window->setSecure(false); + mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0}); + tapAndExpect({spy, window}, Level::NONE, Level::NONE, s); + + waitForTracerIdle(); +} + +TEST_F(InputTracingTest, MatchImeConnectionActive) { + InputTraceSession s{[](auto& config) { + config->set_trace_dispatcher_input_events(true); + config->set_trace_dispatcher_window_dispatch(true); + config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES); + // Rule: Match IME Connection Active as COMPLETE + auto rule = config->add_rules(); + rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE); + rule->set_match_ime_connection_active(true); + }}; + + // Add a normal window and a spy window. + auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID); + auto spy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID); + spy->setSpy(true); + spy->setTrustedOverlay(true); + mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0}); + + // Since IME connection is not active, events should not be traced. + tapAndExpect({spy, window}, Level::NONE, Level::NONE, s); + + mDispatcher->setInputMethodConnectionIsActive(true); + tapAndExpect({spy, window}, Level::COMPLETE, Level::COMPLETE, s); + + mDispatcher->setInputMethodConnectionIsActive(false); + tapAndExpect({spy, window}, Level::NONE, Level::NONE, s); + + waitForTracerIdle(); +} + +TEST_F(InputTracingTest, MatchAllPackages) { + InputTraceSession s{[](auto& config) { + config->set_trace_dispatcher_input_events(true); + config->set_trace_dispatcher_window_dispatch(true); + config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES); + // Rule: Match all package as COMPLETE + auto rule = config->add_rules(); + rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE); + rule->add_match_all_packages(ALLOWED_PKG_1); + rule->add_match_all_packages(ALLOWED_PKG_2); + }}; + + // All windows are allowlisted. + auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID); + window->setOwnerInfo(PID, ALLOWED_UID_1); + auto spy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID); + spy->setOwnerInfo(PID, ALLOWED_UID_2); + spy->setSpy(true); + spy->setTrustedOverlay(true); + auto systemSpy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID); + systemSpy->setOwnerInfo(PID, gui::Uid{AID_SYSTEM}); + systemSpy->setSpy(true); + systemSpy->setTrustedOverlay(true); + mDispatcher->onWindowInfosChanged( + {{*systemSpy->getInfo(), *spy->getInfo(), *window->getInfo()}, {}, 0, 0}); + + tapAndExpect({systemSpy, spy, window}, Level::COMPLETE, Level::COMPLETE, s); + + // Add a disallowed spy. This will result in the event not being traced for all windows. + auto disallowedSpy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID); + disallowedSpy->setOwnerInfo(PID, DISALLOWED_UID_1); + disallowedSpy->setSpy(true); + disallowedSpy->setTrustedOverlay(true); + mDispatcher->onWindowInfosChanged({{*systemSpy->getInfo(), *spy->getInfo(), + *disallowedSpy->getInfo(), *window->getInfo()}, + {}, + 0, + 0}); + + tapAndExpect({systemSpy, spy, disallowedSpy, window}, Level::NONE, Level::NONE, s); + + // Change the owner of the disallowed spy to one for which we don't have a package mapping. + disallowedSpy->setOwnerInfo(PID, UNLISTED_UID); + mDispatcher->onWindowInfosChanged({{*systemSpy->getInfo(), *spy->getInfo(), + *disallowedSpy->getInfo(), *window->getInfo()}, + {}, + 0, + 0}); + + tapAndExpect({systemSpy, spy, disallowedSpy, window}, Level::NONE, Level::NONE, s); + + // Remove the disallowed spy. Events are traced again. + mDispatcher->onWindowInfosChanged( + {{*systemSpy->getInfo(), *spy->getInfo(), *window->getInfo()}, {}, 0, 0}); + + tapAndExpect({systemSpy, spy, window}, Level::COMPLETE, Level::COMPLETE, s); + + waitForTracerIdle(); +} + +TEST_F(InputTracingTest, MatchAnyPackages) { + InputTraceSession s{[](auto& config) { + config->set_trace_dispatcher_input_events(true); + config->set_trace_dispatcher_window_dispatch(true); + config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES); + // Rule: Match any package as COMPLETE + auto rule = config->add_rules(); + rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE); + rule->add_match_any_packages(ALLOWED_PKG_1); + rule->add_match_any_packages(ALLOWED_PKG_2); + }}; + + // Just a disallowed window. Events are not traced. + auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID); + window->setOwnerInfo(PID, DISALLOWED_UID_1); + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + + tapAndExpect({window}, Level::NONE, Level::NONE, s); + + // Add a spy for which we don't have a package mapping. Events are still not traced. + auto disallowedSpy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID); + disallowedSpy->setOwnerInfo(PID, UNLISTED_UID); + disallowedSpy->setSpy(true); + disallowedSpy->setTrustedOverlay(true); + mDispatcher->onWindowInfosChanged({{*disallowedSpy->getInfo(), *window->getInfo()}, {}, 0, 0}); + + tapAndExpect({disallowedSpy, window}, Level::NONE, Level::NONE, s); + + // Add an allowed spy. Events are now traced for all packages. + auto spy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID); + spy->setOwnerInfo(PID, ALLOWED_UID_1); + spy->setSpy(true); + spy->setTrustedOverlay(true); + mDispatcher->onWindowInfosChanged( + {{*disallowedSpy->getInfo(), *spy->getInfo(), *window->getInfo()}, {}, 0, 0}); + + tapAndExpect({disallowedSpy, spy, window}, Level::COMPLETE, Level::COMPLETE, s); + + // Add another disallowed spy. Events are still traced. + auto disallowedSpy2 = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID); + disallowedSpy2->setOwnerInfo(PID, DISALLOWED_UID_2); + disallowedSpy2->setSpy(true); + disallowedSpy2->setTrustedOverlay(true); + mDispatcher->onWindowInfosChanged({{*disallowedSpy->getInfo(), *disallowedSpy2->getInfo(), + *spy->getInfo(), *window->getInfo()}, + {}, + 0, + 0}); + + tapAndExpect({disallowedSpy, disallowedSpy2, spy, window}, Level::COMPLETE, Level::COMPLETE, s); + + waitForTracerIdle(); +} + +TEST_F(InputTracingTest, MultipleMatchersInOneRule) { + InputTraceSession s{[](auto& config) { + config->set_trace_dispatcher_input_events(true); + config->set_trace_dispatcher_window_dispatch(true); + config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES); + // Rule: Match all of the following conditions as COMPLETE + auto rule = config->add_rules(); + rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE); + rule->add_match_all_packages(ALLOWED_PKG_1); + rule->add_match_all_packages(ALLOWED_PKG_2); + rule->add_match_any_packages(ALLOWED_PKG_1); + rule->add_match_any_packages(DISALLOWED_PKG_1); + rule->set_match_secure(false); + rule->set_match_ime_connection_active(false); + }}; + + // A single window into an allowed UID. Matches all matchers. + auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID); + window->setOwnerInfo(PID, ALLOWED_UID_1); + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + + tapAndExpect({window}, Level::COMPLETE, Level::COMPLETE, s); + + // Secure window does not match. + window->setSecure(true); + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + + tapAndExpect({window}, Level::NONE, Level::NONE, s); + + // IME Connection Active does not match. + window->setSecure(false); + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + mDispatcher->setInputMethodConnectionIsActive(true); + + tapAndExpect({window}, Level::NONE, Level::NONE, s); + + // Event going to DISALLOWED_PKG_1 does not match because it's not listed in match_all_packages. + mDispatcher->setInputMethodConnectionIsActive(false); + auto disallowedSpy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID); + disallowedSpy->setOwnerInfo(PID, DISALLOWED_UID_1); + disallowedSpy->setSpy(true); + disallowedSpy->setTrustedOverlay(true); + mDispatcher->onWindowInfosChanged({{*disallowedSpy->getInfo(), *window->getInfo()}, {}, 0, 0}); + + tapAndExpect({disallowedSpy, window}, Level::NONE, Level::NONE, s); + + // Event going to ALLOWED_PKG_1 does not match because it's not listed in match_any_packages. + window->setOwnerInfo(PID, ALLOWED_UID_2); + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + + tapAndExpect({window}, Level::NONE, Level::NONE, s); + + // All conditions match. + auto spy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID); + spy->setOwnerInfo(PID, ALLOWED_UID_1); + spy->setSpy(true); + spy->setTrustedOverlay(true); + mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0}); + + tapAndExpect({spy, window}, Level::COMPLETE, Level::COMPLETE, s); + + waitForTracerIdle(); +} + +TEST_F(InputTracingTest, MultipleRulesMatchInOrder) { + InputTraceSession s{[](auto& config) { + config->set_trace_dispatcher_input_events(true); + config->set_trace_dispatcher_window_dispatch(true); + config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES); + // Rule: Don't trace secure events + auto rule1 = config->add_rules(); + rule1->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_NONE); + rule1->set_match_secure(true); + // Rule: Trace matched packages as COMPLETE when IME inactive + auto rule2 = config->add_rules(); + rule2->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE); + rule2->add_match_all_packages(ALLOWED_PKG_1); + rule2->add_match_all_packages(ALLOWED_PKG_2); + rule2->set_match_ime_connection_active(false); + // Rule: Trace the rest of the events as REDACTED + auto rule3 = config->add_rules(); + rule3->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_REDACTED); + }}; + + auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID); + window->setOwnerInfo(PID, ALLOWED_UID_1); + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + + tapAndExpect({window}, Level::COMPLETE, Level::COMPLETE, s); + + // Verify that the first rule that matches in the order that they are specified is the + // one that applies to the event. + mDispatcher->setInputMethodConnectionIsActive(true); + tapAndExpect({window}, Level::REDACTED, Level::REDACTED, s); + + mDispatcher->setInputMethodConnectionIsActive(false); + auto spy = sp<FakeWindowHandle>::make(APP, mDispatcher, "Spy", DISPLAY_ID); + spy->setOwnerInfo(PID, ALLOWED_UID_2); + spy->setSpy(true); + spy->setTrustedOverlay(true); + spy->setSecure(true); + mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0}); + + tapAndExpect({spy, window}, Level::NONE, Level::NONE, s); + + spy->setSecure(false); + mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0}); + + tapAndExpect({spy, window}, Level::COMPLETE, Level::COMPLETE, s); + + spy->setOwnerInfo(PID, DISALLOWED_UID_1); + mDispatcher->onWindowInfosChanged({{*spy->getInfo(), *window->getInfo()}, {}, 0, 0}); + + tapAndExpect({spy, window}, Level::REDACTED, Level::REDACTED, s); + + waitForTracerIdle(); +} + +TEST_F(InputTracingTest, TraceInboundEvents) { + InputTraceSession s{[](auto& config) { + // Only trace inbounds events - don't trace window dispatch + config->set_trace_dispatcher_input_events(true); + config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES); + // Rule: Trace everything as REDACTED + auto rule1 = config->add_rules(); + rule1->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_REDACTED); + }}; + + auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID); + window->setOwnerInfo(PID, ALLOWED_UID_1); + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + + // Only the inbound events are traced. No dispatch events are traced. + tapAndExpect({window}, Level::REDACTED, Level::NONE, s); + + // Notify a down event, which should be traced. + const auto down = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110)) + .build(); + s.expectMotionTraced(Level::REDACTED, toMotionEvent(down)); + mDispatcher->notifyMotion(down); + auto consumed = window->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + s.expectDispatchTraced(Level::NONE, {*consumed, window}); + + // Force a cancel event to be synthesized. This should not be traced, because only inbound + // events are requested. + mDispatcher->cancelCurrentTouch(); + consumed = window->consumeMotionEvent(WithMotionAction(ACTION_CANCEL)); + s.expectMotionTraced(Level::NONE, *consumed); + s.expectDispatchTraced(Level::NONE, {*consumed, window}); + + waitForTracerIdle(); +} + +TEST_F(InputTracingTest, TraceWindowDispatch) { + InputTraceSession s{[](auto& config) { + // Only trace window dispatch - don't trace event details + config->set_trace_dispatcher_window_dispatch(true); + config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES); + // Rule: Trace everything as REDACTED + auto rule1 = config->add_rules(); + rule1->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_REDACTED); + }}; + + auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID); + window->setOwnerInfo(PID, ALLOWED_UID_1); + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + + // Only dispatch events are traced. No inbound events are traced. + tapAndExpect({window}, Level::NONE, Level::REDACTED, s); + + // Notify a down event; the dispatch should be traced. + const auto down = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110)) + .build(); + s.expectMotionTraced(Level::NONE, toMotionEvent(down)); + mDispatcher->notifyMotion(down); + auto consumed = window->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + s.expectDispatchTraced(Level::REDACTED, {*consumed, window}); + + // Force a cancel event to be synthesized. All events that are dispatched should be traced. + mDispatcher->cancelCurrentTouch(); + consumed = window->consumeMotionEvent(WithMotionAction(ACTION_CANCEL)); + s.expectMotionTraced(Level::NONE, *consumed); + s.expectDispatchTraced(Level::REDACTED, {*consumed, window}); +} + +TEST_F(InputTracingTest, SimultaneousTracingSessions) { + auto s1 = std::make_unique<InputTraceSession>( + [](auto& config) { config->set_mode(AndroidInputEventConfig::TRACE_MODE_TRACE_ALL); }); + + auto window = sp<FakeWindowHandle>::make(APP, mDispatcher, "Window", DISPLAY_ID); + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + setFocusedWindow(window); + window->consumeFocusEvent(true); + + tapAndExpect({window}, Level::COMPLETE, Level::COMPLETE, *s1); + keypressAndExpect({window}, Level::COMPLETE, Level::COMPLETE, *s1); + + auto s2 = std::make_unique<InputTraceSession>([](auto& config) { + config->set_trace_dispatcher_input_events(true); + config->set_trace_dispatcher_window_dispatch(true); + config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES); + // Rule: Trace all events as REDACTED when IME inactive + auto rule = config->add_rules(); + rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_REDACTED); + rule->set_match_ime_connection_active(false); + }); + + auto s3 = std::make_unique<InputTraceSession>([](auto& config) { + // Only trace window dispatch + config->set_trace_dispatcher_window_dispatch(true); + config->set_mode(AndroidInputEventConfig::TRACE_MODE_USE_RULES); + // Rule: Trace non-secure events as COMPLETE + auto rule = config->add_rules(); + rule->set_trace_level(AndroidInputEventConfig::TRACE_LEVEL_COMPLETE); + rule->set_match_secure(false); + }); + + // Down event should be recorded on all traces. + const auto down = MotionArgsBuilder(ACTION_DOWN, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110)) + .build(); + mDispatcher->notifyMotion(down); + s1->expectMotionTraced(Level::COMPLETE, toMotionEvent(down)); + s2->expectMotionTraced(Level::REDACTED, toMotionEvent(down)); + s3->expectMotionTraced(Level::NONE, toMotionEvent(down)); + auto consumed = window->consumeMotionEvent(WithMotionAction(ACTION_DOWN)); + s1->expectDispatchTraced(Level::COMPLETE, {*consumed, window}); + s2->expectDispatchTraced(Level::REDACTED, {*consumed, window}); + s3->expectDispatchTraced(Level::COMPLETE, {*consumed, window}); + + // Move event when IME is active. + mDispatcher->setInputMethodConnectionIsActive(true); + const auto move1 = MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110)) + .build(); + mDispatcher->notifyMotion(move1); + s1->expectMotionTraced(Level::COMPLETE, toMotionEvent(move1)); + s2->expectMotionTraced(Level::NONE, toMotionEvent(move1)); + s3->expectMotionTraced(Level::NONE, toMotionEvent(move1)); + consumed = window->consumeMotionEvent(WithMotionAction(ACTION_MOVE)); + s1->expectDispatchTraced(Level::COMPLETE, {*consumed, window}); + s2->expectDispatchTraced(Level::NONE, {*consumed, window}); + s3->expectDispatchTraced(Level::COMPLETE, {*consumed, window}); + + // Move event after window became secure. + mDispatcher->setInputMethodConnectionIsActive(false); + window->setSecure(true); + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + const auto move2 = MotionArgsBuilder(ACTION_MOVE, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110)) + .build(); + mDispatcher->notifyMotion(move2); + s1->expectMotionTraced(Level::COMPLETE, toMotionEvent(move2)); + s2->expectMotionTraced(Level::REDACTED, toMotionEvent(move2)); + s3->expectMotionTraced(Level::NONE, toMotionEvent(move2)); + consumed = window->consumeMotionEvent(WithMotionAction(ACTION_MOVE)); + s1->expectDispatchTraced(Level::COMPLETE, {*consumed, window}); + s2->expectDispatchTraced(Level::REDACTED, {*consumed, window}); + s3->expectDispatchTraced(Level::NONE, {*consumed, window}); + + waitForTracerIdle(); + s2.reset(); + + // Up event. + window->setSecure(false); + mDispatcher->onWindowInfosChanged({{*window->getInfo()}, {}, 0, 0}); + const auto up = MotionArgsBuilder(ACTION_UP, AINPUT_SOURCE_TOUCHSCREEN) + .pointer(PointerBuilder(0, ToolType::FINGER).x(100).y(110)) + .build(); + mDispatcher->notifyMotion(up); + s1->expectMotionTraced(Level::COMPLETE, toMotionEvent(up)); + s3->expectMotionTraced(Level::NONE, toMotionEvent(up)); + consumed = window->consumeMotionEvent(WithMotionAction(ACTION_UP)); + s1->expectDispatchTraced(Level::COMPLETE, {*consumed, window}); + s3->expectDispatchTraced(Level::COMPLETE, {*consumed, window}); + + waitForTracerIdle(); + s3.reset(); + + tapAndExpect({window}, Level::COMPLETE, Level::COMPLETE, *s1); + keypressAndExpect({window}, Level::COMPLETE, Level::COMPLETE, *s1); + + waitForTracerIdle(); + s1.reset(); +} + +} // namespace android::inputdispatcher::trace |