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/include/android/input.h b/include/android/input.h
index b70d424..7973487 100644
--- a/include/android/input.h
+++ b/include/android/input.h
@@ -859,7 +859,7 @@
/** HDMI */
AINPUT_SOURCE_HDMI = 0x02000000 | AINPUT_SOURCE_CLASS_BUTTON,
/** sensor */
- AINPUT_SOURCE_SENSOR = 0x04000000 | AINPUT_SOURCE_UNKNOWN,
+ AINPUT_SOURCE_SENSOR = 0x04000000 | AINPUT_SOURCE_CLASS_NONE,
/** rotary encoder */
AINPUT_SOURCE_ROTARY_ENCODER = 0x00400000 | AINPUT_SOURCE_CLASS_NONE,
diff --git a/include/input/InputDevice.h b/include/input/InputDevice.h
index 2bd7bd2..2deb99d 100644
--- a/include/input/InputDevice.h
+++ b/include/input/InputDevice.h
@@ -100,6 +100,13 @@
SPECIAL_TRIGGER = 3,
};
+enum class InputDeviceLightType : int32_t {
+ SINGLE = 0,
+ PLAYER_ID = 1,
+ RGB = 2,
+ MULTI_COLOR = 3,
+};
+
struct InputDeviceSensorInfo {
explicit InputDeviceSensorInfo(std::string name, std::string vendor, int32_t version,
InputDeviceSensorType type, InputDeviceSensorAccuracy accuracy,
@@ -156,6 +163,20 @@
int32_t id;
};
+struct InputDeviceLightInfo {
+ explicit InputDeviceLightInfo(std::string name, int32_t id, InputDeviceLightType type,
+ int32_t ordinal)
+ : name(name), id(id), type(type), ordinal(ordinal) {}
+ // Name string of the light.
+ std::string name;
+ // Light id
+ int32_t id;
+ // Type of the light.
+ InputDeviceLightType type;
+ // Ordinal of the light
+ int32_t ordinal;
+};
+
/*
* Describes the characteristics and capabilities of an input device.
*/
@@ -198,6 +219,7 @@
float min, float max, float flat, float fuzz, float resolution);
void addMotionRange(const MotionRange& range);
void addSensorInfo(const InputDeviceSensorInfo& info);
+ void addLightInfo(const InputDeviceLightInfo& info);
inline void setKeyboardType(int32_t keyboardType) { mKeyboardType = keyboardType; }
inline int32_t getKeyboardType() const { return mKeyboardType; }
@@ -230,6 +252,10 @@
const std::vector<InputDeviceSensorType> getSensorTypes();
+ const std::vector<int32_t> getLightIds();
+
+ const InputDeviceLightInfo* getLightInfo(int32_t id);
+
private:
int32_t mId;
int32_t mGeneration;
@@ -248,6 +274,8 @@
std::vector<MotionRange> mMotionRanges;
std::unordered_map<InputDeviceSensorType, InputDeviceSensorInfo> mSensors;
+ /* Map from light ID to light info */
+ std::unordered_map<int32_t, InputDeviceLightInfo> mLights;
};
/* Types of input device configuration files. */
diff --git a/libs/input/InputDevice.cpp b/libs/input/InputDevice.cpp
index 698cf6e..ffcc1cd 100644
--- a/libs/input/InputDevice.cpp
+++ b/libs/input/InputDevice.cpp
@@ -170,7 +170,8 @@
mHasButtonUnderPad(other.mHasButtonUnderPad),
mHasSensor(other.mHasSensor),
mMotionRanges(other.mMotionRanges),
- mSensors(other.mSensors) {}
+ mSensors(other.mSensors),
+ mLights(other.mLights) {}
InputDeviceInfo::~InputDeviceInfo() {
}
@@ -193,6 +194,7 @@
mHasSensor = false;
mMotionRanges.clear();
mSensors.clear();
+ mLights.clear();
}
const InputDeviceInfo::MotionRange* InputDeviceInfo::getMotionRange(
@@ -229,6 +231,13 @@
mSensors.insert_or_assign(info.type, info);
}
+void InputDeviceInfo::addLightInfo(const InputDeviceLightInfo& info) {
+ if (mLights.find(info.id) != mLights.end()) {
+ ALOGW("Light id %d already exists, will be replaced by new light added.", info.id);
+ }
+ mLights.insert_or_assign(info.id, info);
+}
+
const std::vector<InputDeviceSensorType> InputDeviceInfo::getSensorTypes() {
std::vector<InputDeviceSensorType> types;
for (const auto& [type, info] : mSensors) {
@@ -245,4 +254,20 @@
return &it->second;
}
+const std::vector<int32_t> InputDeviceInfo::getLightIds() {
+ std::vector<int32_t> ids;
+ for (const auto& [id, info] : mLights) {
+ ids.push_back(id);
+ }
+ return ids;
+}
+
+const InputDeviceLightInfo* InputDeviceInfo::getLightInfo(int32_t id) {
+ auto it = mLights.find(id);
+ if (it == mLights.end()) {
+ return nullptr;
+ }
+ return &it->second;
+}
+
} // namespace android
diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h
index 69aea84..3bf212a 100644
--- a/services/inputflinger/include/InputReaderBase.h
+++ b/services/inputflinger/include/InputReaderBase.h
@@ -113,6 +113,10 @@
/* Get battery status of a particular input device. */
virtual std::optional<int32_t> getBatteryStatus(int32_t deviceId) = 0;
+ virtual std::vector<int32_t> getLightIds(int32_t deviceId) = 0;
+
+ virtual const InputDeviceLightInfo* getLightInfo(int32_t deviceId, int32_t lightId) = 0;
+
/* Return true if the device can send input events to the specified display. */
virtual bool canDispatchToDisplay(int32_t deviceId, int32_t displayId) = 0;
@@ -126,6 +130,15 @@
/* Flush sensor data in input reader mapper. */
virtual void flushSensor(int32_t deviceId, InputDeviceSensorType sensorType) = 0;
+
+ /* Set color for the light */
+ virtual bool setLightColor(int32_t deviceId, int32_t lightId, int32_t color) = 0;
+ /* Set player ID for the light */
+ virtual bool setLightPlayerId(int32_t deviceId, int32_t lightId, int32_t playerId) = 0;
+ /* Get light color */
+ virtual std::optional<int32_t> getLightColor(int32_t deviceId, int32_t lightId) = 0;
+ /* Get light player ID */
+ virtual std::optional<int32_t> getLightPlayerId(int32_t deviceId, int32_t lightId) = 0;
};
// --- InputReaderConfiguration ---
diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp
index 7f979f2..ffb98e0 100644
--- a/services/inputflinger/reader/Android.bp
+++ b/services/inputflinger/reader/Android.bp
@@ -36,6 +36,7 @@
"mapper/InputMapper.cpp",
"mapper/JoystickInputMapper.cpp",
"mapper/KeyboardInputMapper.cpp",
+ "mapper/LightInputMapper.cpp",
"mapper/MultiTouchInputMapper.cpp",
"mapper/RotaryEncoderInputMapper.cpp",
"mapper/SensorInputMapper.cpp",
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.
diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp
index 574f651..cbf3b69 100644
--- a/services/inputflinger/reader/InputDevice.cpp
+++ b/services/inputflinger/reader/InputDevice.cpp
@@ -27,6 +27,7 @@
#include "InputReaderContext.h"
#include "JoystickInputMapper.h"
#include "KeyboardInputMapper.h"
+#include "LightInputMapper.h"
#include "MultiTouchInputMapper.h"
#include "RotaryEncoderInputMapper.h"
#include "SensorInputMapper.h"
@@ -161,9 +162,22 @@
mappers.push_back(std::make_unique<VibratorInputMapper>(*contextPtr));
}
- // Battery-like devices.
+ // Battery-like devices. Only one battery mapper for each EventHub device.
if (classes.test(InputDeviceClass::BATTERY)) {
- mappers.push_back(std::make_unique<BatteryInputMapper>(*contextPtr));
+ InputDeviceInfo deviceInfo;
+ getDeviceInfo(&deviceInfo);
+ if (!deviceInfo.hasBattery()) {
+ mappers.push_back(std::make_unique<BatteryInputMapper>(*contextPtr));
+ }
+ }
+
+ // Light-containing devices. Only one light mapper for each EventHub device.
+ if (classes.test(InputDeviceClass::LIGHT)) {
+ InputDeviceInfo deviceInfo;
+ getDeviceInfo(&deviceInfo);
+ if (deviceInfo.getLightIds().empty()) {
+ mappers.push_back(std::make_unique<LightInputMapper>(*contextPtr));
+ }
}
// Keyboard-like devices.
@@ -505,6 +519,32 @@
return first_in_mappers<int32_t>([](InputMapper& mapper) { return mapper.getBatteryStatus(); });
}
+bool InputDevice::setLightColor(int32_t lightId, int32_t color) {
+ bool success = true;
+ for_each_mapper([&success, lightId, color](InputMapper& mapper) {
+ success &= mapper.setLightColor(lightId, color);
+ });
+ return success;
+}
+
+bool InputDevice::setLightPlayerId(int32_t lightId, int32_t playerId) {
+ bool success = true;
+ for_each_mapper([&success, lightId, playerId](InputMapper& mapper) {
+ success &= mapper.setLightPlayerId(lightId, playerId);
+ });
+ return success;
+}
+
+std::optional<int32_t> InputDevice::getLightColor(int32_t lightId) {
+ return first_in_mappers<int32_t>(
+ [lightId](InputMapper& mapper) { return mapper.getLightColor(lightId); });
+}
+
+std::optional<int32_t> InputDevice::getLightPlayerId(int32_t lightId) {
+ return first_in_mappers<int32_t>(
+ [lightId](InputMapper& mapper) { return mapper.getLightPlayerId(lightId); });
+}
+
int32_t InputDevice::getMetaState() {
int32_t result = 0;
for_each_mapper([&result](InputMapper& mapper) { result |= mapper.getMetaState(); });
diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp
index 14fb77b..2d0fdf7 100644
--- a/services/inputflinger/reader/InputReader.cpp
+++ b/services/inputflinger/reader/InputReader.cpp
@@ -696,6 +696,70 @@
return std::nullopt;
}
+std::vector<int32_t> InputReader::getLightIds(int32_t deviceId) {
+ std::scoped_lock _l(mLock);
+
+ InputDevice* device = findInputDeviceLocked(deviceId);
+ if (device) {
+ InputDeviceInfo info;
+ device->getDeviceInfo(&info);
+ return info.getLightIds();
+ }
+ return {};
+}
+
+const InputDeviceLightInfo* InputReader::getLightInfo(int32_t deviceId, int32_t lightId) {
+ std::scoped_lock _l(mLock);
+
+ InputDevice* device = findInputDeviceLocked(deviceId);
+ if (device) {
+ InputDeviceInfo info;
+ device->getDeviceInfo(&info);
+ return info.getLightInfo(lightId);
+ }
+ return nullptr;
+}
+
+bool InputReader::setLightColor(int32_t deviceId, int32_t lightId, int32_t color) {
+ std::scoped_lock _l(mLock);
+
+ InputDevice* device = findInputDeviceLocked(deviceId);
+ if (device) {
+ return device->setLightColor(lightId, color);
+ }
+ return false;
+}
+
+bool InputReader::setLightPlayerId(int32_t deviceId, int32_t lightId, int32_t playerId) {
+ std::scoped_lock _l(mLock);
+
+ InputDevice* device = findInputDeviceLocked(deviceId);
+ if (device) {
+ return device->setLightPlayerId(lightId, playerId);
+ }
+ return false;
+}
+
+std::optional<int32_t> InputReader::getLightColor(int32_t deviceId, int32_t lightId) {
+ std::scoped_lock _l(mLock);
+
+ InputDevice* device = findInputDeviceLocked(deviceId);
+ if (device) {
+ return device->getLightColor(lightId);
+ }
+ return std::nullopt;
+}
+
+std::optional<int32_t> InputReader::getLightPlayerId(int32_t deviceId, int32_t lightId) {
+ std::scoped_lock _l(mLock);
+
+ InputDevice* device = findInputDeviceLocked(deviceId);
+ if (device) {
+ return device->getLightPlayerId(lightId);
+ }
+ return std::nullopt;
+}
+
bool InputReader::isInputDeviceEnabled(int32_t deviceId) {
std::scoped_lock _l(mLock);
diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h
index 30967df..e6164d3 100644
--- a/services/inputflinger/reader/include/EventHub.h
+++ b/services/inputflinger/reader/include/EventHub.h
@@ -47,6 +47,8 @@
namespace android {
+/* Number of colors : {red, green, blue} */
+static constexpr size_t COLOR_NUM = 3;
/*
* A raw event as retrieved from the EventHub.
*/
@@ -127,6 +129,9 @@
/* The input device has a battery */
BATTERY = 0x00004000,
+ /* The input device has sysfs controllable lights */
+ LIGHT = 0x00008000,
+
/* The input device is virtual (not a real device, not part of UI configuration). */
VIRTUAL = 0x40000000,
@@ -134,6 +139,46 @@
EXTERNAL = 0x80000000,
};
+enum class SysfsClass : uint32_t {
+ POWER_SUPPLY = 0,
+ LEDS = 1,
+};
+
+enum class LightColor : uint32_t {
+ RED = 0,
+ GREEN = 1,
+ BLUE = 2,
+};
+
+enum class InputLightClass : uint32_t {
+ /* The input light has brightness node. */
+ BRIGHTNESS = 0x00000001,
+ /* The input light has red name. */
+ RED = 0x00000002,
+ /* The input light has green name. */
+ GREEN = 0x00000004,
+ /* The input light has blue name. */
+ BLUE = 0x00000008,
+ /* The input light has global name. */
+ GLOBAL = 0x00000010,
+ /* The input light has multi index node. */
+ MULTI_INDEX = 0x00000020,
+ /* The input light has multi intensity node. */
+ MULTI_INTENSITY = 0x00000040,
+ /* The input light has max brightness node. */
+ MAX_BRIGHTNESS = 0x00000080,
+};
+
+/* Describes a raw light. */
+struct RawLightInfo {
+ int32_t id;
+ std::string name;
+ std::optional<int32_t> maxBrightness;
+ Flags<InputLightClass> flags;
+ std::array<int32_t, COLOR_NUM> rgbIndex;
+ std::filesystem::path path;
+};
+
/*
* Gets the class that owns an axis, in cases where multiple classes might claim
* the same axis for different purposes.
@@ -214,7 +259,16 @@
virtual std::vector<TouchVideoFrame> getVideoFrames(int32_t deviceId) = 0;
virtual base::Result<std::pair<InputDeviceSensorType, int32_t>> mapSensor(int32_t deviceId,
int32_t absCode) = 0;
-
+ // Raw lights are sysfs led light nodes we found from the EventHub device sysfs node,
+ // containing the raw info of the sysfs node structure.
+ virtual const std::vector<int32_t> getRawLightIds(int32_t deviceId) = 0;
+ virtual std::optional<RawLightInfo> getRawLightInfo(int32_t deviceId, int32_t lightId) = 0;
+ virtual std::optional<int32_t> getLightBrightness(int32_t deviceId, int32_t lightId) = 0;
+ virtual void setLightBrightness(int32_t deviceId, int32_t lightId, int32_t brightness) = 0;
+ virtual std::optional<std::unordered_map<LightColor, int32_t>> getLightIntensities(
+ int32_t deviceId, int32_t lightId) = 0;
+ virtual void setLightIntensities(int32_t deviceId, int32_t lightId,
+ std::unordered_map<LightColor, int32_t> intensities) = 0;
/*
* Query current input state.
*/
@@ -377,6 +431,17 @@
base::Result<std::pair<InputDeviceSensorType, int32_t>> mapSensor(
int32_t deviceId, int32_t absCode) override final;
+ const std::vector<int32_t> getRawLightIds(int32_t deviceId) override final;
+
+ std::optional<RawLightInfo> getRawLightInfo(int32_t deviceId, int32_t lightId) override final;
+
+ std::optional<int32_t> getLightBrightness(int32_t deviceId, int32_t lightId) override final;
+ void setLightBrightness(int32_t deviceId, int32_t lightId, int32_t brightness) override final;
+ std::optional<std::unordered_map<LightColor, int32_t>> getLightIntensities(
+ int32_t deviceId, int32_t lightId) override final;
+ void setLightIntensities(int32_t deviceId, int32_t lightId,
+ std::unordered_map<LightColor, int32_t> intensities) override final;
+
void setExcludedDevices(const std::vector<std::string>& devices) override final;
int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const override final;
@@ -458,9 +523,12 @@
bool ffEffectPlaying;
int16_t ffEffectId; // initially -1
- // The paths are invalid when .empty() returns true
- std::filesystem::path sysfsRootPath;
- std::filesystem::path sysfsBatteryPath;
+ // The paths are invalid when they're std::nullopt
+ std::optional<std::filesystem::path> sysfsRootPath;
+ std::optional<std::filesystem::path> sysfsBatteryPath;
+ // maps from light id to light info
+ std::unordered_map<int32_t, RawLightInfo> lightInfos;
+ int32_t nextLightId;
int32_t controllerNumber;
@@ -491,6 +559,8 @@
void setLedForControllerLocked();
status_t mapLed(int32_t led, int32_t* outScanCode) const;
void setLedStateLocked(int32_t led, bool on);
+ bool configureBatteryLocked();
+ bool configureLightsLocked();
};
status_t openDeviceLocked(const std::string& devicePath);
diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h
index e4186c8..34c330b 100644
--- a/services/inputflinger/reader/include/InputDevice.h
+++ b/services/inputflinger/reader/include/InputDevice.h
@@ -95,6 +95,11 @@
std::optional<int32_t> getBatteryCapacity();
std::optional<int32_t> getBatteryStatus();
+ bool setLightColor(int32_t lightId, int32_t color);
+ bool setLightPlayerId(int32_t lightId, int32_t playerId);
+ std::optional<int32_t> getLightColor(int32_t lightId);
+ std::optional<int32_t> getLightPlayerId(int32_t lightId);
+
int32_t getMetaState();
void updateMetaState(int32_t keyCode);
@@ -254,6 +259,30 @@
return mEventHub->mapSensor(mId, absCode);
}
+ inline const std::vector<int32_t> getRawLightIds() { return mEventHub->getRawLightIds(mId); }
+
+ inline std::optional<RawLightInfo> getRawLightInfo(int32_t lightId) {
+ return mEventHub->getRawLightInfo(mId, lightId);
+ }
+
+ inline std::optional<int32_t> getLightBrightness(int32_t lightId) {
+ return mEventHub->getLightBrightness(mId, lightId);
+ }
+
+ inline void setLightBrightness(int32_t lightId, int32_t brightness) {
+ return mEventHub->setLightBrightness(mId, lightId, brightness);
+ }
+
+ inline std::optional<std::unordered_map<LightColor, int32_t>> getLightIntensities(
+ int32_t lightId) {
+ return mEventHub->getLightIntensities(mId, lightId);
+ }
+
+ inline void setLightIntensities(int32_t lightId,
+ std::unordered_map<LightColor, int32_t> intensities) {
+ return mEventHub->setLightIntensities(mId, lightId, intensities);
+ }
+
inline std::vector<TouchVideoFrame> getVideoFrames() { return mEventHub->getVideoFrames(mId); }
inline int32_t getScanCodeState(int32_t scanCode) const {
return mEventHub->getScanCodeState(mId, scanCode);
diff --git a/services/inputflinger/reader/include/InputReader.h b/services/inputflinger/reader/include/InputReader.h
index 81e3e9a..1405671 100644
--- a/services/inputflinger/reader/include/InputReader.h
+++ b/services/inputflinger/reader/include/InputReader.h
@@ -99,6 +99,18 @@
std::optional<int32_t> getBatteryStatus(int32_t deviceId) override;
+ std::vector<int32_t> getLightIds(int32_t deviceId) override;
+
+ const InputDeviceLightInfo* getLightInfo(int32_t deviceId, int32_t lightId) override;
+
+ bool setLightColor(int32_t deviceId, int32_t lightId, int32_t color) override;
+
+ bool setLightPlayerId(int32_t deviceId, int32_t lightId, int32_t playerId) override;
+
+ std::optional<int32_t> getLightColor(int32_t deviceId, int32_t lightId) override;
+
+ std::optional<int32_t> getLightPlayerId(int32_t deviceId, int32_t lightId) override;
+
protected:
// These members are protected so they can be instrumented by test cases.
virtual std::shared_ptr<InputDevice> createDeviceLocked(int32_t deviceId,
diff --git a/services/inputflinger/reader/mapper/BatteryInputMapper.cpp b/services/inputflinger/reader/mapper/BatteryInputMapper.cpp
index afdc5ab..e4fb3a6 100644
--- a/services/inputflinger/reader/mapper/BatteryInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/BatteryInputMapper.cpp
@@ -24,7 +24,7 @@
: InputMapper(deviceContext) {}
uint32_t BatteryInputMapper::getSources() {
- return 0;
+ return AINPUT_SOURCE_UNKNOWN;
}
void BatteryInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
diff --git a/services/inputflinger/reader/mapper/InputMapper.h b/services/inputflinger/reader/mapper/InputMapper.h
index 1cc5979..bd543e5 100644
--- a/services/inputflinger/reader/mapper/InputMapper.h
+++ b/services/inputflinger/reader/mapper/InputMapper.h
@@ -77,6 +77,11 @@
virtual std::optional<int32_t> getBatteryCapacity() { return std::nullopt; }
virtual std::optional<int32_t> getBatteryStatus() { return std::nullopt; }
+ virtual bool setLightColor(int32_t lightId, int32_t color) { return true; }
+ virtual bool setLightPlayerId(int32_t lightId, int32_t playerId) { return true; }
+ virtual std::optional<int32_t> getLightColor(int32_t lightId) { return std::nullopt; }
+ virtual std::optional<int32_t> getLightPlayerId(int32_t lightId) { return std::nullopt; }
+
virtual int32_t getMetaState();
virtual void updateMetaState(int32_t keyCode);
diff --git a/services/inputflinger/reader/mapper/LightInputMapper.cpp b/services/inputflinger/reader/mapper/LightInputMapper.cpp
new file mode 100644
index 0000000..be1f722
--- /dev/null
+++ b/services/inputflinger/reader/mapper/LightInputMapper.cpp
@@ -0,0 +1,478 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#include <locale>
+#include <regex>
+
+#include "../Macros.h"
+
+#include "LightInputMapper.h"
+#include "input/NamedEnum.h"
+
+// Log detailed debug messages about input device lights.
+static constexpr bool DEBUG_LIGHT_DETAILS = false;
+
+namespace android {
+
+static inline int32_t getAlpha(int32_t color) {
+ return (color >> 24) & 0xff;
+}
+
+static inline int32_t getRed(int32_t color) {
+ return (color >> 16) & 0xff;
+}
+
+static inline int32_t getGreen(int32_t color) {
+ return (color >> 8) & 0xff;
+}
+
+static inline int32_t getBlue(int32_t color) {
+ return color & 0xff;
+}
+
+static inline int32_t toArgb(int32_t brightness, int32_t red, int32_t green, int32_t blue) {
+ return (brightness & 0xff) << 24 | (red & 0xff) << 16 | (green & 0xff) << 8 | (blue & 0xff);
+}
+
+/**
+ * Light input mapper owned by InputReader device, implements the native API for querying input
+ * lights, getting and setting the lights brightness and color, by interacting with EventHub
+ * devices.
+ * TODO b/180342233: Reconsider the inputflinger design to accommodate the device class
+ * like lights and battery.
+ */
+LightInputMapper::LightInputMapper(InputDeviceContext& deviceContext)
+ : InputMapper(deviceContext) {}
+
+LightInputMapper::~LightInputMapper() {}
+
+std::optional<std::int32_t> LightInputMapper::Light::getRawLightBrightness(int32_t rawLightId) {
+ std::optional<RawLightInfo> rawInfo = context.getRawLightInfo(rawLightId);
+ std::optional<int32_t> ret = context.getLightBrightness(rawLightId);
+ if (!rawInfo.has_value() || !ret.has_value()) {
+ return std::nullopt;
+ }
+ int brightness = ret.value();
+
+ // If the light node doesn't have max brightness, use the default max brightness.
+ int rawMaxBrightness = rawInfo->maxBrightness.value_or(MAX_BRIGHTNESS);
+ float ratio = MAX_BRIGHTNESS / rawMaxBrightness;
+ // Scale the returned brightness in [0, rawMaxBrightness] to [0, 255]
+ if (rawMaxBrightness != MAX_BRIGHTNESS) {
+ brightness = brightness * ratio;
+ }
+ if (DEBUG_LIGHT_DETAILS) {
+ ALOGD("getRawLightBrightness rawLightId %d brightness 0x%x ratio %.2f", rawLightId,
+ brightness, ratio);
+ }
+ return brightness;
+}
+
+void LightInputMapper::Light::setRawLightBrightness(int32_t rawLightId, int32_t brightness) {
+ std::optional<RawLightInfo> rawInfo = context.getRawLightInfo(rawLightId);
+ if (!rawInfo.has_value()) {
+ return;
+ }
+ // If the light node doesn't have max brightness, use the default max brightness.
+ int rawMaxBrightness = rawInfo->maxBrightness.value_or(MAX_BRIGHTNESS);
+ float ratio = MAX_BRIGHTNESS / rawMaxBrightness;
+ // Scale the requested brightness in [0, 255] to [0, rawMaxBrightness]
+ if (rawMaxBrightness != MAX_BRIGHTNESS) {
+ brightness = ceil(brightness / ratio);
+ }
+ if (DEBUG_LIGHT_DETAILS) {
+ ALOGD("setRawLightBrightness rawLightId %d brightness 0x%x ratio %.2f", rawLightId,
+ brightness, ratio);
+ }
+ context.setLightBrightness(rawLightId, brightness);
+}
+
+bool LightInputMapper::SingleLight::setLightColor(int32_t color) {
+ int32_t brightness = getAlpha(color);
+ setRawLightBrightness(rawId, brightness);
+
+ return true;
+}
+
+bool LightInputMapper::RgbLight::setLightColor(int32_t color) {
+ // Compose color value as per:
+ // https://developer.android.com/reference/android/graphics/Color?hl=en
+ // int color = (A & 0xff) << 24 | (R & 0xff) << 16 | (G & 0xff) << 8 | (B & 0xff);
+ // The alpha component is used to scale the R,G,B leds brightness, with the ratio to
+ // MAX_BRIGHTNESS.
+ brightness = getAlpha(color);
+ int32_t red = 0;
+ int32_t green = 0;
+ int32_t blue = 0;
+ if (brightness > 0) {
+ float ratio = MAX_BRIGHTNESS / brightness;
+ red = ceil(getRed(color) / ratio);
+ green = ceil(getGreen(color) / ratio);
+ blue = ceil(getBlue(color) / ratio);
+ }
+ setRawLightBrightness(rawRgbIds.at(LightColor::RED), red);
+ setRawLightBrightness(rawRgbIds.at(LightColor::GREEN), green);
+ setRawLightBrightness(rawRgbIds.at(LightColor::BLUE), blue);
+ if (rawGlobalId.has_value()) {
+ setRawLightBrightness(rawGlobalId.value(), brightness);
+ }
+
+ return true;
+}
+
+bool LightInputMapper::MultiColorLight::setLightColor(int32_t color) {
+ std::unordered_map<LightColor, int32_t> intensities;
+ intensities.emplace(LightColor::RED, getRed(color));
+ intensities.emplace(LightColor::GREEN, getGreen(color));
+ intensities.emplace(LightColor::BLUE, getBlue(color));
+
+ context.setLightIntensities(rawId, intensities);
+ setRawLightBrightness(rawId, getAlpha(color));
+ return true;
+}
+
+std::optional<int32_t> LightInputMapper::SingleLight::getLightColor() {
+ std::optional<int32_t> brightness = getRawLightBrightness(rawId);
+ if (!brightness.has_value()) {
+ return std::nullopt;
+ }
+
+ return toArgb(brightness.value(), 0 /* red */, 0 /* green */, 0 /* blue */);
+}
+
+std::optional<int32_t> LightInputMapper::RgbLight::getLightColor() {
+ // If the Alpha component is zero, then return color 0.
+ if (brightness == 0) {
+ return 0;
+ }
+ // Compose color value as per:
+ // https://developer.android.com/reference/android/graphics/Color?hl=en
+ // int color = (A & 0xff) << 24 | (R & 0xff) << 16 | (G & 0xff) << 8 | (B & 0xff);
+ std::optional<int32_t> redOr = getRawLightBrightness(rawRgbIds.at(LightColor::RED));
+ std::optional<int32_t> greenOr = getRawLightBrightness(rawRgbIds.at(LightColor::GREEN));
+ std::optional<int32_t> blueOr = getRawLightBrightness(rawRgbIds.at(LightColor::BLUE));
+ // If we can't get brightness for any of the RGB light
+ if (!redOr.has_value() || !greenOr.has_value() || !blueOr.has_value()) {
+ return std::nullopt;
+ }
+
+ // Compose the ARGB format color. As the R,G,B color led brightness is scaled by Alpha
+ // value, scale it back to return the nominal color value.
+ float ratio = MAX_BRIGHTNESS / brightness;
+ int32_t red = round(redOr.value() * ratio);
+ int32_t green = round(greenOr.value() * ratio);
+ int32_t blue = round(blueOr.value() * ratio);
+
+ if (red > MAX_BRIGHTNESS || green > MAX_BRIGHTNESS || blue > MAX_BRIGHTNESS) {
+ // Previously stored brightness isn't valid for current LED values, so just reset to max
+ // brightness since an app couldn't have provided these values in the first place.
+ red = redOr.value();
+ green = greenOr.value();
+ blue = blueOr.value();
+ brightness = MAX_BRIGHTNESS;
+ }
+
+ return toArgb(brightness, red, green, blue);
+}
+
+std::optional<int32_t> LightInputMapper::MultiColorLight::getLightColor() {
+ auto ret = context.getLightIntensities(rawId);
+ if (!ret.has_value()) {
+ return std::nullopt;
+ }
+ std::unordered_map<LightColor, int32_t> intensities = ret.value();
+ // Get red, green, blue colors
+ int32_t color = toArgb(0 /* brightness */, intensities.at(LightColor::RED) /* red */,
+ intensities.at(LightColor::GREEN) /* green */,
+ intensities.at(LightColor::BLUE) /* blue */);
+ // Get brightness
+ std::optional<int32_t> brightness = getRawLightBrightness(rawId);
+ if (brightness.has_value()) {
+ return toArgb(brightness.value() /* A */, 0, 0, 0) | color;
+ }
+ return std::nullopt;
+}
+
+bool LightInputMapper::PlayerIdLight::setLightPlayerId(int32_t playerId) {
+ if (rawLightIds.find(playerId) == rawLightIds.end()) {
+ return false;
+ }
+ for (const auto& [id, rawId] : rawLightIds) {
+ if (playerId == id) {
+ setRawLightBrightness(rawId, MAX_BRIGHTNESS);
+ } else {
+ setRawLightBrightness(rawId, 0);
+ }
+ }
+ return true;
+}
+
+std::optional<int32_t> LightInputMapper::PlayerIdLight::getLightPlayerId() {
+ for (const auto& [id, rawId] : rawLightIds) {
+ std::optional<int32_t> brightness = getRawLightBrightness(rawId);
+ if (brightness.has_value() && brightness.value() > 0) {
+ return id;
+ }
+ }
+ return std::nullopt;
+}
+
+void LightInputMapper::SingleLight::dump(std::string& dump) {
+ dump += StringPrintf(INDENT4 "Color: 0x%x\n", getLightColor().value_or(0));
+}
+
+void LightInputMapper::PlayerIdLight::dump(std::string& dump) {
+ dump += StringPrintf(INDENT4 "PlayerId: %d\n", getLightPlayerId().value_or(-1));
+ dump += StringPrintf(INDENT4 "Raw Player ID LEDs:");
+ for (const auto& [id, rawId] : rawLightIds) {
+ dump += StringPrintf("id %d -> %d ", id, rawId);
+ }
+ dump += "\n";
+}
+
+void LightInputMapper::RgbLight::dump(std::string& dump) {
+ dump += StringPrintf(INDENT4 "Color: 0x%x\n", getLightColor().value_or(0));
+ dump += StringPrintf(INDENT4 "Raw RGB LEDs: [%d, %d, %d] ", rawRgbIds.at(LightColor::RED),
+ rawRgbIds.at(LightColor::GREEN), rawRgbIds.at(LightColor::BLUE));
+ if (rawGlobalId.has_value()) {
+ dump += StringPrintf(INDENT4 "Raw Global LED: [%d] ", rawGlobalId.value());
+ }
+ dump += "\n";
+}
+
+void LightInputMapper::MultiColorLight::dump(std::string& dump) {
+ dump += StringPrintf(INDENT4 "Color: 0x%x\n", getLightColor().value_or(0));
+}
+
+uint32_t LightInputMapper::getSources() {
+ return AINPUT_SOURCE_UNKNOWN;
+}
+
+void LightInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
+ InputMapper::populateDeviceInfo(info);
+
+ for (const auto& [lightId, light] : mLights) {
+ // Input device light doesn't support ordinal, always pass 1.
+ InputDeviceLightInfo lightInfo(light->name, light->id, light->type, 1 /* ordinal */);
+ info->addLightInfo(lightInfo);
+ }
+}
+
+void LightInputMapper::dump(std::string& dump) {
+ dump += INDENT2 "Light Input Mapper:\n";
+ dump += INDENT3 "Lights:\n";
+ for (const auto& [lightId, light] : mLights) {
+ dump += StringPrintf(INDENT4 "Id: %d", lightId);
+ dump += StringPrintf(INDENT4 "Name: %s", light->name.c_str());
+ dump += StringPrintf(INDENT4 "Type: %s", NamedEnum::string(light->type).c_str());
+ light->dump(dump);
+ }
+ // Dump raw lights
+ dump += INDENT3 "RawLights:\n";
+ dump += INDENT4 "Id:\t Name:\t Flags:\t Max brightness:\t Brightness\n";
+ const std::vector<int32_t> rawLightIds = getDeviceContext().getRawLightIds();
+ // Map from raw light id to raw light info
+ std::unordered_map<int32_t, RawLightInfo> rawInfos;
+ for (const auto& rawId : rawLightIds) {
+ std::optional<RawLightInfo> rawInfo = getDeviceContext().getRawLightInfo(rawId);
+ if (!rawInfo.has_value()) {
+ continue;
+ }
+ dump += StringPrintf(INDENT4 "%d", rawId);
+ dump += StringPrintf(INDENT4 "%s", rawInfo->name.c_str());
+ dump += StringPrintf(INDENT4 "%s", rawInfo->flags.string().c_str());
+ dump += StringPrintf(INDENT4 "%d", rawInfo->maxBrightness.value_or(MAX_BRIGHTNESS));
+ dump += StringPrintf(INDENT4 "%d\n",
+ getDeviceContext().getLightBrightness(rawId).value_or(-1));
+ }
+}
+
+void LightInputMapper::configure(nsecs_t when, const InputReaderConfiguration* config,
+ uint32_t changes) {
+ InputMapper::configure(when, config, changes);
+
+ if (!changes) { // first time only
+ bool hasRedLed = false;
+ bool hasGreenLed = false;
+ bool hasBlueLed = false;
+ std::optional<int32_t> rawGlobalId = std::nullopt;
+ // Player ID light common name string
+ std::string playerIdName;
+ // Raw RGB color to raw light ID
+ std::unordered_map<LightColor, int32_t /* rawLightId */> rawRgbIds;
+ // Map from player Id to raw light Id
+ std::unordered_map<int32_t, int32_t> playerIdLightIds;
+ mLights.clear();
+
+ // Check raw lights
+ const std::vector<int32_t> rawLightIds = getDeviceContext().getRawLightIds();
+ // Map from raw light id to raw light info
+ std::unordered_map<int32_t, RawLightInfo> rawInfos;
+ for (const auto& rawId : rawLightIds) {
+ std::optional<RawLightInfo> rawInfo = getDeviceContext().getRawLightInfo(rawId);
+ if (!rawInfo.has_value()) {
+ continue;
+ }
+ rawInfos.insert_or_assign(rawId, rawInfo.value());
+ // Check if this is a group LEDs for player ID
+ std::regex lightPattern("([a-z]+)([0-9]+)");
+ std::smatch results;
+ if (std::regex_match(rawInfo->name, results, lightPattern)) {
+ std::string commonName = results[1].str();
+ int32_t playerId = std::stoi(results[2]);
+ if (playerIdLightIds.empty()) {
+ playerIdName = commonName;
+ playerIdLightIds.insert_or_assign(playerId, rawId);
+ } else {
+ // Make sure the player ID leds have common string name
+ if (playerIdName.compare(commonName) == 0 &&
+ playerIdLightIds.find(playerId) == playerIdLightIds.end()) {
+ playerIdLightIds.insert_or_assign(playerId, rawId);
+ }
+ }
+ }
+ // Check if this is an LED of RGB light
+ if (rawInfo->flags.test(InputLightClass::RED)) {
+ hasRedLed = true;
+ rawRgbIds.emplace(LightColor::RED, rawId);
+ }
+ if (rawInfo->flags.test(InputLightClass::GREEN)) {
+ hasGreenLed = true;
+ rawRgbIds.emplace(LightColor::GREEN, rawId);
+ }
+ if (rawInfo->flags.test(InputLightClass::BLUE)) {
+ hasBlueLed = true;
+ rawRgbIds.emplace(LightColor::BLUE, rawId);
+ }
+ if (rawInfo->flags.test(InputLightClass::GLOBAL)) {
+ rawGlobalId = rawId;
+ }
+ if (DEBUG_LIGHT_DETAILS) {
+ ALOGD("Light rawId %d name %s max %d flags %s \n", rawInfo->id,
+ rawInfo->name.c_str(), rawInfo->maxBrightness.value_or(MAX_BRIGHTNESS),
+ rawInfo->flags.string().c_str());
+ }
+ }
+
+ // Construct a player ID light
+ if (playerIdLightIds.size() > 1) {
+ std::unique_ptr<Light> light =
+ std::make_unique<PlayerIdLight>(getDeviceContext(), playerIdName, ++mNextId,
+ playerIdLightIds);
+ mLights.insert_or_assign(light->id, std::move(light));
+ // Remove these raw lights from raw light info as they've been used to compose a
+ // Player ID light, so we do not expose these raw lights as single lights.
+ for (const auto& [playerId, rawId] : playerIdLightIds) {
+ rawInfos.erase(rawId);
+ }
+ }
+ // Construct a RGB light for composed RGB light
+ if (hasRedLed && hasGreenLed && hasBlueLed) {
+ if (DEBUG_LIGHT_DETAILS) {
+ ALOGD("Rgb light ids [%d, %d, %d] \n", rawRgbIds.at(LightColor::RED),
+ rawRgbIds.at(LightColor::GREEN), rawRgbIds.at(LightColor::BLUE));
+ }
+ std::unique_ptr<Light> light = std::make_unique<RgbLight>(getDeviceContext(), ++mNextId,
+ rawRgbIds, rawGlobalId);
+ mLights.insert_or_assign(light->id, std::move(light));
+ // Remove from raw light info as they've been composed a RBG light.
+ rawInfos.erase(rawRgbIds.at(LightColor::RED));
+ rawInfos.erase(rawRgbIds.at(LightColor::GREEN));
+ rawInfos.erase(rawRgbIds.at(LightColor::BLUE));
+ if (rawGlobalId.has_value()) {
+ rawInfos.erase(rawGlobalId.value());
+ }
+ }
+
+ // Check the rest of raw light infos
+ for (const auto& [rawId, rawInfo] : rawInfos) {
+ // If the node is multi-color led, construct a MULTI_COLOR light
+ if (rawInfo.flags.test(InputLightClass::MULTI_INDEX) &&
+ rawInfo.flags.test(InputLightClass::MULTI_INTENSITY)) {
+ if (DEBUG_LIGHT_DETAILS) {
+ ALOGD("Multicolor light Id %d name %s \n", rawInfo.id, rawInfo.name.c_str());
+ }
+ std::unique_ptr<Light> light =
+ std::make_unique<MultiColorLight>(getDeviceContext(), rawInfo.name,
+ ++mNextId, rawInfo.id);
+ mLights.insert_or_assign(light->id, std::move(light));
+ continue;
+ }
+ // Construct a single LED light
+ if (DEBUG_LIGHT_DETAILS) {
+ ALOGD("Single light Id %d name %s \n", rawInfo.id, rawInfo.name.c_str());
+ }
+ std::unique_ptr<Light> light =
+ std::make_unique<SingleLight>(getDeviceContext(), rawInfo.name, ++mNextId,
+ rawInfo.id);
+
+ mLights.insert_or_assign(light->id, std::move(light));
+ }
+ }
+}
+
+void LightInputMapper::reset(nsecs_t when) {
+ InputMapper::reset(when);
+}
+
+void LightInputMapper::process(const RawEvent* rawEvent) {}
+
+bool LightInputMapper::setLightColor(int32_t lightId, int32_t color) {
+ auto it = mLights.find(lightId);
+ if (it == mLights.end()) {
+ return false;
+ }
+ auto& light = it->second;
+ if (DEBUG_LIGHT_DETAILS) {
+ ALOGD("setLightColor lightId %d type %s color 0x%x", lightId,
+ NamedEnum::string(light->type).c_str(), color);
+ }
+ return light->setLightColor(color);
+}
+
+std::optional<int32_t> LightInputMapper::getLightColor(int32_t lightId) {
+ auto it = mLights.find(lightId);
+ if (it == mLights.end()) {
+ return std::nullopt;
+ }
+ auto& light = it->second;
+ std::optional<int32_t> color = light->getLightColor();
+ if (DEBUG_LIGHT_DETAILS) {
+ ALOGD("getLightColor lightId %d type %s color 0x%x", lightId,
+ NamedEnum::string(light->type).c_str(), color.value_or(0));
+ }
+ return color;
+}
+
+bool LightInputMapper::setLightPlayerId(int32_t lightId, int32_t playerId) {
+ auto it = mLights.find(lightId);
+ if (it == mLights.end()) {
+ return false;
+ }
+ auto& light = it->second;
+ return light->setLightPlayerId(playerId);
+}
+
+std::optional<int32_t> LightInputMapper::getLightPlayerId(int32_t lightId) {
+ auto it = mLights.find(lightId);
+ if (it == mLights.end()) {
+ return std::nullopt;
+ }
+ auto& light = it->second;
+ return light->getLightPlayerId();
+}
+
+} // namespace android
diff --git a/services/inputflinger/reader/mapper/LightInputMapper.h b/services/inputflinger/reader/mapper/LightInputMapper.h
new file mode 100644
index 0000000..9254720
--- /dev/null
+++ b/services/inputflinger/reader/mapper/LightInputMapper.h
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#ifndef _UI_INPUTREADER_LIGHT_INPUT_MAPPER_H
+#define _UI_INPUTREADER_LIGHT_INPUT_MAPPER_H
+
+#include "InputMapper.h"
+
+namespace android {
+
+class LightInputMapper : public InputMapper {
+ // Refer to https://developer.android.com/reference/kotlin/android/graphics/Color
+ /* Number of colors : {red, green, blue} */
+ static constexpr size_t COLOR_NUM = 3;
+ static constexpr int32_t MAX_BRIGHTNESS = 0xff;
+
+public:
+ explicit LightInputMapper(InputDeviceContext& deviceContext);
+ ~LightInputMapper() override;
+
+ uint32_t getSources() override;
+ void populateDeviceInfo(InputDeviceInfo* deviceInfo) override;
+ void dump(std::string& dump) override;
+ void configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes) override;
+ void reset(nsecs_t when) override;
+ void process(const RawEvent* rawEvent) override;
+ bool setLightColor(int32_t lightId, int32_t color) override;
+ bool setLightPlayerId(int32_t lightId, int32_t playerId) override;
+ std::optional<int32_t> getLightColor(int32_t lightId) override;
+ std::optional<int32_t> getLightPlayerId(int32_t lightId) override;
+
+private:
+ struct Light {
+ explicit Light(InputDeviceContext& context, std::string name, int32_t id,
+ InputDeviceLightType type)
+ : context(context), name(name), id(id), type(type) {}
+ virtual ~Light() {}
+ InputDeviceContext& context;
+ std::string name;
+ int32_t id;
+ InputDeviceLightType type;
+
+ virtual bool setLightColor(int32_t color) { return false; }
+ virtual std::optional<int32_t> getLightColor() { return std::nullopt; }
+ virtual bool setLightPlayerId(int32_t playerId) { return false; }
+ virtual std::optional<int32_t> getLightPlayerId() { return std::nullopt; }
+
+ virtual void dump(std::string& dump) {}
+
+ std::optional<std::int32_t> getRawLightBrightness(int32_t rawLightId);
+ void setRawLightBrightness(int32_t rawLightId, int32_t brightness);
+ };
+
+ struct SingleLight : public Light {
+ explicit SingleLight(InputDeviceContext& context, std::string name, int32_t id,
+ int32_t rawId)
+ : Light(context, name, id, InputDeviceLightType::SINGLE), rawId(rawId) {}
+ int32_t rawId;
+
+ bool setLightColor(int32_t color) override;
+ std::optional<int32_t> getLightColor() override;
+ void dump(std::string& dump) override;
+ };
+
+ struct RgbLight : public Light {
+ explicit RgbLight(InputDeviceContext& context, int32_t id,
+ std::unordered_map<LightColor, int32_t> rawRgbIds,
+ std::optional<int32_t> rawGlobalId)
+ : Light(context, "RGB", id, InputDeviceLightType::RGB),
+ rawRgbIds(rawRgbIds),
+ rawGlobalId(rawGlobalId) {
+ brightness = rawGlobalId.has_value()
+ ? getRawLightBrightness(rawGlobalId.value()).value_or(MAX_BRIGHTNESS)
+ : MAX_BRIGHTNESS;
+ }
+ // Map from color to raw light id.
+ std::unordered_map<LightColor, int32_t /* rawLightId */> rawRgbIds;
+ // Optional global control raw light id.
+ std::optional<int32_t> rawGlobalId;
+ int32_t brightness;
+
+ bool setLightColor(int32_t color) override;
+ std::optional<int32_t> getLightColor() override;
+ void dump(std::string& dump) override;
+ };
+
+ struct MultiColorLight : public Light {
+ explicit MultiColorLight(InputDeviceContext& context, std::string name, int32_t id,
+ int32_t rawId)
+ : Light(context, name, id, InputDeviceLightType::MULTI_COLOR), rawId(rawId) {}
+ int32_t rawId;
+
+ bool setLightColor(int32_t color) override;
+ std::optional<int32_t> getLightColor() override;
+ void dump(std::string& dump) override;
+ };
+
+ struct PlayerIdLight : public Light {
+ explicit PlayerIdLight(InputDeviceContext& context, std::string name, int32_t id,
+ std::unordered_map<int32_t, int32_t> rawLightIds)
+ : Light(context, name, id, InputDeviceLightType::PLAYER_ID),
+ rawLightIds(rawLightIds) {}
+ // Map from player Id to raw light Id
+ std::unordered_map<int32_t, int32_t> rawLightIds;
+
+ bool setLightPlayerId(int32_t palyerId) override;
+ std::optional<int32_t> getLightPlayerId() override;
+ void dump(std::string& dump) override;
+ };
+
+ int32_t mNextId = 0;
+
+ // Light color map from light color to the color index.
+ static const std::unordered_map<std::string, size_t> LIGHT_COLORS;
+
+ // Light map from light ID to Light
+ std::unordered_map<int32_t, std::unique_ptr<Light>> mLights;
+};
+
+} // namespace android
+
+#endif // _UI_INPUTREADER_LIGHT_INPUT_MAPPER_H
\ No newline at end of file
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 409c62a..2836516 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -22,6 +22,7 @@
#include <InputReaderBase.h>
#include <InputReaderFactory.h>
#include <KeyboardInputMapper.h>
+#include <LightInputMapper.h>
#include <MultiTouchInputMapper.h>
#include <SensorInputMapper.h>
#include <SingleTouchInputMapper.h>
@@ -36,6 +37,7 @@
#include <math.h>
#include <memory>
+#include <regex>
#include "input/DisplayViewport.h"
#include "input/Input.h"
@@ -70,6 +72,9 @@
static constexpr int32_t THIRD_TRACKING_ID = 2;
static constexpr int32_t BATTERY_STATUS = 4;
static constexpr int32_t BATTERY_CAPACITY = 66;
+static constexpr int32_t LIGHT_BRIGHTNESS = 0x55000000;
+static constexpr int32_t LIGHT_COLOR = 0x7F448866;
+static constexpr int32_t LIGHT_PLAYER_ID = 2;
// Error tolerance for floating point assertions.
static const float EPSILON = 0.001f;
@@ -83,6 +88,10 @@
return (x + y) / 2;
}
+// 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}};
// --- FakePointerController ---
@@ -412,6 +421,12 @@
std::vector<RawEvent> mEvents GUARDED_BY(mLock);
std::unordered_map<int32_t /*deviceId*/, std::vector<TouchVideoFrame>> mVideoFrames;
std::vector<int32_t> mVibrators = {0, 1};
+ std::unordered_map<int32_t, RawLightInfo> mRawLightInfos;
+ // Simulates a device light brightness, from light id to light brightness.
+ std::unordered_map<int32_t /* lightId */, int32_t /* brightness*/> mLightBrightness;
+ // Simulates a device light intensities, from light id to light intensities map.
+ std::unordered_map<int32_t /* lightId */, std::unordered_map<LightColor, int32_t>>
+ mLightIntensities;
public:
virtual ~FakeEventHub() {
@@ -562,6 +577,19 @@
device->mscBitmask.loadFromBuffer(buffer);
}
+ void addRawLightInfo(int32_t rawId, RawLightInfo&& info) {
+ mRawLightInfos.emplace(rawId, std::move(info));
+ }
+
+ void fakeLightBrightness(int32_t rawId, int32_t brightness) {
+ mLightBrightness.emplace(rawId, brightness);
+ }
+
+ void fakeLightIntensities(int32_t rawId,
+ const std::unordered_map<LightColor, int32_t> intensities) {
+ mLightIntensities.emplace(rawId, std::move(intensities));
+ }
+
bool getLedState(int32_t deviceId, int32_t led) {
Device* device = getDevice(deviceId);
return device->leds.valueFor(led);
@@ -869,6 +897,48 @@
std::optional<int32_t> getBatteryStatus(int32_t) const override { return BATTERY_STATUS; }
+ const std::vector<int32_t> getRawLightIds(int32_t deviceId) override {
+ std::vector<int32_t> ids;
+ for (const auto& [rawId, info] : mRawLightInfos) {
+ ids.push_back(rawId);
+ }
+ return ids;
+ }
+
+ std::optional<RawLightInfo> getRawLightInfo(int32_t deviceId, int32_t lightId) override {
+ auto it = mRawLightInfos.find(lightId);
+ if (it == mRawLightInfos.end()) {
+ return std::nullopt;
+ }
+ return it->second;
+ }
+
+ void setLightBrightness(int32_t deviceId, int32_t lightId, int32_t brightness) override {
+ mLightBrightness.emplace(lightId, brightness);
+ }
+
+ void setLightIntensities(int32_t deviceId, int32_t lightId,
+ std::unordered_map<LightColor, int32_t> intensities) override {
+ mLightIntensities.emplace(lightId, intensities);
+ };
+
+ std::optional<int32_t> getLightBrightness(int32_t deviceId, int32_t lightId) override {
+ auto lightIt = mLightBrightness.find(lightId);
+ if (lightIt == mLightBrightness.end()) {
+ return std::nullopt;
+ }
+ return lightIt->second;
+ }
+
+ std::optional<std::unordered_map<LightColor, int32_t>> getLightIntensities(
+ int32_t deviceId, int32_t lightId) override {
+ auto lightIt = mLightIntensities.find(lightId);
+ if (lightIt == mLightIntensities.end()) {
+ return std::nullopt;
+ }
+ return lightIt->second;
+ };
+
virtual bool isExternal(int32_t) const {
return false;
}
@@ -1976,6 +2046,49 @@
ASSERT_EQ(mReader->getBatteryStatus(deviceId), BATTERY_STATUS);
}
+class FakeLightInputMapper : public FakeInputMapper {
+public:
+ FakeLightInputMapper(InputDeviceContext& deviceContext, uint32_t sources)
+ : FakeInputMapper(deviceContext, sources) {}
+
+ bool setLightColor(int32_t lightId, int32_t color) override {
+ getDeviceContext().setLightBrightness(lightId, color >> 24);
+ return true;
+ }
+
+ std::optional<int32_t> getLightColor(int32_t lightId) override {
+ std::optional<int32_t> result = getDeviceContext().getLightBrightness(lightId);
+ if (!result.has_value()) {
+ return std::nullopt;
+ }
+ return result.value() << 24;
+ }
+};
+
+TEST_F(InputReaderTest, LightGetColor) {
+ constexpr int32_t deviceId = END_RESERVED_ID + 1000;
+ Flags<InputDeviceClass> deviceClass = InputDeviceClass::KEYBOARD | InputDeviceClass::LIGHT;
+ constexpr int32_t eventHubId = 1;
+ const char* DEVICE_LOCATION = "BLUETOOTH";
+ std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake", DEVICE_LOCATION);
+ FakeLightInputMapper& mapper =
+ device->addMapper<FakeLightInputMapper>(eventHubId, AINPUT_SOURCE_KEYBOARD);
+ mReader->pushNextDevice(device);
+ RawLightInfo info = {.id = 1,
+ .name = "Mono",
+ .maxBrightness = 255,
+ .flags = InputLightClass::BRIGHTNESS,
+ .path = ""};
+ mFakeEventHub->addRawLightInfo(1 /* rawId */, std::move(info));
+ mFakeEventHub->fakeLightBrightness(1 /* rawId */, 0x55);
+
+ ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr));
+ ASSERT_NO_FATAL_FAILURE(mapper.assertConfigureWasCalled());
+
+ ASSERT_TRUE(mReader->setLightColor(deviceId, 1 /* lightId */, LIGHT_BRIGHTNESS));
+ ASSERT_EQ(mReader->getLightColor(deviceId, 1 /* lightId */), LIGHT_BRIGHTNESS);
+}
+
// --- InputReaderIntegrationTest ---
// These tests create and interact with the InputReader only through its interface.
@@ -2883,14 +2996,136 @@
BatteryInputMapper& mapper = addMapperAndConfigure<BatteryInputMapper>();
ASSERT_TRUE(mapper.getBatteryCapacity());
- ASSERT_EQ(*mapper.getBatteryCapacity(), BATTERY_CAPACITY);
+ ASSERT_EQ(mapper.getBatteryCapacity().value_or(-1), BATTERY_CAPACITY);
}
TEST_F(BatteryInputMapperTest, GetBatteryStatus) {
BatteryInputMapper& mapper = addMapperAndConfigure<BatteryInputMapper>();
ASSERT_TRUE(mapper.getBatteryStatus());
- ASSERT_EQ(*mapper.getBatteryStatus(), BATTERY_STATUS);
+ ASSERT_EQ(mapper.getBatteryStatus().value_or(-1), BATTERY_STATUS);
+}
+
+// --- LightInputMapperTest ---
+class LightInputMapperTest : public InputMapperTest {
+protected:
+ void SetUp() override { InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::LIGHT); }
+};
+
+TEST_F(LightInputMapperTest, GetSources) {
+ LightInputMapper& mapper = addMapperAndConfigure<LightInputMapper>();
+
+ ASSERT_EQ(AINPUT_SOURCE_UNKNOWN, mapper.getSources());
+}
+
+TEST_F(LightInputMapperTest, SingleLight) {
+ RawLightInfo infoSingle = {.id = 1,
+ .name = "Mono",
+ .maxBrightness = 255,
+ .flags = InputLightClass::BRIGHTNESS,
+ .path = ""};
+ mFakeEventHub->addRawLightInfo(infoSingle.id, std::move(infoSingle));
+
+ LightInputMapper& mapper = addMapperAndConfigure<LightInputMapper>();
+ InputDeviceInfo info;
+ mapper.populateDeviceInfo(&info);
+ const auto& ids = info.getLightIds();
+ ASSERT_EQ(1UL, ids.size());
+ ASSERT_EQ(InputDeviceLightType::SINGLE, info.getLightInfo(ids[0])->type);
+
+ ASSERT_TRUE(mapper.setLightColor(ids[0], LIGHT_BRIGHTNESS));
+ ASSERT_EQ(mapper.getLightColor(ids[0]).value_or(-1), LIGHT_BRIGHTNESS);
+}
+
+TEST_F(LightInputMapperTest, RGBLight) {
+ RawLightInfo infoRed = {.id = 1,
+ .name = "red",
+ .maxBrightness = 255,
+ .flags = InputLightClass::BRIGHTNESS | InputLightClass::RED,
+ .path = ""};
+ RawLightInfo infoGreen = {.id = 2,
+ .name = "green",
+ .maxBrightness = 255,
+ .flags = InputLightClass::BRIGHTNESS | InputLightClass::GREEN,
+ .path = ""};
+ RawLightInfo infoBlue = {.id = 3,
+ .name = "blue",
+ .maxBrightness = 255,
+ .flags = InputLightClass::BRIGHTNESS | InputLightClass::BLUE,
+ .path = ""};
+ mFakeEventHub->addRawLightInfo(infoRed.id, std::move(infoRed));
+ mFakeEventHub->addRawLightInfo(infoGreen.id, std::move(infoGreen));
+ mFakeEventHub->addRawLightInfo(infoBlue.id, std::move(infoBlue));
+
+ LightInputMapper& mapper = addMapperAndConfigure<LightInputMapper>();
+ InputDeviceInfo info;
+ mapper.populateDeviceInfo(&info);
+ const auto& ids = info.getLightIds();
+ ASSERT_EQ(1UL, ids.size());
+ ASSERT_EQ(InputDeviceLightType::RGB, info.getLightInfo(ids[0])->type);
+
+ ASSERT_TRUE(mapper.setLightColor(ids[0], LIGHT_COLOR));
+ ASSERT_EQ(mapper.getLightColor(ids[0]).value_or(-1), LIGHT_COLOR);
+}
+
+TEST_F(LightInputMapperTest, MultiColorRGBLight) {
+ RawLightInfo infoColor = {.id = 1,
+ .name = "red",
+ .maxBrightness = 255,
+ .flags = InputLightClass::BRIGHTNESS |
+ InputLightClass::MULTI_INTENSITY |
+ InputLightClass::MULTI_INDEX,
+ .path = ""};
+
+ mFakeEventHub->addRawLightInfo(infoColor.id, std::move(infoColor));
+
+ LightInputMapper& mapper = addMapperAndConfigure<LightInputMapper>();
+ InputDeviceInfo info;
+ mapper.populateDeviceInfo(&info);
+ const auto& ids = info.getLightIds();
+ ASSERT_EQ(1UL, ids.size());
+ ASSERT_EQ(InputDeviceLightType::MULTI_COLOR, info.getLightInfo(ids[0])->type);
+
+ ASSERT_TRUE(mapper.setLightColor(ids[0], LIGHT_COLOR));
+ ASSERT_EQ(mapper.getLightColor(ids[0]).value_or(-1), LIGHT_COLOR);
+}
+
+TEST_F(LightInputMapperTest, PlayerIdLight) {
+ RawLightInfo info1 = {.id = 1,
+ .name = "player1",
+ .maxBrightness = 255,
+ .flags = InputLightClass::BRIGHTNESS,
+ .path = ""};
+ RawLightInfo info2 = {.id = 2,
+ .name = "player2",
+ .maxBrightness = 255,
+ .flags = InputLightClass::BRIGHTNESS,
+ .path = ""};
+ RawLightInfo info3 = {.id = 3,
+ .name = "player3",
+ .maxBrightness = 255,
+ .flags = InputLightClass::BRIGHTNESS,
+ .path = ""};
+ RawLightInfo info4 = {.id = 4,
+ .name = "player4",
+ .maxBrightness = 255,
+ .flags = InputLightClass::BRIGHTNESS,
+ .path = ""};
+ mFakeEventHub->addRawLightInfo(info1.id, std::move(info1));
+ mFakeEventHub->addRawLightInfo(info2.id, std::move(info2));
+ mFakeEventHub->addRawLightInfo(info3.id, std::move(info3));
+ mFakeEventHub->addRawLightInfo(info4.id, std::move(info4));
+
+ LightInputMapper& mapper = addMapperAndConfigure<LightInputMapper>();
+ InputDeviceInfo info;
+ mapper.populateDeviceInfo(&info);
+ const auto& ids = info.getLightIds();
+ ASSERT_EQ(1UL, ids.size());
+ ASSERT_EQ(InputDeviceLightType::PLAYER_ID, info.getLightInfo(ids[0])->type);
+
+ ASSERT_FALSE(mapper.setLightColor(ids[0], LIGHT_COLOR));
+ ASSERT_TRUE(mapper.setLightPlayerId(ids[0], LIGHT_PLAYER_ID));
+ ASSERT_EQ(mapper.getLightPlayerId(ids[0]).value_or(-1), LIGHT_PLAYER_ID);
}
// --- KeyboardInputMapperTest ---