blob: 71f37176c3ddc4ef656e5bdc9945b5d64bf808b5 [file] [log] [blame]
Prabir Pradhanaddf8e92023-04-06 00:28:48 +00001/*
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
Prabir Pradhanae10ee62023-05-12 19:44:18 +000020#include "KeyCodeClassifications.h"
21
Prabir Pradhan852db892023-04-06 22:16:44 +000022#include <android-base/stringprintf.h>
23#include <input/PrintTools.h>
24#include <linux/input.h>
Prabir Pradhan852db892023-04-06 22:16:44 +000025
Prabir Pradhanaddf8e92023-04-06 00:28:48 +000026namespace android {
27
Prabir Pradhan852db892023-04-06 22:16:44 +000028using android::base::StringPrintf;
29using std::chrono::nanoseconds;
30
31namespace {
32
33constexpr nanoseconds DEFAULT_USAGE_SESSION_TIMEOUT = std::chrono::seconds(5);
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 */
39const bool DEBUG = __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG, ANDROID_LOG_INFO);
40
41int32_t linuxBusToInputDeviceBusEnum(int32_t linuxBus) {
42 switch (linuxBus) {
43 case BUS_USB:
44 return util::INPUT_DEVICE_USAGE_REPORTED__DEVICE_BUS__USB;
45 case BUS_BLUETOOTH:
46 return util::INPUT_DEVICE_USAGE_REPORTED__DEVICE_BUS__BLUETOOTH;
47 default:
48 return util::INPUT_DEVICE_USAGE_REPORTED__DEVICE_BUS__OTHER;
49 }
50}
51
52class : public InputDeviceMetricsLogger {
53 nanoseconds getCurrentTime() override { return nanoseconds(systemTime(SYSTEM_TIME_MONOTONIC)); }
54
55 void logInputDeviceUsageReported(const InputDeviceIdentifier& identifier,
56 nanoseconds sessionDuration) override {
57 const int32_t durationMillis =
58 std::chrono::duration_cast<std::chrono::milliseconds>(sessionDuration).count();
59 const static std::vector<int32_t> empty;
60
61 ALOGD_IF(DEBUG, "Usage session reported for device: %s", identifier.name.c_str());
62 ALOGD_IF(DEBUG, " Total duration: %dms", durationMillis);
63
64 util::stats_write(util::INPUTDEVICE_USAGE_REPORTED, identifier.vendor, identifier.product,
65 identifier.version, linuxBusToInputDeviceBusEnum(identifier.bus),
66 durationMillis, /*usage_sources=*/empty,
67 /*usage_durations_per_source=*/empty, /*uids=*/empty,
68 /*usage_durations_per_uid=*/empty);
69 }
70} sStatsdLogger;
71
72bool isIgnoredInputDeviceId(int32_t deviceId) {
73 switch (deviceId) {
74 case INVALID_INPUT_DEVICE_ID:
75 case VIRTUAL_KEYBOARD_ID:
76 return true;
77 default:
78 return false;
79 }
80}
81
82} // namespace
83
Prabir Pradhanae10ee62023-05-12 19:44:18 +000084InputDeviceUsageSource getUsageSourceForKeyArgs(const InputDeviceInfo& info,
85 const NotifyKeyArgs& keyArgs) {
86 if (!isFromSource(keyArgs.source, AINPUT_SOURCE_KEYBOARD)) {
87 return InputDeviceUsageSource::UNKNOWN;
88 }
89
90 if (isFromSource(keyArgs.source, AINPUT_SOURCE_DPAD) &&
91 DPAD_ALL_KEYCODES.count(keyArgs.keyCode) != 0) {
92 return InputDeviceUsageSource::DPAD;
93 }
94
95 if (isFromSource(keyArgs.source, AINPUT_SOURCE_GAMEPAD) &&
96 GAMEPAD_KEYCODES.count(keyArgs.keyCode) != 0) {
97 return InputDeviceUsageSource::GAMEPAD;
98 }
99
100 if (info.getKeyboardType() == AINPUT_KEYBOARD_TYPE_ALPHABETIC) {
101 return InputDeviceUsageSource::KEYBOARD;
102 }
103
104 return InputDeviceUsageSource::BUTTONS;
105}
106
107std::set<InputDeviceUsageSource> getUsageSourcesForMotionArgs(const NotifyMotionArgs& motionArgs) {
108 LOG_ALWAYS_FATAL_IF(motionArgs.pointerCount < 1, "Received motion args without pointers");
109 std::set<InputDeviceUsageSource> sources;
110
111 for (uint32_t i = 0; i < motionArgs.pointerCount; i++) {
112 const auto toolType = motionArgs.pointerProperties[i].toolType;
113 if (isFromSource(motionArgs.source, AINPUT_SOURCE_MOUSE)) {
114 if (toolType == ToolType::MOUSE) {
115 sources.emplace(InputDeviceUsageSource::MOUSE);
116 continue;
117 }
118 if (toolType == ToolType::FINGER) {
119 sources.emplace(InputDeviceUsageSource::TOUCHPAD);
120 continue;
121 }
122 if (isStylusToolType(toolType)) {
123 sources.emplace(InputDeviceUsageSource::STYLUS_INDIRECT);
124 continue;
125 }
126 }
127 if (isFromSource(motionArgs.source, AINPUT_SOURCE_MOUSE_RELATIVE) &&
128 toolType == ToolType::MOUSE) {
129 sources.emplace(InputDeviceUsageSource::MOUSE_CAPTURED);
130 continue;
131 }
132 if (isFromSource(motionArgs.source, AINPUT_SOURCE_TOUCHPAD) &&
133 toolType == ToolType::FINGER) {
134 sources.emplace(InputDeviceUsageSource::TOUCHPAD_CAPTURED);
135 continue;
136 }
137 if (isFromSource(motionArgs.source, AINPUT_SOURCE_BLUETOOTH_STYLUS) &&
138 isStylusToolType(toolType)) {
139 sources.emplace(InputDeviceUsageSource::STYLUS_FUSED);
140 continue;
141 }
142 if (isFromSource(motionArgs.source, AINPUT_SOURCE_STYLUS) && isStylusToolType(toolType)) {
143 sources.emplace(InputDeviceUsageSource::STYLUS_DIRECT);
144 continue;
145 }
146 if (isFromSource(motionArgs.source, AINPUT_SOURCE_TOUCH_NAVIGATION)) {
147 sources.emplace(InputDeviceUsageSource::TOUCH_NAVIGATION);
148 continue;
149 }
150 if (isFromSource(motionArgs.source, AINPUT_SOURCE_JOYSTICK)) {
151 sources.emplace(InputDeviceUsageSource::JOYSTICK);
152 continue;
153 }
154 if (isFromSource(motionArgs.source, AINPUT_SOURCE_ROTARY_ENCODER)) {
155 sources.emplace(InputDeviceUsageSource::ROTARY_ENCODER);
156 continue;
157 }
158 if (isFromSource(motionArgs.source, AINPUT_SOURCE_TRACKBALL)) {
159 sources.emplace(InputDeviceUsageSource::TRACKBALL);
160 continue;
161 }
162 if (isFromSource(motionArgs.source, AINPUT_SOURCE_TOUCHSCREEN)) {
163 sources.emplace(InputDeviceUsageSource::TOUCHSCREEN);
164 continue;
165 }
166 sources.emplace(InputDeviceUsageSource::UNKNOWN);
167 }
168
169 return sources;
170}
171
172// --- InputDeviceMetricsCollector ---
173
Prabir Pradhanaddf8e92023-04-06 00:28:48 +0000174InputDeviceMetricsCollector::InputDeviceMetricsCollector(InputListenerInterface& listener)
Prabir Pradhan852db892023-04-06 22:16:44 +0000175 : InputDeviceMetricsCollector(listener, sStatsdLogger, DEFAULT_USAGE_SESSION_TIMEOUT) {}
176
177InputDeviceMetricsCollector::InputDeviceMetricsCollector(InputListenerInterface& listener,
178 InputDeviceMetricsLogger& logger,
179 nanoseconds usageSessionTimeout)
180 : mNextListener(listener), mLogger(logger), mUsageSessionTimeout(usageSessionTimeout) {}
Prabir Pradhanaddf8e92023-04-06 00:28:48 +0000181
182void InputDeviceMetricsCollector::notifyInputDevicesChanged(
183 const NotifyInputDevicesChangedArgs& args) {
Prabir Pradhan852db892023-04-06 22:16:44 +0000184 processUsages();
185 onInputDevicesChanged(args.inputDeviceInfos);
Prabir Pradhanaddf8e92023-04-06 00:28:48 +0000186 mNextListener.notify(args);
187}
188
189void InputDeviceMetricsCollector::notifyConfigurationChanged(
190 const NotifyConfigurationChangedArgs& args) {
Prabir Pradhan852db892023-04-06 22:16:44 +0000191 processUsages();
Prabir Pradhanaddf8e92023-04-06 00:28:48 +0000192 mNextListener.notify(args);
193}
194
195void InputDeviceMetricsCollector::notifyKey(const NotifyKeyArgs& args) {
Prabir Pradhan852db892023-04-06 22:16:44 +0000196 processUsages();
197 onInputDeviceUsage(DeviceId{args.deviceId}, nanoseconds(args.eventTime));
198
Prabir Pradhanaddf8e92023-04-06 00:28:48 +0000199 mNextListener.notify(args);
200}
201
202void InputDeviceMetricsCollector::notifyMotion(const NotifyMotionArgs& args) {
Prabir Pradhan852db892023-04-06 22:16:44 +0000203 processUsages();
204 onInputDeviceUsage(DeviceId{args.deviceId}, nanoseconds(args.eventTime));
205
Prabir Pradhanaddf8e92023-04-06 00:28:48 +0000206 mNextListener.notify(args);
207}
208
209void InputDeviceMetricsCollector::notifySwitch(const NotifySwitchArgs& args) {
Prabir Pradhan852db892023-04-06 22:16:44 +0000210 processUsages();
Prabir Pradhanaddf8e92023-04-06 00:28:48 +0000211 mNextListener.notify(args);
212}
213
214void InputDeviceMetricsCollector::notifySensor(const NotifySensorArgs& args) {
Prabir Pradhan852db892023-04-06 22:16:44 +0000215 processUsages();
Prabir Pradhanaddf8e92023-04-06 00:28:48 +0000216 mNextListener.notify(args);
217}
218
219void InputDeviceMetricsCollector::notifyVibratorState(const NotifyVibratorStateArgs& args) {
Prabir Pradhan852db892023-04-06 22:16:44 +0000220 processUsages();
Prabir Pradhanaddf8e92023-04-06 00:28:48 +0000221 mNextListener.notify(args);
222}
223
224void InputDeviceMetricsCollector::notifyDeviceReset(const NotifyDeviceResetArgs& args) {
Prabir Pradhan852db892023-04-06 22:16:44 +0000225 processUsages();
Prabir Pradhanaddf8e92023-04-06 00:28:48 +0000226 mNextListener.notify(args);
227}
228
229void InputDeviceMetricsCollector::notifyPointerCaptureChanged(
230 const NotifyPointerCaptureChangedArgs& args) {
Prabir Pradhan852db892023-04-06 22:16:44 +0000231 processUsages();
Prabir Pradhanaddf8e92023-04-06 00:28:48 +0000232 mNextListener.notify(args);
233}
234
235void InputDeviceMetricsCollector::dump(std::string& dump) {
236 dump += "InputDeviceMetricsCollector:\n";
Prabir Pradhan852db892023-04-06 22:16:44 +0000237
238 dump += " Logged device IDs: " + dumpMapKeys(mLoggedDeviceInfos, &toString) + "\n";
239 dump += " Devices with active usage sessions: " +
240 dumpMapKeys(mActiveUsageSessions, &toString) + "\n";
241}
242
243void InputDeviceMetricsCollector::onInputDevicesChanged(const std::vector<InputDeviceInfo>& infos) {
244 std::map<DeviceId, InputDeviceIdentifier> newDeviceIds;
245
246 for (const InputDeviceInfo& info : infos) {
247 if (isIgnoredInputDeviceId(info.getId())) {
248 continue;
249 }
250 newDeviceIds.emplace(info.getId(), info.getIdentifier());
251 }
252
253 for (auto [deviceId, identifier] : mLoggedDeviceInfos) {
254 if (newDeviceIds.count(deviceId) != 0) {
255 continue;
256 }
257 onInputDeviceRemoved(deviceId, identifier);
258 }
259
260 std::swap(newDeviceIds, mLoggedDeviceInfos);
261}
262
263void InputDeviceMetricsCollector::onInputDeviceRemoved(DeviceId deviceId,
264 const InputDeviceIdentifier& identifier) {
265 // Report usage for that device if there is an active session.
266 auto it = mActiveUsageSessions.find(deviceId);
267 if (it != mActiveUsageSessions.end()) {
268 mLogger.logInputDeviceUsageReported(identifier, it->second.end - it->second.start);
269 mActiveUsageSessions.erase(it);
270 }
271 // We don't remove this from mLoggedDeviceInfos because it will be updated in
272 // onInputDevicesChanged().
273}
274
275void InputDeviceMetricsCollector::onInputDeviceUsage(DeviceId deviceId, nanoseconds eventTime) {
276 if (mLoggedDeviceInfos.count(deviceId) == 0) {
277 // Do not track usage for devices that are not logged.
278 return;
279 }
280
281 auto [it, inserted] = mActiveUsageSessions.try_emplace(deviceId, eventTime, eventTime);
282 if (!inserted) {
283 it->second.end = eventTime;
284 }
285}
286
287void InputDeviceMetricsCollector::processUsages() {
288 const auto usageSessionExpiryTime = mLogger.getCurrentTime() - mUsageSessionTimeout;
289
290 std::vector<DeviceId> completedUsageSessions;
291
292 for (const auto& [deviceId, usageSession] : mActiveUsageSessions) {
293 if (usageSession.end <= usageSessionExpiryTime) {
294 completedUsageSessions.emplace_back(deviceId);
295 }
296 }
297
298 for (DeviceId deviceId : completedUsageSessions) {
299 const auto it = mLoggedDeviceInfos.find(deviceId);
300 LOG_ALWAYS_FATAL_IF(it == mLoggedDeviceInfos.end());
301
302 const auto& session = mActiveUsageSessions[deviceId];
303 mLogger.logInputDeviceUsageReported(it->second, session.end - session.start);
304
305 mActiveUsageSessions.erase(deviceId);
306 }
Prabir Pradhanaddf8e92023-04-06 00:28:48 +0000307}
308
309} // namespace android