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/InputCommonConverter.cpp b/services/inputflinger/InputCommonConverter.cpp
index 6ccd9e7..417c1f3 100644
--- a/services/inputflinger/InputCommonConverter.cpp
+++ b/services/inputflinger/InputCommonConverter.cpp
@@ -20,6 +20,9 @@
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 @@
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 4d3b768..0d4cbb0 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 9c39743..91ebe9b 100644
--- a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp
+++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.cpp
@@ -149,6 +149,8 @@
// --- PerfettoBackend ---
+bool PerfettoBackend::sUseInProcessBackendForTest{false};
+
std::once_flag PerfettoBackend::sDataSourceRegistrationFlag{};
std::atomic<int32_t> PerfettoBackend::sNextInstanceId{1};
@@ -159,7 +161,8 @@
// 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 @@
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 @@
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 @@
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 e945066..fdfe495 100644
--- a/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h
+++ b/services/inputflinger/dispatcher/trace/InputTracingPerfettoBackend.h
@@ -51,6 +51,8 @@
public:
using GetPackageUid = std::function<gui::Uid(std::string)>;
+ static bool sUseInProcessBackendForTest;
+
explicit PerfettoBackend(GetPackageUid);
~PerfettoBackend() override = default;
@@ -61,6 +63,7 @@
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 77d09cb..c0a98f5 100644
--- a/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp
+++ b/services/inputflinger/dispatcher/trace/ThreadedBackend.cpp
@@ -84,13 +84,18 @@
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 @@
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 650a87e..52a84c4 100644
--- a/services/inputflinger/dispatcher/trace/ThreadedBackend.h
+++ b/services/inputflinger/dispatcher/trace/ThreadedBackend.h
@@ -42,6 +42,9 @@
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 @@
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 8ffbc11..1bd5595 100644
--- a/services/inputflinger/include/NotifyArgsBuilders.h
+++ b/services/inputflinger/include/NotifyArgsBuilders.h
@@ -30,8 +30,11 @@
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 @@
return *this;
}
- NotifyMotionArgs build() {
+ NotifyMotionArgs build() const {
std::vector<PointerProperties> pointerProperties;
std::vector<PointerCoords> pointerCoords;
for (const PointerBuilder& pointer : mPointers) {
@@ -106,19 +109,17 @@
}
// 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();
+ resolvedCursorX = pointerCoords[0].getX();
+ resolvedCursorY = pointerCoords[0].getY();
}
- if (mAction == AMOTION_EVENT_ACTION_CANCEL) {
- addFlag(AMOTION_EVENT_FLAG_CANCELED);
- }
-
- return {InputEvent::nextId(),
+ return {mEventId,
mEventTime,
/*readTime=*/mEventTime,
mDeviceId,
@@ -137,13 +138,14 @@
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 @@
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 @@
}
NotifyKeyArgs build() const {
- return {InputEvent::nextId(),
+ return {mEventId,
mEventTime,
/*readTime=*/mEventTime,
mDeviceId,
@@ -222,6 +224,7 @@
}
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 6ae9790..9b5db23 100644
--- a/services/inputflinger/tests/Android.bp
+++ b/services/inputflinger/tests/Android.bp
@@ -67,6 +67,8 @@
"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 e231bcc..1360cd0 100644
--- a/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp
+++ b/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp
@@ -466,8 +466,15 @@
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 d83924f..2cc018e 100644
--- a/services/inputflinger/tests/FakeInputDispatcherPolicy.h
+++ b/services/inputflinger/tests/FakeInputDispatcherPolicy.h
@@ -115,6 +115,7 @@
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 @@
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 c0c8975..26c2b4b 100644
--- a/services/inputflinger/tests/FakeWindows.h
+++ b/services/inputflinger/tests/FakeWindows.h
@@ -157,6 +157,16 @@
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 @@
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 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
diff --git a/services/inputflinger/tests/InputTraceSession.h b/services/inputflinger/tests/InputTraceSession.h
new file mode 100644
index 0000000..ed20bc8
--- /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 0000000..fe4d6d9
--- /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