blob: 50f336b952f0aecd73cfa8c29e6c2ae016a032a1 [file] [log] [blame]
/*
* Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#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)
: 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