Support Inputdevice LightsManager feature in frameworks.
Add lights manager support to input frameworks.
Bug: 161633625
Test: atest LightsManagerTest, atest InputDeviceLightsManagerTest
Change-Id: Ie00357bce0f6c98e9eada5e0a79f93f48e7a4d1b
diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp
index 8f8c051..e939d1c 100644
--- a/services/inputflinger/reader/EventHub.cpp
+++ b/services/inputflinger/reader/EventHub.cpp
@@ -48,6 +48,7 @@
#include <utils/Timers.h>
#include <filesystem>
+#include <regex>
#include "EventHub.h"
@@ -84,6 +85,30 @@
{"Full", 100},
{"Unknown", 50}};
+// Mapping for input led class node names lookup.
+// https://www.kernel.org/doc/html/latest/leds/leds-class.html
+static const std::unordered_map<std::string, InputLightClass> LIGHT_CLASSES =
+ {{"red", InputLightClass::RED},
+ {"green", InputLightClass::GREEN},
+ {"blue", InputLightClass::BLUE},
+ {"global", InputLightClass::GLOBAL},
+ {"brightness", InputLightClass::BRIGHTNESS},
+ {"multi_index", InputLightClass::MULTI_INDEX},
+ {"multi_intensity", InputLightClass::MULTI_INTENSITY},
+ {"max_brightness", InputLightClass::MAX_BRIGHTNESS}};
+
+// Mapping for input multicolor led class node names.
+// https://www.kernel.org/doc/html/latest/leds/leds-class-multicolor.html
+static const std::unordered_map<InputLightClass, std::string> LIGHT_NODES =
+ {{InputLightClass::BRIGHTNESS, "brightness"},
+ {InputLightClass::MULTI_INDEX, "multi_index"},
+ {InputLightClass::MULTI_INTENSITY, "multi_intensity"}};
+
+// Mapping for light color name and the light color
+const std::unordered_map<std::string, LightColor> LIGHT_COLORS = {{"red", LightColor::RED},
+ {"green", LightColor::GREEN},
+ {"blue", LightColor::BLUE}};
+
static inline const char* toString(bool value) {
return value ? "true" : "false";
}
@@ -151,14 +176,14 @@
* Returns the sysfs root path of the input device
*
*/
-static std::filesystem::path getSysfsRootPath(const char* devicePath) {
+static std::optional<std::filesystem::path> getSysfsRootPath(const char* devicePath) {
std::error_code errorCode;
// Stat the device path to get the major and minor number of the character file
struct stat statbuf;
if (stat(devicePath, &statbuf) == -1) {
ALOGE("Could not stat device %s due to error: %s.", devicePath, std::strerror(errno));
- return std::filesystem::path();
+ return std::nullopt;
}
unsigned int major_num = major(statbuf.st_rdev);
@@ -173,7 +198,7 @@
if (errorCode) {
ALOGW("Could not run filesystem::canonical() due to error %d : %s.", errorCode.value(),
errorCode.message().c_str());
- return std::filesystem::path();
+ return std::nullopt;
}
// Continue to go up a directory until we reach a directory named "input"
@@ -192,26 +217,68 @@
}
// Not found
- return std::filesystem::path();
+ return std::nullopt;
}
return sysfsPath;
}
/**
- * Returns the power supply node in sys fs
- *
+ * Returns the list of files under a specified path.
*/
-static std::filesystem::path findPowerSupplyNode(const std::filesystem::path& sysfsRootPath) {
- for (auto path = sysfsRootPath; path != "/"; path = path.parent_path()) {
- std::error_code errorCode;
- auto iter = std::filesystem::directory_iterator(path / "power_supply", errorCode);
- if (!errorCode && iter != std::filesystem::directory_iterator()) {
- return iter->path();
+static std::vector<std::filesystem::path> allFilesInPath(const std::filesystem::path& path) {
+ std::vector<std::filesystem::path> nodes;
+ std::error_code errorCode;
+ auto iter = std::filesystem::directory_iterator(path, errorCode);
+ while (!errorCode && iter != std::filesystem::directory_iterator()) {
+ nodes.push_back(iter->path());
+ iter++;
+ }
+ return nodes;
+}
+
+/**
+ * Returns the list of files under a specified directory in a sysfs path.
+ * Example:
+ * findSysfsNodes(sysfsRootPath, SysfsClass::LEDS) will return all led nodes under "leds" directory
+ * in the sysfs path.
+ */
+static std::vector<std::filesystem::path> findSysfsNodes(const std::filesystem::path& sysfsRoot,
+ SysfsClass clazz) {
+ std::string nodeStr = NamedEnum::string(clazz);
+ std::for_each(nodeStr.begin(), nodeStr.end(),
+ [](char& c) { c = std::tolower(static_cast<unsigned char>(c)); });
+ std::vector<std::filesystem::path> nodes;
+ for (auto path = sysfsRoot; path != "/" && nodes.empty(); path = path.parent_path()) {
+ nodes = allFilesInPath(path / nodeStr);
+ }
+ return nodes;
+}
+
+static std::optional<std::array<LightColor, COLOR_NUM>> getColorIndexArray(
+ std::filesystem::path path) {
+ std::string indexStr;
+ if (!base::ReadFileToString(path, &indexStr)) {
+ return std::nullopt;
+ }
+
+ // Parse the multi color LED index file, refer to kernel docs
+ // leds/leds-class-multicolor.html
+ std::regex indexPattern("(red|green|blue)\\s(red|green|blue)\\s(red|green|blue)[\\n]");
+ std::smatch results;
+ std::array<LightColor, COLOR_NUM> colors;
+ if (!std::regex_match(indexStr, results, indexPattern)) {
+ return std::nullopt;
+ }
+
+ for (size_t i = 1; i < results.size(); i++) {
+ const auto it = LIGHT_COLORS.find(results[i].str());
+ if (it != LIGHT_COLORS.end()) {
+ // intensities.emplace(it->second, 0);
+ colors[i - 1] = it->second;
}
}
- // Not found
- return std::filesystem::path();
+ return colors;
}
// --- Global Functions ---
@@ -280,6 +347,7 @@
virtualKeyMap(nullptr),
ffEffectPlaying(false),
ffEffectId(-1),
+ nextLightId(0),
controllerNumber(0),
enabled(true),
isVirtual(fd < 0) {}
@@ -469,6 +537,70 @@
return NAME_NOT_FOUND;
}
+// Check the sysfs path for any input device batteries, returns true if battery found.
+bool EventHub::Device::configureBatteryLocked() {
+ if (!sysfsRootPath.has_value()) {
+ return false;
+ }
+ // Check if device has any batteries.
+ std::vector<std::filesystem::path> batteryPaths =
+ findSysfsNodes(sysfsRootPath.value(), SysfsClass::POWER_SUPPLY);
+ // We only support single battery for an input device, if multiple batteries exist only the
+ // first one is supported.
+ if (batteryPaths.empty()) {
+ // Set path to be empty
+ sysfsBatteryPath = std::nullopt;
+ return false;
+ }
+ // If a battery exists
+ sysfsBatteryPath = batteryPaths[0];
+ return true;
+}
+
+// Check the sysfs path for any input device lights, returns true if lights found.
+bool EventHub::Device::configureLightsLocked() {
+ if (!sysfsRootPath.has_value()) {
+ return false;
+ }
+ // Check if device has any lights.
+ const auto& paths = findSysfsNodes(sysfsRootPath.value(), SysfsClass::LEDS);
+ for (const auto& nodePath : paths) {
+ RawLightInfo info;
+ info.id = ++nextLightId;
+ info.path = nodePath;
+ info.name = nodePath.filename();
+ info.maxBrightness = std::nullopt;
+ size_t nameStart = info.name.rfind(":");
+ if (nameStart != std::string::npos) {
+ // Trim the name to color name
+ info.name = info.name.substr(nameStart + 1);
+ // Set InputLightClass flag for colors
+ const auto it = LIGHT_CLASSES.find(info.name);
+ if (it != LIGHT_CLASSES.end()) {
+ info.flags |= it->second;
+ }
+ }
+ // Scan the path for all the files
+ // Refer to https://www.kernel.org/doc/Documentation/leds/leds-class.txt
+ const auto& files = allFilesInPath(nodePath);
+ for (const auto& file : files) {
+ const auto it = LIGHT_CLASSES.find(file.filename().string());
+ if (it != LIGHT_CLASSES.end()) {
+ info.flags |= it->second;
+ // If the node has maximum brightness, read it
+ if (it->second == InputLightClass::MAX_BRIGHTNESS) {
+ std::string str;
+ if (base::ReadFileToString(file, &str)) {
+ info.maxBrightness = std::stoi(str);
+ }
+ }
+ }
+ }
+ lightInfos.insert_or_assign(info.id, info);
+ }
+ return !lightInfos.empty();
+}
+
/**
* Get the capabilities for the current process.
* Crashes the system if unable to create / check / destroy the capabilities object.
@@ -829,6 +961,161 @@
return Errorf("Device not found or device has no key layout.");
}
+const std::vector<int32_t> EventHub::getRawLightIds(int32_t deviceId) {
+ std::scoped_lock _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+ std::vector<int32_t> lightIds;
+
+ if (device != nullptr) {
+ for (const auto [id, info] : device->lightInfos) {
+ lightIds.push_back(id);
+ }
+ }
+ return lightIds;
+}
+
+std::optional<RawLightInfo> EventHub::getRawLightInfo(int32_t deviceId, int32_t lightId) {
+ std::scoped_lock _l(mLock);
+ Device* device = getDeviceLocked(deviceId);
+
+ if (device != nullptr) {
+ auto it = device->lightInfos.find(lightId);
+ if (it != device->lightInfos.end()) {
+ return it->second;
+ }
+ }
+ return std::nullopt;
+}
+
+std::optional<int32_t> EventHub::getLightBrightness(int32_t deviceId, int32_t lightId) {
+ std::scoped_lock _l(mLock);
+
+ Device* device = getDeviceLocked(deviceId);
+ if (device == nullptr) {
+ return std::nullopt;
+ }
+
+ auto it = device->lightInfos.find(lightId);
+ if (it == device->lightInfos.end()) {
+ return std::nullopt;
+ }
+ std::string buffer;
+ if (!base::ReadFileToString(it->second.path / LIGHT_NODES.at(InputLightClass::BRIGHTNESS),
+ &buffer)) {
+ return std::nullopt;
+ }
+ return std::stoi(buffer);
+}
+
+std::optional<std::unordered_map<LightColor, int32_t>> EventHub::getLightIntensities(
+ int32_t deviceId, int32_t lightId) {
+ std::scoped_lock _l(mLock);
+
+ Device* device = getDeviceLocked(deviceId);
+ if (device == nullptr) {
+ return std::nullopt;
+ }
+
+ auto lightIt = device->lightInfos.find(lightId);
+ if (lightIt == device->lightInfos.end()) {
+ return std::nullopt;
+ }
+
+ auto ret =
+ getColorIndexArray(lightIt->second.path / LIGHT_NODES.at(InputLightClass::MULTI_INDEX));
+
+ if (!ret.has_value()) {
+ return std::nullopt;
+ }
+ std::array<LightColor, COLOR_NUM> colors = ret.value();
+
+ std::string intensityStr;
+ if (!base::ReadFileToString(lightIt->second.path /
+ LIGHT_NODES.at(InputLightClass::MULTI_INTENSITY),
+ &intensityStr)) {
+ return std::nullopt;
+ }
+
+ // Intensity node outputs 3 color values
+ std::regex intensityPattern("([0-9]+)\\s([0-9]+)\\s([0-9]+)[\\n]");
+ std::smatch results;
+
+ if (!std::regex_match(intensityStr, results, intensityPattern)) {
+ return std::nullopt;
+ }
+ std::unordered_map<LightColor, int32_t> intensities;
+ for (size_t i = 1; i < results.size(); i++) {
+ int value = std::stoi(results[i].str());
+ intensities.emplace(colors[i - 1], value);
+ }
+ return intensities;
+}
+
+void EventHub::setLightBrightness(int32_t deviceId, int32_t lightId, int32_t brightness) {
+ std::scoped_lock _l(mLock);
+
+ Device* device = getDeviceLocked(deviceId);
+ if (device == nullptr) {
+ ALOGE("Device Id %d does not exist", deviceId);
+ return;
+ }
+ auto lightIt = device->lightInfos.find(lightId);
+ if (lightIt == device->lightInfos.end()) {
+ ALOGE("Light Id %d does not exist.", lightId);
+ return;
+ }
+
+ if (!base::WriteStringToFile(std::to_string(brightness),
+ lightIt->second.path /
+ LIGHT_NODES.at(InputLightClass::BRIGHTNESS))) {
+ ALOGE("Can not write to file, error: %s", strerror(errno));
+ }
+}
+
+void EventHub::setLightIntensities(int32_t deviceId, int32_t lightId,
+ std::unordered_map<LightColor, int32_t> intensities) {
+ std::scoped_lock _l(mLock);
+
+ Device* device = getDeviceLocked(deviceId);
+ if (device == nullptr) {
+ ALOGE("Device Id %d does not exist", deviceId);
+ return;
+ }
+ auto lightIt = device->lightInfos.find(lightId);
+ if (lightIt == device->lightInfos.end()) {
+ ALOGE("Light Id %d does not exist.", lightId);
+ return;
+ }
+
+ auto ret =
+ getColorIndexArray(lightIt->second.path / LIGHT_NODES.at(InputLightClass::MULTI_INDEX));
+
+ if (!ret.has_value()) {
+ return;
+ }
+ std::array<LightColor, COLOR_NUM> colors = ret.value();
+
+ std::string rgbStr;
+ for (size_t i = 0; i < COLOR_NUM; i++) {
+ auto it = intensities.find(colors[i]);
+ if (it != intensities.end()) {
+ rgbStr += std::to_string(it->second);
+ // Insert space between colors
+ if (i < COLOR_NUM - 1) {
+ rgbStr += " ";
+ }
+ }
+ }
+ // Append new line
+ rgbStr += "\n";
+
+ if (!base::WriteStringToFile(rgbStr,
+ lightIt->second.path /
+ LIGHT_NODES.at(InputLightClass::MULTI_INTENSITY))) {
+ ALOGE("Can not write to file, error: %s", strerror(errno));
+ }
+}
+
void EventHub::setExcludedDevices(const std::vector<std::string>& devices) {
std::scoped_lock _l(mLock);
@@ -1068,18 +1355,18 @@
Device* device = getDeviceLocked(deviceId);
std::string buffer;
- if (!device || (device->sysfsBatteryPath.empty())) {
+ if (device == nullptr || !device->sysfsBatteryPath.has_value()) {
return std::nullopt;
}
// Some devices report battery capacity as an integer through the "capacity" file
- if (base::ReadFileToString(device->sysfsBatteryPath / "capacity", &buffer)) {
+ if (base::ReadFileToString(device->sysfsBatteryPath.value() / "capacity", &buffer)) {
return std::stoi(buffer);
}
// Other devices report capacity as an enum value POWER_SUPPLY_CAPACITY_LEVEL_XXX
// These values are taken from kernel source code include/linux/power_supply.h
- if (base::ReadFileToString(device->sysfsBatteryPath / "capacity_level", &buffer)) {
+ if (base::ReadFileToString(device->sysfsBatteryPath.value() / "capacity_level", &buffer)) {
const auto it = BATTERY_LEVEL.find(buffer);
if (it != BATTERY_LEVEL.end()) {
return it->second;
@@ -1093,11 +1380,11 @@
Device* device = getDeviceLocked(deviceId);
std::string buffer;
- if (!device || (device->sysfsBatteryPath.empty())) {
+ if (device == nullptr || !device->sysfsBatteryPath.has_value()) {
return std::nullopt;
}
- if (!base::ReadFileToString(device->sysfsBatteryPath / "status", &buffer)) {
+ if (!base::ReadFileToString(device->sysfsBatteryPath.value() / "status", &buffer)) {
ALOGE("Failed to read sysfs battery info: %s", strerror(errno));
return std::nullopt;
}
@@ -1572,6 +1859,12 @@
// Load the configuration file for the device.
device->loadConfigurationLocked();
+ // Grab the device's sysfs path
+ device->sysfsRootPath = getSysfsRootPath(devicePath.c_str());
+ // find related components
+ bool hasBattery = device->configureBatteryLocked();
+ bool hasLights = device->configureLightsLocked();
+
// Figure out the kinds of events the device reports.
device->readDeviceBitMask(EVIOCGBIT(EV_KEY, 0), device->keyBitmask);
device->readDeviceBitMask(EVIOCGBIT(EV_ABS, 0), device->absBitmask);
@@ -1721,16 +2014,14 @@
return -1;
}
- // Grab the device's sysfs path
- device->sysfsRootPath = getSysfsRootPath(devicePath.c_str());
+ // Classify InputDeviceClass::BATTERY.
+ if (hasBattery) {
+ device->classes |= InputDeviceClass::BATTERY;
+ }
- if (!device->sysfsRootPath.empty()) {
- device->sysfsBatteryPath = findPowerSupplyNode(device->sysfsRootPath);
-
- // Check if a battery exists
- if (!device->sysfsBatteryPath.empty()) {
- device->classes |= InputDeviceClass::BATTERY;
- }
+ // Classify InputDeviceClass::LIGHT.
+ if (hasLights) {
+ device->classes |= InputDeviceClass::LIGHT;
}
// Determine whether the device has a mic.