Loading include/input/InputDevice.h +3 −0 Original line number Diff line number Diff line Loading @@ -68,6 +68,9 @@ struct InputDeviceIdentifier { * while conforming to the filename limitations. */ std::string getCanonicalName() const; bool operator==(const InputDeviceIdentifier&) const = default; bool operator!=(const InputDeviceIdentifier&) const = default; }; /* Types of input device sensors. Keep sync with core/java/android/hardware/Sensor.java */ Loading include/input/PrintTools.h +14 −0 Original line number Diff line number Diff line Loading @@ -87,6 +87,20 @@ std::string dumpMap(const std::map<K, V>& map, std::string (*keyToString)(const return out; } /** * Convert map keys to string. The keys of the map should be integral type. */ template <typename K, typename V> std::string dumpMapKeys(const std::map<K, V>& map, std::string (*keyToString)(const K&) = constToString) { std::string out; for (const auto& [k, _] : map) { out += out.empty() ? "{" : ", "; out += keyToString(k); } return out.empty() ? "{}" : (out + "}"); } /** * Convert a vector to a string. The values of the vector should be of a type supported by * constToString. Loading services/inputflinger/InputDeviceMetricsCollector.cpp +151 −1 Original line number Diff line number Diff line Loading @@ -17,52 +17,202 @@ #define LOG_TAG "InputDeviceMetricsCollector" #include "InputDeviceMetricsCollector.h" #include <android-base/stringprintf.h> #include <input/PrintTools.h> #include <linux/input.h> #include <statslog.h> namespace android { using android::base::StringPrintf; using std::chrono::nanoseconds; namespace { constexpr nanoseconds DEFAULT_USAGE_SESSION_TIMEOUT = std::chrono::seconds(5); /** * Log debug messages about metrics events logged to statsd. * Enable this via "adb shell setprop log.tag.InputDeviceMetricsCollector DEBUG" (requires restart) */ const bool DEBUG = __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG, ANDROID_LOG_INFO); int32_t linuxBusToInputDeviceBusEnum(int32_t linuxBus) { switch (linuxBus) { case BUS_USB: return util::INPUT_DEVICE_USAGE_REPORTED__DEVICE_BUS__USB; case BUS_BLUETOOTH: return util::INPUT_DEVICE_USAGE_REPORTED__DEVICE_BUS__BLUETOOTH; default: return util::INPUT_DEVICE_USAGE_REPORTED__DEVICE_BUS__OTHER; } } class : public InputDeviceMetricsLogger { nanoseconds getCurrentTime() override { return nanoseconds(systemTime(SYSTEM_TIME_MONOTONIC)); } void logInputDeviceUsageReported(const InputDeviceIdentifier& identifier, nanoseconds sessionDuration) override { const int32_t durationMillis = std::chrono::duration_cast<std::chrono::milliseconds>(sessionDuration).count(); const static std::vector<int32_t> empty; ALOGD_IF(DEBUG, "Usage session reported for device: %s", identifier.name.c_str()); ALOGD_IF(DEBUG, " Total duration: %dms", durationMillis); util::stats_write(util::INPUTDEVICE_USAGE_REPORTED, identifier.vendor, identifier.product, identifier.version, linuxBusToInputDeviceBusEnum(identifier.bus), durationMillis, /*usage_sources=*/empty, /*usage_durations_per_source=*/empty, /*uids=*/empty, /*usage_durations_per_uid=*/empty); } } sStatsdLogger; bool isIgnoredInputDeviceId(int32_t deviceId) { switch (deviceId) { case INVALID_INPUT_DEVICE_ID: case VIRTUAL_KEYBOARD_ID: return true; default: return false; } } } // namespace InputDeviceMetricsCollector::InputDeviceMetricsCollector(InputListenerInterface& listener) : mNextListener(listener){}; : InputDeviceMetricsCollector(listener, sStatsdLogger, DEFAULT_USAGE_SESSION_TIMEOUT) {} InputDeviceMetricsCollector::InputDeviceMetricsCollector(InputListenerInterface& listener, InputDeviceMetricsLogger& logger, nanoseconds usageSessionTimeout) : mNextListener(listener), mLogger(logger), mUsageSessionTimeout(usageSessionTimeout) {} void InputDeviceMetricsCollector::notifyInputDevicesChanged( const NotifyInputDevicesChangedArgs& args) { processUsages(); onInputDevicesChanged(args.inputDeviceInfos); mNextListener.notify(args); } void InputDeviceMetricsCollector::notifyConfigurationChanged( const NotifyConfigurationChangedArgs& args) { processUsages(); mNextListener.notify(args); } void InputDeviceMetricsCollector::notifyKey(const NotifyKeyArgs& args) { processUsages(); onInputDeviceUsage(DeviceId{args.deviceId}, nanoseconds(args.eventTime)); mNextListener.notify(args); } void InputDeviceMetricsCollector::notifyMotion(const NotifyMotionArgs& args) { processUsages(); onInputDeviceUsage(DeviceId{args.deviceId}, nanoseconds(args.eventTime)); mNextListener.notify(args); } void InputDeviceMetricsCollector::notifySwitch(const NotifySwitchArgs& args) { processUsages(); mNextListener.notify(args); } void InputDeviceMetricsCollector::notifySensor(const NotifySensorArgs& args) { processUsages(); mNextListener.notify(args); } void InputDeviceMetricsCollector::notifyVibratorState(const NotifyVibratorStateArgs& args) { processUsages(); mNextListener.notify(args); } void InputDeviceMetricsCollector::notifyDeviceReset(const NotifyDeviceResetArgs& args) { processUsages(); mNextListener.notify(args); } void InputDeviceMetricsCollector::notifyPointerCaptureChanged( const NotifyPointerCaptureChangedArgs& args) { processUsages(); mNextListener.notify(args); } void InputDeviceMetricsCollector::dump(std::string& dump) { dump += "InputDeviceMetricsCollector:\n"; dump += " Logged device IDs: " + dumpMapKeys(mLoggedDeviceInfos, &toString) + "\n"; dump += " Devices with active usage sessions: " + dumpMapKeys(mActiveUsageSessions, &toString) + "\n"; } void InputDeviceMetricsCollector::onInputDevicesChanged(const std::vector<InputDeviceInfo>& infos) { std::map<DeviceId, InputDeviceIdentifier> newDeviceIds; for (const InputDeviceInfo& info : infos) { if (isIgnoredInputDeviceId(info.getId())) { continue; } newDeviceIds.emplace(info.getId(), info.getIdentifier()); } for (auto [deviceId, identifier] : mLoggedDeviceInfos) { if (newDeviceIds.count(deviceId) != 0) { continue; } onInputDeviceRemoved(deviceId, identifier); } std::swap(newDeviceIds, mLoggedDeviceInfos); } void InputDeviceMetricsCollector::onInputDeviceRemoved(DeviceId deviceId, const InputDeviceIdentifier& identifier) { // Report usage for that device if there is an active session. auto it = mActiveUsageSessions.find(deviceId); if (it != mActiveUsageSessions.end()) { mLogger.logInputDeviceUsageReported(identifier, it->second.end - it->second.start); mActiveUsageSessions.erase(it); } // We don't remove this from mLoggedDeviceInfos because it will be updated in // onInputDevicesChanged(). } void InputDeviceMetricsCollector::onInputDeviceUsage(DeviceId deviceId, nanoseconds eventTime) { if (mLoggedDeviceInfos.count(deviceId) == 0) { // Do not track usage for devices that are not logged. return; } auto [it, inserted] = mActiveUsageSessions.try_emplace(deviceId, eventTime, eventTime); if (!inserted) { it->second.end = eventTime; } } void InputDeviceMetricsCollector::processUsages() { const auto usageSessionExpiryTime = mLogger.getCurrentTime() - mUsageSessionTimeout; std::vector<DeviceId> completedUsageSessions; for (const auto& [deviceId, usageSession] : mActiveUsageSessions) { if (usageSession.end <= usageSessionExpiryTime) { completedUsageSessions.emplace_back(deviceId); } } for (DeviceId deviceId : completedUsageSessions) { const auto it = mLoggedDeviceInfos.find(deviceId); LOG_ALWAYS_FATAL_IF(it == mLoggedDeviceInfos.end()); const auto& session = mActiveUsageSessions[deviceId]; mLogger.logInputDeviceUsageReported(it->second, session.end - session.start); mActiveUsageSessions.erase(deviceId); } } } // namespace android services/inputflinger/InputDeviceMetricsCollector.h +45 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,12 @@ #include "InputListener.h" #include <ftl/mixins.h> #include <input/InputDevice.h> #include <chrono> #include <map> #include <vector> namespace android { /** Loading @@ -34,11 +40,24 @@ public: virtual void dump(std::string& dump) = 0; }; /** The logging interface for the metrics collector, injected for testing. */ class InputDeviceMetricsLogger { public: virtual std::chrono::nanoseconds getCurrentTime() = 0; virtual void logInputDeviceUsageReported(const InputDeviceIdentifier&, std::chrono::nanoseconds duration) = 0; virtual ~InputDeviceMetricsLogger() = default; }; class InputDeviceMetricsCollector : public InputDeviceMetricsCollectorInterface { public: explicit InputDeviceMetricsCollector(InputListenerInterface& listener); ~InputDeviceMetricsCollector() override = default; // Test constructor InputDeviceMetricsCollector(InputListenerInterface& listener, InputDeviceMetricsLogger& logger, std::chrono::nanoseconds usageSessionTimeout); void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override; void notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) override; void notifyKey(const NotifyKeyArgs& args) override; Loading @@ -53,6 +72,32 @@ public: private: InputListenerInterface& mNextListener; InputDeviceMetricsLogger& mLogger; const std::chrono::nanoseconds mUsageSessionTimeout; // Type-safe wrapper for input device id. struct DeviceId : ftl::Constructible<DeviceId, std::int32_t>, ftl::Equatable<DeviceId>, ftl::Orderable<DeviceId> { using Constructible::Constructible; }; static std::string toString(const DeviceId& id) { return std::to_string(ftl::to_underlying(id)); } std::map<DeviceId, InputDeviceIdentifier> mLoggedDeviceInfos; struct UsageSession { std::chrono::nanoseconds start; std::chrono::nanoseconds end; }; // The input devices that currently have active usage sessions. std::map<DeviceId, UsageSession> mActiveUsageSessions; void onInputDevicesChanged(const std::vector<InputDeviceInfo>& infos); void onInputDeviceRemoved(DeviceId deviceId, const InputDeviceIdentifier& identifier); void onInputDeviceUsage(DeviceId deviceId, std::chrono::nanoseconds eventTime); void processUsages(); }; } // namespace android services/inputflinger/tests/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -47,6 +47,7 @@ cc_test { "FocusResolver_test.cpp", "GestureConverter_test.cpp", "HardwareStateConverter_test.cpp", "InputDeviceMetricsCollector_test.cpp", "InputMapperTest.cpp", "InputProcessor_test.cpp", "InputProcessorConverter_test.cpp", Loading Loading
include/input/InputDevice.h +3 −0 Original line number Diff line number Diff line Loading @@ -68,6 +68,9 @@ struct InputDeviceIdentifier { * while conforming to the filename limitations. */ std::string getCanonicalName() const; bool operator==(const InputDeviceIdentifier&) const = default; bool operator!=(const InputDeviceIdentifier&) const = default; }; /* Types of input device sensors. Keep sync with core/java/android/hardware/Sensor.java */ Loading
include/input/PrintTools.h +14 −0 Original line number Diff line number Diff line Loading @@ -87,6 +87,20 @@ std::string dumpMap(const std::map<K, V>& map, std::string (*keyToString)(const return out; } /** * Convert map keys to string. The keys of the map should be integral type. */ template <typename K, typename V> std::string dumpMapKeys(const std::map<K, V>& map, std::string (*keyToString)(const K&) = constToString) { std::string out; for (const auto& [k, _] : map) { out += out.empty() ? "{" : ", "; out += keyToString(k); } return out.empty() ? "{}" : (out + "}"); } /** * Convert a vector to a string. The values of the vector should be of a type supported by * constToString. Loading
services/inputflinger/InputDeviceMetricsCollector.cpp +151 −1 Original line number Diff line number Diff line Loading @@ -17,52 +17,202 @@ #define LOG_TAG "InputDeviceMetricsCollector" #include "InputDeviceMetricsCollector.h" #include <android-base/stringprintf.h> #include <input/PrintTools.h> #include <linux/input.h> #include <statslog.h> namespace android { using android::base::StringPrintf; using std::chrono::nanoseconds; namespace { constexpr nanoseconds DEFAULT_USAGE_SESSION_TIMEOUT = std::chrono::seconds(5); /** * Log debug messages about metrics events logged to statsd. * Enable this via "adb shell setprop log.tag.InputDeviceMetricsCollector DEBUG" (requires restart) */ const bool DEBUG = __android_log_is_loggable(ANDROID_LOG_DEBUG, LOG_TAG, ANDROID_LOG_INFO); int32_t linuxBusToInputDeviceBusEnum(int32_t linuxBus) { switch (linuxBus) { case BUS_USB: return util::INPUT_DEVICE_USAGE_REPORTED__DEVICE_BUS__USB; case BUS_BLUETOOTH: return util::INPUT_DEVICE_USAGE_REPORTED__DEVICE_BUS__BLUETOOTH; default: return util::INPUT_DEVICE_USAGE_REPORTED__DEVICE_BUS__OTHER; } } class : public InputDeviceMetricsLogger { nanoseconds getCurrentTime() override { return nanoseconds(systemTime(SYSTEM_TIME_MONOTONIC)); } void logInputDeviceUsageReported(const InputDeviceIdentifier& identifier, nanoseconds sessionDuration) override { const int32_t durationMillis = std::chrono::duration_cast<std::chrono::milliseconds>(sessionDuration).count(); const static std::vector<int32_t> empty; ALOGD_IF(DEBUG, "Usage session reported for device: %s", identifier.name.c_str()); ALOGD_IF(DEBUG, " Total duration: %dms", durationMillis); util::stats_write(util::INPUTDEVICE_USAGE_REPORTED, identifier.vendor, identifier.product, identifier.version, linuxBusToInputDeviceBusEnum(identifier.bus), durationMillis, /*usage_sources=*/empty, /*usage_durations_per_source=*/empty, /*uids=*/empty, /*usage_durations_per_uid=*/empty); } } sStatsdLogger; bool isIgnoredInputDeviceId(int32_t deviceId) { switch (deviceId) { case INVALID_INPUT_DEVICE_ID: case VIRTUAL_KEYBOARD_ID: return true; default: return false; } } } // namespace InputDeviceMetricsCollector::InputDeviceMetricsCollector(InputListenerInterface& listener) : mNextListener(listener){}; : InputDeviceMetricsCollector(listener, sStatsdLogger, DEFAULT_USAGE_SESSION_TIMEOUT) {} InputDeviceMetricsCollector::InputDeviceMetricsCollector(InputListenerInterface& listener, InputDeviceMetricsLogger& logger, nanoseconds usageSessionTimeout) : mNextListener(listener), mLogger(logger), mUsageSessionTimeout(usageSessionTimeout) {} void InputDeviceMetricsCollector::notifyInputDevicesChanged( const NotifyInputDevicesChangedArgs& args) { processUsages(); onInputDevicesChanged(args.inputDeviceInfos); mNextListener.notify(args); } void InputDeviceMetricsCollector::notifyConfigurationChanged( const NotifyConfigurationChangedArgs& args) { processUsages(); mNextListener.notify(args); } void InputDeviceMetricsCollector::notifyKey(const NotifyKeyArgs& args) { processUsages(); onInputDeviceUsage(DeviceId{args.deviceId}, nanoseconds(args.eventTime)); mNextListener.notify(args); } void InputDeviceMetricsCollector::notifyMotion(const NotifyMotionArgs& args) { processUsages(); onInputDeviceUsage(DeviceId{args.deviceId}, nanoseconds(args.eventTime)); mNextListener.notify(args); } void InputDeviceMetricsCollector::notifySwitch(const NotifySwitchArgs& args) { processUsages(); mNextListener.notify(args); } void InputDeviceMetricsCollector::notifySensor(const NotifySensorArgs& args) { processUsages(); mNextListener.notify(args); } void InputDeviceMetricsCollector::notifyVibratorState(const NotifyVibratorStateArgs& args) { processUsages(); mNextListener.notify(args); } void InputDeviceMetricsCollector::notifyDeviceReset(const NotifyDeviceResetArgs& args) { processUsages(); mNextListener.notify(args); } void InputDeviceMetricsCollector::notifyPointerCaptureChanged( const NotifyPointerCaptureChangedArgs& args) { processUsages(); mNextListener.notify(args); } void InputDeviceMetricsCollector::dump(std::string& dump) { dump += "InputDeviceMetricsCollector:\n"; dump += " Logged device IDs: " + dumpMapKeys(mLoggedDeviceInfos, &toString) + "\n"; dump += " Devices with active usage sessions: " + dumpMapKeys(mActiveUsageSessions, &toString) + "\n"; } void InputDeviceMetricsCollector::onInputDevicesChanged(const std::vector<InputDeviceInfo>& infos) { std::map<DeviceId, InputDeviceIdentifier> newDeviceIds; for (const InputDeviceInfo& info : infos) { if (isIgnoredInputDeviceId(info.getId())) { continue; } newDeviceIds.emplace(info.getId(), info.getIdentifier()); } for (auto [deviceId, identifier] : mLoggedDeviceInfos) { if (newDeviceIds.count(deviceId) != 0) { continue; } onInputDeviceRemoved(deviceId, identifier); } std::swap(newDeviceIds, mLoggedDeviceInfos); } void InputDeviceMetricsCollector::onInputDeviceRemoved(DeviceId deviceId, const InputDeviceIdentifier& identifier) { // Report usage for that device if there is an active session. auto it = mActiveUsageSessions.find(deviceId); if (it != mActiveUsageSessions.end()) { mLogger.logInputDeviceUsageReported(identifier, it->second.end - it->second.start); mActiveUsageSessions.erase(it); } // We don't remove this from mLoggedDeviceInfos because it will be updated in // onInputDevicesChanged(). } void InputDeviceMetricsCollector::onInputDeviceUsage(DeviceId deviceId, nanoseconds eventTime) { if (mLoggedDeviceInfos.count(deviceId) == 0) { // Do not track usage for devices that are not logged. return; } auto [it, inserted] = mActiveUsageSessions.try_emplace(deviceId, eventTime, eventTime); if (!inserted) { it->second.end = eventTime; } } void InputDeviceMetricsCollector::processUsages() { const auto usageSessionExpiryTime = mLogger.getCurrentTime() - mUsageSessionTimeout; std::vector<DeviceId> completedUsageSessions; for (const auto& [deviceId, usageSession] : mActiveUsageSessions) { if (usageSession.end <= usageSessionExpiryTime) { completedUsageSessions.emplace_back(deviceId); } } for (DeviceId deviceId : completedUsageSessions) { const auto it = mLoggedDeviceInfos.find(deviceId); LOG_ALWAYS_FATAL_IF(it == mLoggedDeviceInfos.end()); const auto& session = mActiveUsageSessions[deviceId]; mLogger.logInputDeviceUsageReported(it->second, session.end - session.start); mActiveUsageSessions.erase(deviceId); } } } // namespace android
services/inputflinger/InputDeviceMetricsCollector.h +45 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,12 @@ #include "InputListener.h" #include <ftl/mixins.h> #include <input/InputDevice.h> #include <chrono> #include <map> #include <vector> namespace android { /** Loading @@ -34,11 +40,24 @@ public: virtual void dump(std::string& dump) = 0; }; /** The logging interface for the metrics collector, injected for testing. */ class InputDeviceMetricsLogger { public: virtual std::chrono::nanoseconds getCurrentTime() = 0; virtual void logInputDeviceUsageReported(const InputDeviceIdentifier&, std::chrono::nanoseconds duration) = 0; virtual ~InputDeviceMetricsLogger() = default; }; class InputDeviceMetricsCollector : public InputDeviceMetricsCollectorInterface { public: explicit InputDeviceMetricsCollector(InputListenerInterface& listener); ~InputDeviceMetricsCollector() override = default; // Test constructor InputDeviceMetricsCollector(InputListenerInterface& listener, InputDeviceMetricsLogger& logger, std::chrono::nanoseconds usageSessionTimeout); void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override; void notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) override; void notifyKey(const NotifyKeyArgs& args) override; Loading @@ -53,6 +72,32 @@ public: private: InputListenerInterface& mNextListener; InputDeviceMetricsLogger& mLogger; const std::chrono::nanoseconds mUsageSessionTimeout; // Type-safe wrapper for input device id. struct DeviceId : ftl::Constructible<DeviceId, std::int32_t>, ftl::Equatable<DeviceId>, ftl::Orderable<DeviceId> { using Constructible::Constructible; }; static std::string toString(const DeviceId& id) { return std::to_string(ftl::to_underlying(id)); } std::map<DeviceId, InputDeviceIdentifier> mLoggedDeviceInfos; struct UsageSession { std::chrono::nanoseconds start; std::chrono::nanoseconds end; }; // The input devices that currently have active usage sessions. std::map<DeviceId, UsageSession> mActiveUsageSessions; void onInputDevicesChanged(const std::vector<InputDeviceInfo>& infos); void onInputDeviceRemoved(DeviceId deviceId, const InputDeviceIdentifier& identifier); void onInputDeviceUsage(DeviceId deviceId, std::chrono::nanoseconds eventTime); void processUsages(); }; } // namespace android
services/inputflinger/tests/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -47,6 +47,7 @@ cc_test { "FocusResolver_test.cpp", "GestureConverter_test.cpp", "HardwareStateConverter_test.cpp", "InputDeviceMetricsCollector_test.cpp", "InputMapperTest.cpp", "InputProcessor_test.cpp", "InputProcessorConverter_test.cpp", Loading