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