1 /*
2 * Copyright 2023 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 #define LOG_TAG "InputDeviceMetricsCollector"
18 #include "InputDeviceMetricsCollector.h"
19
20 #include "InputDeviceMetricsSource.h"
21
22 #include <android-base/stringprintf.h>
23 #include <input/PrintTools.h>
24
25 namespace android {
26
27 using android::base::StringPrintf;
28 using std::chrono::nanoseconds;
29 using std::chrono_literals::operator""ns;
30
31 namespace {
32
33 constexpr nanoseconds DEFAULT_USAGE_SESSION_TIMEOUT = std::chrono::minutes(2);
34
35 /**
36 * Log debug messages about metrics events logged to statsd.
37 * Enable this via "adb shell setprop log.tag.InputDeviceMetricsCollector DEBUG" (requires restart)
38 */
39 const bool DEBUG = __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG, ANDROID_LOG_INFO);
40
41 constexpr size_t INTERACTIONS_QUEUE_CAPACITY = 500;
42
linuxBusToInputDeviceBusEnum(int32_t linuxBus,bool isUsiStylus)43 int32_t linuxBusToInputDeviceBusEnum(int32_t linuxBus, bool isUsiStylus) {
44 if (isUsiStylus) {
45 // This is a stylus connected over the Universal Stylus Initiative (USI) protocol.
46 // For metrics purposes, we treat this protocol as a separate bus.
47 return util::INPUT_DEVICE_USAGE_REPORTED__DEVICE_BUS__USI;
48 }
49
50 // When adding cases to this switch, also add them to the copy of this method in
51 // TouchpadInputMapper.cpp.
52 // TODO(b/286394420): deduplicate this method with the one in TouchpadInputMapper.cpp.
53 switch (linuxBus) {
54 case BUS_USB:
55 return util::INPUT_DEVICE_USAGE_REPORTED__DEVICE_BUS__USB;
56 case BUS_BLUETOOTH:
57 return util::INPUT_DEVICE_USAGE_REPORTED__DEVICE_BUS__BLUETOOTH;
58 default:
59 return util::INPUT_DEVICE_USAGE_REPORTED__DEVICE_BUS__OTHER;
60 }
61 }
62
63 class : public InputDeviceMetricsLogger {
getCurrentTime()64 nanoseconds getCurrentTime() override { return nanoseconds(systemTime(SYSTEM_TIME_MONOTONIC)); }
65
logInputDeviceUsageReported(const MetricsDeviceInfo & info,const DeviceUsageReport & report)66 void logInputDeviceUsageReported(const MetricsDeviceInfo& info,
67 const DeviceUsageReport& report) override {
68 const int32_t durationMillis =
69 std::chrono::duration_cast<std::chrono::milliseconds>(report.usageDuration).count();
70 const static std::vector<int32_t> empty;
71
72 ALOGD_IF(DEBUG, "Usage session reported for device id: %d", info.deviceId);
73 ALOGD_IF(DEBUG, " Total duration: %dms", durationMillis);
74 ALOGD_IF(DEBUG, " Source breakdown:");
75
76 std::vector<int32_t> sources;
77 std::vector<int32_t> durationsPerSource;
78 for (auto& [src, dur] : report.sourceBreakdown) {
79 sources.push_back(ftl::to_underlying(src));
80 int32_t durMillis = std::chrono::duration_cast<std::chrono::milliseconds>(dur).count();
81 durationsPerSource.emplace_back(durMillis);
82 ALOGD_IF(DEBUG, " - usageSource: %s\t duration: %dms",
83 ftl::enum_string(src).c_str(), durMillis);
84 }
85
86 ALOGD_IF(DEBUG, " Uid breakdown:");
87
88 std::vector<int32_t> uids;
89 std::vector<int32_t> durationsPerUid;
90 for (auto& [uid, dur] : report.uidBreakdown) {
91 uids.push_back(uid.val());
92 int32_t durMillis = std::chrono::duration_cast<std::chrono::milliseconds>(dur).count();
93 durationsPerUid.push_back(durMillis);
94 ALOGD_IF(DEBUG, " - uid: %s\t duration: %dms", uid.toString().c_str(),
95 durMillis);
96 }
97 util::stats_write(util::INPUTDEVICE_USAGE_REPORTED, info.vendor, info.product, info.version,
98 linuxBusToInputDeviceBusEnum(info.bus, info.isUsiStylus), durationMillis,
99 sources, durationsPerSource, uids, durationsPerUid);
100 }
101 } sStatsdLogger;
102
isIgnoredInputDeviceId(int32_t deviceId)103 bool isIgnoredInputDeviceId(int32_t deviceId) {
104 switch (deviceId) {
105 case INVALID_INPUT_DEVICE_ID:
106 case VIRTUAL_KEYBOARD_ID:
107 return true;
108 default:
109 return false;
110 }
111 }
112
113 } // namespace
114
InputDeviceMetricsCollector(InputListenerInterface & listener)115 InputDeviceMetricsCollector::InputDeviceMetricsCollector(InputListenerInterface& listener)
116 : InputDeviceMetricsCollector(listener, sStatsdLogger, DEFAULT_USAGE_SESSION_TIMEOUT) {}
117
InputDeviceMetricsCollector(InputListenerInterface & listener,InputDeviceMetricsLogger & logger,nanoseconds usageSessionTimeout)118 InputDeviceMetricsCollector::InputDeviceMetricsCollector(InputListenerInterface& listener,
119 InputDeviceMetricsLogger& logger,
120 nanoseconds usageSessionTimeout)
121 : mNextListener(listener),
122 mLogger(logger),
123 mUsageSessionTimeout(usageSessionTimeout),
124 mInteractionsQueue(INTERACTIONS_QUEUE_CAPACITY) {}
125
notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs & args)126 void InputDeviceMetricsCollector::notifyInputDevicesChanged(
127 const NotifyInputDevicesChangedArgs& args) {
128 {
129 std::scoped_lock lock(mLock);
130 reportCompletedSessions();
131 onInputDevicesChanged(args.inputDeviceInfos);
132 }
133 mNextListener.notify(args);
134 }
135
notifyConfigurationChanged(const NotifyConfigurationChangedArgs & args)136 void InputDeviceMetricsCollector::notifyConfigurationChanged(
137 const NotifyConfigurationChangedArgs& args) {
138 {
139 std::scoped_lock lock(mLock);
140 reportCompletedSessions();
141 }
142 mNextListener.notify(args);
143 }
144
notifyKey(const NotifyKeyArgs & args)145 void InputDeviceMetricsCollector::notifyKey(const NotifyKeyArgs& args) {
146 {
147 std::scoped_lock lock(mLock);
148 reportCompletedSessions();
149 const SourceProvider getSources = [&args](const MetricsDeviceInfo& info) {
150 return std::set{getUsageSourceForKeyArgs(info.keyboardType, args)};
151 };
152 onInputDeviceUsage(DeviceId{args.deviceId}, nanoseconds(args.eventTime), getSources);
153 }
154 mNextListener.notify(args);
155 }
156
notifyMotion(const NotifyMotionArgs & args)157 void InputDeviceMetricsCollector::notifyMotion(const NotifyMotionArgs& args) {
158 {
159 std::scoped_lock lock(mLock);
160 reportCompletedSessions();
161 onInputDeviceUsage(DeviceId{args.deviceId}, nanoseconds(args.eventTime),
162 [&args](const auto&) { return getUsageSourcesForMotionArgs(args); });
163 }
164
165 mNextListener.notify(args);
166 }
167
notifySwitch(const NotifySwitchArgs & args)168 void InputDeviceMetricsCollector::notifySwitch(const NotifySwitchArgs& args) {
169 {
170 std::scoped_lock lock(mLock);
171 reportCompletedSessions();
172 }
173 mNextListener.notify(args);
174 }
175
notifySensor(const NotifySensorArgs & args)176 void InputDeviceMetricsCollector::notifySensor(const NotifySensorArgs& args) {
177 {
178 std::scoped_lock lock(mLock);
179 reportCompletedSessions();
180 }
181 mNextListener.notify(args);
182 }
183
notifyVibratorState(const NotifyVibratorStateArgs & args)184 void InputDeviceMetricsCollector::notifyVibratorState(const NotifyVibratorStateArgs& args) {
185 {
186 std::scoped_lock lock(mLock);
187 reportCompletedSessions();
188 }
189 mNextListener.notify(args);
190 }
191
notifyDeviceReset(const NotifyDeviceResetArgs & args)192 void InputDeviceMetricsCollector::notifyDeviceReset(const NotifyDeviceResetArgs& args) {
193 {
194 std::scoped_lock lock(mLock);
195 reportCompletedSessions();
196 }
197 mNextListener.notify(args);
198 }
199
notifyPointerCaptureChanged(const NotifyPointerCaptureChangedArgs & args)200 void InputDeviceMetricsCollector::notifyPointerCaptureChanged(
201 const NotifyPointerCaptureChangedArgs& args) {
202 {
203 std::scoped_lock lock(mLock);
204 reportCompletedSessions();
205 }
206 mNextListener.notify(args);
207 }
208
notifyDeviceInteraction(int32_t deviceId,nsecs_t timestamp,const std::set<Uid> & uids)209 void InputDeviceMetricsCollector::notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp,
210 const std::set<Uid>& uids) {
211 if (isIgnoredInputDeviceId(deviceId)) {
212 return;
213 }
214 std::scoped_lock lock(mLock);
215 mInteractionsQueue.push(DeviceId{deviceId}, timestamp, uids);
216 }
217
dump(std::string & dump)218 void InputDeviceMetricsCollector::dump(std::string& dump) {
219 std::scoped_lock lock(mLock);
220 dump += "InputDeviceMetricsCollector:\n";
221
222 dump += " Logged device IDs: " + dumpMapKeys(mLoggedDeviceInfos, &toString) + "\n";
223 dump += " Devices with active usage sessions: " +
224 dumpMapKeys(mActiveUsageSessions, &toString) + "\n";
225 }
226
monitor()227 void InputDeviceMetricsCollector::monitor() {
228 std::scoped_lock lock(mLock);
229 }
230
onInputDevicesChanged(const std::vector<InputDeviceInfo> & infos)231 void InputDeviceMetricsCollector::onInputDevicesChanged(const std::vector<InputDeviceInfo>& infos) {
232 std::map<DeviceId, MetricsDeviceInfo> newDeviceInfos;
233
234 for (const InputDeviceInfo& info : infos) {
235 if (isIgnoredInputDeviceId(info.getId())) {
236 continue;
237 }
238 const auto& i = info.getIdentifier();
239 newDeviceInfos.emplace(info.getId(),
240 MetricsDeviceInfo{
241 .deviceId = info.getId(),
242 .vendor = i.vendor,
243 .product = i.product,
244 .version = i.version,
245 .bus = i.bus,
246 .isUsiStylus = info.getUsiVersion().has_value(),
247 .keyboardType = info.getKeyboardType(),
248 });
249 }
250
251 for (auto [deviceId, info] : mLoggedDeviceInfos) {
252 if (newDeviceInfos.count(deviceId) != 0) {
253 continue;
254 }
255 onInputDeviceRemoved(deviceId, info);
256 }
257
258 std::swap(newDeviceInfos, mLoggedDeviceInfos);
259 }
260
onInputDeviceRemoved(DeviceId deviceId,const MetricsDeviceInfo & info)261 void InputDeviceMetricsCollector::onInputDeviceRemoved(DeviceId deviceId,
262 const MetricsDeviceInfo& info) {
263 auto it = mActiveUsageSessions.find(deviceId);
264 if (it == mActiveUsageSessions.end()) {
265 return;
266 }
267 // Report usage for that device if there is an active session.
268 auto& [_, activeSession] = *it;
269 mLogger.logInputDeviceUsageReported(info, activeSession.finishSession());
270 mActiveUsageSessions.erase(it);
271
272 // We don't remove this from mLoggedDeviceInfos because it will be updated in
273 // onInputDevicesChanged().
274 }
275
onInputDeviceUsage(DeviceId deviceId,nanoseconds eventTime,const SourceProvider & getSources)276 void InputDeviceMetricsCollector::onInputDeviceUsage(DeviceId deviceId, nanoseconds eventTime,
277 const SourceProvider& getSources) {
278 auto infoIt = mLoggedDeviceInfos.find(deviceId);
279 if (infoIt == mLoggedDeviceInfos.end()) {
280 // Do not track usage for devices that are not logged.
281 return;
282 }
283
284 auto [sessionIt, _] =
285 mActiveUsageSessions.try_emplace(deviceId, mUsageSessionTimeout, eventTime);
286 for (InputDeviceUsageSource source : getSources(infoIt->second)) {
287 sessionIt->second.recordUsage(eventTime, source);
288 }
289 }
290
onInputDeviceInteraction(const Interaction & interaction)291 void InputDeviceMetricsCollector::onInputDeviceInteraction(const Interaction& interaction) {
292 auto activeSessionIt = mActiveUsageSessions.find(std::get<DeviceId>(interaction));
293 if (activeSessionIt == mActiveUsageSessions.end()) {
294 return;
295 }
296
297 activeSessionIt->second.recordInteraction(interaction);
298 }
299
reportCompletedSessions()300 void InputDeviceMetricsCollector::reportCompletedSessions() {
301 // Process all pending interactions.
302 for (auto interaction = mInteractionsQueue.pop(); interaction;
303 interaction = mInteractionsQueue.pop()) {
304 onInputDeviceInteraction(*interaction);
305 }
306
307 const auto currentTime = mLogger.getCurrentTime();
308 std::vector<DeviceId> completedUsageSessions;
309
310 // Process usages for all active session to determine if any sessions have expired.
311 for (auto& [deviceId, activeSession] : mActiveUsageSessions) {
312 if (activeSession.checkIfCompletedAt(currentTime)) {
313 completedUsageSessions.emplace_back(deviceId);
314 }
315 }
316
317 // Close out and log all expired usage sessions.
318 for (DeviceId deviceId : completedUsageSessions) {
319 const auto infoIt = mLoggedDeviceInfos.find(deviceId);
320 LOG_ALWAYS_FATAL_IF(infoIt == mLoggedDeviceInfos.end());
321
322 auto activeSessionIt = mActiveUsageSessions.find(deviceId);
323 LOG_ALWAYS_FATAL_IF(activeSessionIt == mActiveUsageSessions.end());
324 auto& [_, activeSession] = *activeSessionIt;
325 mLogger.logInputDeviceUsageReported(infoIt->second, activeSession.finishSession());
326 mActiveUsageSessions.erase(activeSessionIt);
327 }
328 }
329
330 // --- InputDeviceMetricsCollector::ActiveSession ---
331
ActiveSession(nanoseconds usageSessionTimeout,nanoseconds startTime)332 InputDeviceMetricsCollector::ActiveSession::ActiveSession(nanoseconds usageSessionTimeout,
333 nanoseconds startTime)
334 : mUsageSessionTimeout(usageSessionTimeout), mDeviceSession({startTime, startTime}) {}
335
recordUsage(nanoseconds eventTime,InputDeviceUsageSource source)336 void InputDeviceMetricsCollector::ActiveSession::recordUsage(nanoseconds eventTime,
337 InputDeviceUsageSource source) {
338 // We assume that event times for subsequent events are always monotonically increasing for each
339 // input device.
340 auto [activeSourceIt, inserted] =
341 mActiveSessionsBySource.try_emplace(source, eventTime, eventTime);
342 if (!inserted) {
343 activeSourceIt->second.end = eventTime;
344 }
345 mDeviceSession.end = eventTime;
346 }
347
recordInteraction(const Interaction & interaction)348 void InputDeviceMetricsCollector::ActiveSession::recordInteraction(const Interaction& interaction) {
349 const auto sessionExpiryTime = mDeviceSession.end + mUsageSessionTimeout;
350 const auto timestamp = std::get<nanoseconds>(interaction);
351 if (timestamp >= sessionExpiryTime) {
352 // This interaction occurred after the device's current active session is set to expire.
353 // Ignore it.
354 return;
355 }
356
357 for (Uid uid : std::get<std::set<Uid>>(interaction)) {
358 auto [activeUidIt, inserted] = mActiveSessionsByUid.try_emplace(uid, timestamp, timestamp);
359 if (!inserted) {
360 activeUidIt->second.end = timestamp;
361 }
362 }
363 }
364
checkIfCompletedAt(nanoseconds timestamp)365 bool InputDeviceMetricsCollector::ActiveSession::checkIfCompletedAt(nanoseconds timestamp) {
366 const auto sessionExpiryTime = timestamp - mUsageSessionTimeout;
367 std::vector<InputDeviceUsageSource> completedSourceSessionsForDevice;
368 for (auto& [source, session] : mActiveSessionsBySource) {
369 if (session.end <= sessionExpiryTime) {
370 completedSourceSessionsForDevice.emplace_back(source);
371 }
372 }
373 for (InputDeviceUsageSource source : completedSourceSessionsForDevice) {
374 auto it = mActiveSessionsBySource.find(source);
375 const auto& [_, session] = *it;
376 mSourceUsageBreakdown.emplace_back(source, session.end - session.start);
377 mActiveSessionsBySource.erase(it);
378 }
379
380 std::vector<Uid> completedUidSessionsForDevice;
381 for (auto& [uid, session] : mActiveSessionsByUid) {
382 if (session.end <= sessionExpiryTime) {
383 completedUidSessionsForDevice.emplace_back(uid);
384 }
385 }
386 for (Uid uid : completedUidSessionsForDevice) {
387 auto it = mActiveSessionsByUid.find(uid);
388 const auto& [_, session] = *it;
389 mUidUsageBreakdown.emplace_back(uid, session.end - session.start);
390 mActiveSessionsByUid.erase(it);
391 }
392
393 // This active session has expired if there are no more active source sessions tracked.
394 return mActiveSessionsBySource.empty();
395 }
396
397 InputDeviceMetricsLogger::DeviceUsageReport
finishSession()398 InputDeviceMetricsCollector::ActiveSession::finishSession() {
399 const auto deviceUsageDuration = mDeviceSession.end - mDeviceSession.start;
400
401 for (const auto& [source, sourceSession] : mActiveSessionsBySource) {
402 mSourceUsageBreakdown.emplace_back(source, sourceSession.end - sourceSession.start);
403 }
404 mActiveSessionsBySource.clear();
405
406 for (const auto& [uid, uidSession] : mActiveSessionsByUid) {
407 mUidUsageBreakdown.emplace_back(uid, uidSession.end - uidSession.start);
408 }
409 mActiveSessionsByUid.clear();
410
411 return {deviceUsageDuration, mSourceUsageBreakdown, mUidUsageBreakdown};
412 }
413
414 } // namespace android
415