1 /*
2 * Copyright 2024 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #pragma once
18
19 #include <perfetto/config/android/android_input_event_config.pbzero.h>
20 #include <perfetto/trace/android/android_input_event.pbzero.h>
21
22 #include "InputTracingBackendInterface.h"
23 #include "InputTracingPerfettoBackendConfig.h"
24
25 namespace proto = perfetto::protos::pbzero;
26
27 namespace android::inputdispatcher::trace {
28
29 namespace internal {
30
31 using namespace ftl::flag_operators;
32
33 // The trace config to use for maximal tracing.
34 const impl::TraceConfig CONFIG_TRACE_ALL{
35 .flags = impl::TraceFlag::TRACE_DISPATCHER_INPUT_EVENTS |
36 impl::TraceFlag::TRACE_DISPATCHER_WINDOW_DISPATCH,
37 .rules = {impl::TraceRule{.level = impl::TraceLevel::TRACE_LEVEL_COMPLETE,
38 .matchAllPackages = {},
39 .matchAnyPackages = {},
40 .matchSecure{},
41 .matchImeConnectionActive = {}}},
42 };
43
44 template <typename Pointer>
writeAxisValue(Pointer * pointer,int32_t axis,float value,bool isRedacted)45 void writeAxisValue(Pointer* pointer, int32_t axis, float value, bool isRedacted) {
46 auto* axisEntry = pointer->add_axis_value();
47 axisEntry->set_axis(axis);
48
49 if (!isRedacted) {
50 axisEntry->set_value(value);
51 }
52 }
53
54 } // namespace internal
55
56 /**
57 * Write traced events into Perfetto protos.
58 *
59 * This class is templated so that the logic can be tested while substituting the proto classes
60 * auto-generated by Perfetto's pbzero library with mock implementations.
61 */
62 template <typename ProtoMotion, typename ProtoKey, typename ProtoDispatch,
63 typename ProtoConfigDecoder>
64 class AndroidInputEventProtoConverter {
65 public:
toProtoMotionEvent(const TracedMotionEvent & event,ProtoMotion & outProto,bool isRedacted)66 static void toProtoMotionEvent(const TracedMotionEvent& event, ProtoMotion& outProto,
67 bool isRedacted) {
68 outProto.set_event_id(event.id);
69 outProto.set_event_time_nanos(event.eventTime);
70 outProto.set_down_time_nanos(event.downTime);
71 outProto.set_source(event.source);
72 outProto.set_action(event.action);
73 outProto.set_device_id(event.deviceId);
74 outProto.set_display_id(event.displayId.val());
75 outProto.set_classification(static_cast<int32_t>(event.classification));
76 outProto.set_flags(event.flags);
77 outProto.set_policy_flags(event.policyFlags);
78 outProto.set_button_state(event.buttonState);
79 outProto.set_action_button(event.actionButton);
80
81 if (!isRedacted) {
82 outProto.set_cursor_position_x(event.xCursorPosition);
83 outProto.set_cursor_position_y(event.yCursorPosition);
84 outProto.set_meta_state(event.metaState);
85 outProto.set_precision_x(event.xPrecision);
86 outProto.set_precision_y(event.yPrecision);
87 }
88
89 for (uint32_t i = 0; i < event.pointerProperties.size(); i++) {
90 auto* pointer = outProto.add_pointer();
91
92 const auto& props = event.pointerProperties[i];
93 pointer->set_pointer_id(props.id);
94 pointer->set_tool_type(static_cast<int32_t>(props.toolType));
95
96 const auto& coords = event.pointerCoords[i];
97 auto bits = BitSet64(coords.bits);
98
99 if (isFromSource(event.source, AINPUT_SOURCE_CLASS_POINTER)) {
100 // Always include the X and Y axes for pointer events, since the
101 // bits will not be marked if the value is 0.
102 for (const auto axis : {AMOTION_EVENT_AXIS_X, AMOTION_EVENT_AXIS_Y}) {
103 if (!bits.hasBit(axis)) {
104 internal::writeAxisValue(pointer, axis, 0.0f, isRedacted);
105 }
106 }
107 }
108
109 for (int32_t axisIndex = 0; !bits.isEmpty(); axisIndex++) {
110 const auto axis = bits.clearFirstMarkedBit();
111 internal::writeAxisValue(pointer, axis, coords.values[axisIndex], isRedacted);
112 }
113 }
114 }
115
toProtoKeyEvent(const TracedKeyEvent & event,ProtoKey & outProto,bool isRedacted)116 static void toProtoKeyEvent(const TracedKeyEvent& event, ProtoKey& outProto, bool isRedacted) {
117 outProto.set_event_id(event.id);
118 outProto.set_event_time_nanos(event.eventTime);
119 outProto.set_down_time_nanos(event.downTime);
120 outProto.set_source(event.source);
121 outProto.set_action(event.action);
122 outProto.set_device_id(event.deviceId);
123 outProto.set_display_id(event.displayId.val());
124 outProto.set_repeat_count(event.repeatCount);
125 outProto.set_flags(event.flags);
126 outProto.set_policy_flags(event.policyFlags);
127
128 if (!isRedacted) {
129 outProto.set_key_code(event.keyCode);
130 outProto.set_scan_code(event.scanCode);
131 outProto.set_meta_state(event.metaState);
132 }
133 }
134
toProtoWindowDispatchEvent(const WindowDispatchArgs & args,ProtoDispatch & outProto,bool isRedacted)135 static void toProtoWindowDispatchEvent(const WindowDispatchArgs& args, ProtoDispatch& outProto,
136 bool isRedacted) {
137 std::visit([&](auto entry) { outProto.set_event_id(entry.id); }, args.eventEntry);
138 outProto.set_vsync_id(args.vsyncId);
139 outProto.set_window_id(args.windowId);
140 outProto.set_resolved_flags(args.resolvedFlags);
141
142 if (isRedacted) {
143 return;
144 }
145 if (auto* motion = std::get_if<TracedMotionEvent>(&args.eventEntry); motion != nullptr) {
146 for (size_t i = 0; i < motion->pointerProperties.size(); i++) {
147 auto* pointerProto = outProto.add_dispatched_pointer();
148 pointerProto->set_pointer_id(motion->pointerProperties[i].id);
149 const auto& coords = motion->pointerCoords[i];
150 const auto rawXY =
151 MotionEvent::calculateTransformedXY(motion->source, args.rawTransform,
152 coords.getXYValue());
153 if (coords.getXYValue() != rawXY) {
154 // These values are only traced if they were modified by the raw transform
155 // to save space. Trace consumers should be aware of this optimization.
156 pointerProto->set_x_in_display(rawXY.x);
157 pointerProto->set_y_in_display(rawXY.y);
158 }
159
160 const auto coordsInWindow =
161 MotionEvent::calculateTransformedCoords(motion->source, motion->flags,
162 args.transform, coords);
163 auto bits = BitSet64(coords.bits);
164 for (int32_t axisIndex = 0; !bits.isEmpty(); axisIndex++) {
165 const uint32_t axis = bits.clearFirstMarkedBit();
166 const float axisValueInWindow = coordsInWindow.values[axisIndex];
167 // Only values that are modified by the window transform are traced.
168 if (coords.values[axisIndex] != axisValueInWindow) {
169 auto* axisEntry = pointerProto->add_axis_value_in_window();
170 axisEntry->set_axis(axis);
171 axisEntry->set_value(axisValueInWindow);
172 }
173 }
174 }
175 }
176 }
177
parseConfig(ProtoConfigDecoder & protoConfig)178 static impl::TraceConfig parseConfig(ProtoConfigDecoder& protoConfig) {
179 if (protoConfig.has_mode() &&
180 protoConfig.mode() == proto::AndroidInputEventConfig::TRACE_MODE_TRACE_ALL) {
181 // User has requested the preset for maximal tracing
182 return internal::CONFIG_TRACE_ALL;
183 }
184
185 impl::TraceConfig config;
186
187 // Parse trace flags
188 if (protoConfig.has_trace_dispatcher_input_events() &&
189 protoConfig.trace_dispatcher_input_events()) {
190 config.flags |= impl::TraceFlag::TRACE_DISPATCHER_INPUT_EVENTS;
191 }
192 if (protoConfig.has_trace_dispatcher_window_dispatch() &&
193 protoConfig.trace_dispatcher_window_dispatch()) {
194 config.flags |= impl::TraceFlag::TRACE_DISPATCHER_WINDOW_DISPATCH;
195 }
196
197 // Parse trace rules
198 auto rulesIt = protoConfig.rules();
199 while (rulesIt) {
200 proto::AndroidInputEventConfig::TraceRule::Decoder protoRule{rulesIt->as_bytes()};
201 config.rules.emplace_back();
202 auto& rule = config.rules.back();
203
204 rule.level = protoRule.has_trace_level()
205 ? static_cast<impl::TraceLevel>(protoRule.trace_level())
206 : impl::TraceLevel::TRACE_LEVEL_NONE;
207
208 if (protoRule.has_match_all_packages()) {
209 auto pkgIt = protoRule.match_all_packages();
210 while (pkgIt) {
211 rule.matchAllPackages.emplace_back(pkgIt->as_std_string());
212 pkgIt++;
213 }
214 }
215
216 if (protoRule.has_match_any_packages()) {
217 auto pkgIt = protoRule.match_any_packages();
218 while (pkgIt) {
219 rule.matchAnyPackages.emplace_back(pkgIt->as_std_string());
220 pkgIt++;
221 }
222 }
223
224 if (protoRule.has_match_secure()) {
225 rule.matchSecure = protoRule.match_secure();
226 }
227
228 if (protoRule.has_match_ime_connection_active()) {
229 rule.matchImeConnectionActive = protoRule.match_ime_connection_active();
230 }
231
232 rulesIt++;
233 }
234
235 return config;
236 }
237 };
238
239 } // namespace android::inputdispatcher::trace
240