diff options
author | 2021-01-06 18:45:18 -0800 | |
---|---|---|
committer | 2021-02-22 16:19:29 -0800 | |
commit | 3fdbfef367514df07b0cf158fc24fe72aca0541f (patch) | |
tree | ef7afcbe6f8f35ccd83c55a1511019c3240b072e | |
parent | 97792f96d7760311383ca9c94590d5039a025583 (diff) |
Support Inputdevice LightsManager feature in frameworks.
Add lights manager support to input frameworks.
Bug: 161633625
Test: atest LightsManagerTest, atest InputDeviceLightsManagerTest
Change-Id: Ie00357bce0f6c98e9eada5e0a79f93f48e7a4d1b
-rw-r--r-- | include/android/input.h | 2 | ||||
-rw-r--r-- | include/input/InputDevice.h | 28 | ||||
-rw-r--r-- | libs/input/InputDevice.cpp | 27 | ||||
-rw-r--r-- | services/inputflinger/include/InputReaderBase.h | 13 | ||||
-rw-r--r-- | services/inputflinger/reader/Android.bp | 1 | ||||
-rw-r--r-- | services/inputflinger/reader/EventHub.cpp | 347 | ||||
-rw-r--r-- | services/inputflinger/reader/InputDevice.cpp | 44 | ||||
-rw-r--r-- | services/inputflinger/reader/InputReader.cpp | 64 | ||||
-rw-r--r-- | services/inputflinger/reader/include/EventHub.h | 78 | ||||
-rw-r--r-- | services/inputflinger/reader/include/InputDevice.h | 29 | ||||
-rw-r--r-- | services/inputflinger/reader/include/InputReader.h | 12 | ||||
-rw-r--r-- | services/inputflinger/reader/mapper/BatteryInputMapper.cpp | 2 | ||||
-rw-r--r-- | services/inputflinger/reader/mapper/InputMapper.h | 5 | ||||
-rw-r--r-- | services/inputflinger/reader/mapper/LightInputMapper.cpp | 478 | ||||
-rw-r--r-- | services/inputflinger/reader/mapper/LightInputMapper.h | 135 | ||||
-rw-r--r-- | services/inputflinger/tests/InputReader_test.cpp | 239 |
16 files changed, 1465 insertions, 39 deletions
diff --git a/include/android/input.h b/include/android/input.h index b70d42427d..797348742d 100644 --- a/include/android/input.h +++ b/include/android/input.h @@ -859,7 +859,7 @@ enum { /** 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 2bd7bd2004..2deb99d154 100644 --- a/include/input/InputDevice.h +++ b/include/input/InputDevice.h @@ -100,6 +100,13 @@ enum class InputDeviceSensorReportingMode : int32_t { 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 @@ struct InputDeviceSensorInfo { 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 @@ public: 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 @@ public: 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 @@ private: 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 698cf6eebc..ffcc1cd93a 100644 --- a/libs/input/InputDevice.cpp +++ b/libs/input/InputDevice.cpp @@ -170,7 +170,8 @@ InputDeviceInfo::InputDeviceInfo(const InputDeviceInfo& other) mHasButtonUnderPad(other.mHasButtonUnderPad), mHasSensor(other.mHasSensor), mMotionRanges(other.mMotionRanges), - mSensors(other.mSensors) {} + mSensors(other.mSensors), + mLights(other.mLights) {} InputDeviceInfo::~InputDeviceInfo() { } @@ -193,6 +194,7 @@ void InputDeviceInfo::initialize(int32_t id, int32_t generation, int32_t control mHasSensor = false; mMotionRanges.clear(); mSensors.clear(); + mLights.clear(); } const InputDeviceInfo::MotionRange* InputDeviceInfo::getMotionRange( @@ -229,6 +231,13 @@ void InputDeviceInfo::addSensorInfo(const InputDeviceSensorInfo& info) { 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 @@ const InputDeviceSensorInfo* InputDeviceInfo::getSensorInfo(InputDeviceSensorTyp 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 69aea84cd0..3bf212a51b 100644 --- a/services/inputflinger/include/InputReaderBase.h +++ b/services/inputflinger/include/InputReaderBase.h @@ -113,6 +113,10 @@ public: /* 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 @@ public: /* 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 7f979f2bd3..ffb98e0ca8 100644 --- a/services/inputflinger/reader/Android.bp +++ b/services/inputflinger/reader/Android.bp @@ -36,6 +36,7 @@ filegroup { "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 8f8c0513b2..e939d1cb91 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 @@ static const std::unordered_map<std::string, int32_t> BATTERY_LEVEL = {{"Critica {"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 @@ static nsecs_t processEventTimestamp(const struct input_event& event) { * 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 @@ static std::filesystem::path getSysfsRootPath(const char* devicePath) { 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 @@ static std::filesystem::path getSysfsRootPath(const char* devicePath) { } // 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 @@ EventHub::Device::Device(int fd, int32_t id, const std::string& path, virtualKeyMap(nullptr), ffEffectPlaying(false), ffEffectId(-1), + nextLightId(0), controllerNumber(0), enabled(true), isVirtual(fd < 0) {} @@ -469,6 +537,70 @@ status_t EventHub::Device::mapLed(int32_t led, int32_t* outScanCode) const { 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 @@ base::Result<std::pair<InputDeviceSensorType, int32_t>> EventHub::mapSensor(int3 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 @@ std::optional<int32_t> EventHub::getBatteryCapacity(int32_t deviceId) const { 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 @@ std::optional<int32_t> EventHub::getBatteryStatus(int32_t deviceId) const { 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 @@ status_t EventHub::openDeviceLocked(const std::string& devicePath) { // 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 @@ status_t EventHub::openDeviceLocked(const std::string& devicePath) { return -1; } - // Grab the device's sysfs path - device->sysfsRootPath = getSysfsRootPath(devicePath.c_str()); - - if (!device->sysfsRootPath.empty()) { - device->sysfsBatteryPath = findPowerSupplyNode(device->sysfsRootPath); + // Classify InputDeviceClass::BATTERY. + if (hasBattery) { + device->classes |= InputDeviceClass::BATTERY; + } - // 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 574f65122a..cbf3b69f29 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 @@ void InputDevice::addEventHubDevice(int32_t eventHubId, bool populateMappers) { 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 @@ std::optional<int32_t> InputDevice::getBatteryStatus() { 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 14fb77bfad..2d0fdf7958 100644 --- a/services/inputflinger/reader/InputReader.cpp +++ b/services/inputflinger/reader/InputReader.cpp @@ -696,6 +696,70 @@ std::optional<int32_t> InputReader::getBatteryStatus(int32_t deviceId) { 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 30967dfdf2..e6164d38d4 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 @@ enum class InputDeviceClass : uint32_t { /* 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 @@ enum class InputDeviceClass : uint32_t { 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 @@ public: 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 @@ public: 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 @@ private: 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 @@ private: 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 e4186c86cd..34c330b89c 100644 --- a/services/inputflinger/reader/include/InputDevice.h +++ b/services/inputflinger/reader/include/InputDevice.h @@ -95,6 +95,11 @@ public: 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 @@ public: 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 81e3e9ab24..1405671a50 100644 --- a/services/inputflinger/reader/include/InputReader.h +++ b/services/inputflinger/reader/include/InputReader.h @@ -99,6 +99,18 @@ public: 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 afdc5abdc2..e4fb3a6c9f 100644 --- a/services/inputflinger/reader/mapper/BatteryInputMapper.cpp +++ b/services/inputflinger/reader/mapper/BatteryInputMapper.cpp @@ -24,7 +24,7 @@ BatteryInputMapper::BatteryInputMapper(InputDeviceContext& deviceContext) : 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 1cc59795c8..bd543e5798 100644 --- a/services/inputflinger/reader/mapper/InputMapper.h +++ b/services/inputflinger/reader/mapper/InputMapper.h @@ -77,6 +77,11 @@ public: 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 0000000000..be1f722e2e --- /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 0000000000..9254720385 --- /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 409c62a0e2..2836516a41 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 SECOND_TRACKING_ID = 1; 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 @@ static inline float avg(float x, float y) { 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 @@ class FakeEventHub : public EventHubInterface { 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 @@ public: 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 @@ private: 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 @@ TEST_F(InputReaderTest, BatteryGetStatus) { 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 @@ TEST_F(BatteryInputMapperTest, GetBatteryCapacity) { 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 --- |