Introduce Device Controller to InputReader

Introduce Device Controller to manage non-evdev devices associated with
the input device kernel interface. These devices doesn't interact with
inputflinger with input events which requires a mapper to process the
raw event with assoicated input sources.

Bug: 180342233
Test: atest inputflinger_tests
Change-Id: Ib492bb3de889db180c05367bb6a7e7a2e0d78ce7
diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp
index 1af70a4..7468894 100644
--- a/services/inputflinger/reader/Android.bp
+++ b/services/inputflinger/reader/Android.bp
@@ -24,6 +24,7 @@
 cc_library_headers {
     name: "libinputreader_headers",
     export_include_dirs: [
+        "controller",
         "include",
         "mapper",
         "mapper/accumulator",
@@ -35,17 +36,16 @@
     srcs: [
         "EventHub.cpp",
         "InputDevice.cpp",
+        "controller/InputController.cpp",
         "mapper/accumulator/CursorButtonAccumulator.cpp",
         "mapper/accumulator/CursorScrollAccumulator.cpp",
         "mapper/accumulator/SingleTouchMotionAccumulator.cpp",
         "mapper/accumulator/TouchButtonAccumulator.cpp",
-        "mapper/BatteryInputMapper.cpp",
         "mapper/CursorInputMapper.cpp",
         "mapper/ExternalStylusInputMapper.cpp",
         "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 07011f5..045d24c 100644
--- a/services/inputflinger/reader/EventHub.cpp
+++ b/services/inputflinger/reader/EventHub.cpp
@@ -70,6 +70,20 @@
 static constexpr int32_t FF_STRONG_MAGNITUDE_CHANNEL_IDX = 0;
 static constexpr int32_t FF_WEAK_MAGNITUDE_CHANNEL_IDX = 1;
 
+// Mapping for input battery class node IDs lookup.
+// https://www.kernel.org/doc/Documentation/power/power_supply_class.txt
+static const std::unordered_map<std::string, InputBatteryClass> BATTERY_CLASSES =
+        {{"capacity", InputBatteryClass::CAPACITY},
+         {"capacity_level", InputBatteryClass::CAPACITY_LEVEL},
+         {"status", InputBatteryClass::STATUS}};
+
+// Mapping for input battery class node names lookup.
+// https://www.kernel.org/doc/Documentation/power/power_supply_class.txt
+static const std::unordered_map<InputBatteryClass, std::string> BATTERY_NODES =
+        {{InputBatteryClass::CAPACITY, "capacity"},
+         {InputBatteryClass::CAPACITY_LEVEL, "capacity_level"},
+         {InputBatteryClass::STATUS, "status"}};
+
 // must be kept in sync with definitions in kernel /drivers/power/supply/power_supply_sysfs.c
 static const std::unordered_map<std::string, int32_t> BATTERY_STATUS =
         {{"Unknown", BATTERY_STATUS_UNKNOWN},
@@ -349,7 +363,7 @@
         virtualKeyMap(nullptr),
         ffEffectPlaying(false),
         ffEffectId(-1),
-        nextLightId(0),
+        miscDevice(nullptr),
         controllerNumber(0),
         enabled(true),
         isVirtual(fd < 0) {}
@@ -540,32 +554,36 @@
 }
 
 // Check the sysfs path for any input device batteries, returns true if battery found.
-bool EventHub::Device::configureBatteryLocked() {
-    if (!sysfsRootPath.has_value()) {
-        return false;
+bool EventHub::MiscDevice::configureBatteryLocked() {
+    nextBatteryId = 0;
+    // Check if device has any battery.
+    const auto& paths = findSysfsNodes(sysfsRootPath, SysfsClass::POWER_SUPPLY);
+    for (const auto& nodePath : paths) {
+        RawBatteryInfo info;
+        info.id = ++nextBatteryId;
+        info.path = nodePath;
+        info.name = nodePath.filename();
+
+        // 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 = BATTERY_CLASSES.find(file.filename().string());
+            if (it != BATTERY_CLASSES.end()) {
+                info.flags |= it->second;
+            }
+        }
+        batteryInfos.insert_or_assign(info.id, info);
+        ALOGD("configureBatteryLocked rawBatteryId %d name %s", info.id, info.name.c_str());
     }
-    // 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;
+    return !batteryInfos.empty();
 }
 
 // Check the sysfs path for any input device lights, returns true if lights found.
-bool EventHub::Device::configureLightsLocked() {
-    if (!sysfsRootPath.has_value()) {
-        return false;
-    }
+bool EventHub::MiscDevice::configureLightsLocked() {
+    nextLightId = 0;
     // Check if device has any lights.
-    const auto& paths = findSysfsNodes(sysfsRootPath.value(), SysfsClass::LEDS);
+    const auto& paths = findSysfsNodes(sysfsRootPath, SysfsClass::LEDS);
     for (const auto& nodePath : paths) {
         RawLightInfo info;
         info.id = ++nextLightId;
@@ -599,6 +617,7 @@
             }
         }
         lightInfos.insert_or_assign(info.id, info);
+        ALOGD("configureLightsLocked rawLightId %d name %s", info.id, info.name.c_str());
     }
     return !lightInfos.empty();
 }
@@ -963,42 +982,92 @@
     return Errorf("Device not found or device has no key layout.");
 }
 
+// Gets the battery info map from battery ID to RawBatteryInfo of the miscellaneous device
+// associated with the device ID. Returns an empty map if no miscellaneous device found.
+const std::unordered_map<int32_t, RawBatteryInfo>& EventHub::getBatteryInfoLocked(
+        int32_t deviceId) const {
+    static const std::unordered_map<int32_t, RawBatteryInfo> EMPTY_BATTERY_INFO = {};
+    Device* device = getDeviceLocked(deviceId);
+    if (device == nullptr) {
+        return EMPTY_BATTERY_INFO;
+    }
+    auto it = mMiscDevices.find(device->identifier.descriptor);
+    if (it == mMiscDevices.end()) {
+        return EMPTY_BATTERY_INFO;
+    }
+    return it->second->batteryInfos;
+}
+
+const std::vector<int32_t> EventHub::getRawBatteryIds(int32_t deviceId) {
+    std::scoped_lock _l(mLock);
+    std::vector<int32_t> batteryIds;
+
+    for (const auto [id, info] : getBatteryInfoLocked(deviceId)) {
+        batteryIds.push_back(id);
+    }
+
+    return batteryIds;
+}
+
+std::optional<RawBatteryInfo> EventHub::getRawBatteryInfo(int32_t deviceId, int32_t batteryId) {
+    std::scoped_lock _l(mLock);
+
+    const auto infos = getBatteryInfoLocked(deviceId);
+
+    auto it = infos.find(batteryId);
+    if (it != infos.end()) {
+        return it->second;
+    }
+
+    return std::nullopt;
+}
+
+// Gets the light info map from light ID to RawLightInfo of the miscellaneous device associated
+// with the deivice ID. Returns an empty map if no miscellaneous device found.
+const std::unordered_map<int32_t, RawLightInfo>& EventHub::getLightInfoLocked(
+        int32_t deviceId) const {
+    static const std::unordered_map<int32_t, RawLightInfo> EMPTY_LIGHT_INFO = {};
+    Device* device = getDeviceLocked(deviceId);
+    if (device == nullptr) {
+        return EMPTY_LIGHT_INFO;
+    }
+    auto it = mMiscDevices.find(device->identifier.descriptor);
+    if (it == mMiscDevices.end()) {
+        return EMPTY_LIGHT_INFO;
+    }
+    return it->second->lightInfos;
+}
+
 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);
-        }
+    for (const auto [id, info] : getLightInfoLocked(deviceId)) {
+        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;
-        }
+    const auto infos = getLightInfoLocked(deviceId);
+
+    auto it = infos.find(lightId);
+    if (it != infos.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()) {
+    const auto infos = getLightInfoLocked(deviceId);
+    auto it = infos.find(lightId);
+    if (it == infos.end()) {
         return std::nullopt;
     }
     std::string buffer;
@@ -1013,13 +1082,9 @@
         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()) {
+    const auto infos = getLightInfoLocked(deviceId);
+    auto lightIt = infos.find(lightId);
+    if (lightIt == infos.end()) {
         return std::nullopt;
     }
 
@@ -1056,14 +1121,10 @@
 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);
+    const auto infos = getLightInfoLocked(deviceId);
+    auto lightIt = infos.find(lightId);
+    if (lightIt == infos.end()) {
+        ALOGE("%s lightId %d not found ", __func__, lightId);
         return;
     }
 
@@ -1078,13 +1139,9 @@
                                    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()) {
+    const auto infos = getLightInfoLocked(deviceId);
+    auto lightIt = infos.find(lightId);
+    if (lightIt == infos.end()) {
         ALOGE("Light Id %d does not exist.", lightId);
         return;
     }
@@ -1352,51 +1409,56 @@
     return nullptr;
 }
 
-std::optional<int32_t> EventHub::getBatteryCapacity(int32_t deviceId) const {
+std::optional<int32_t> EventHub::getBatteryCapacity(int32_t deviceId, int32_t batteryId) const {
     std::scoped_lock _l(mLock);
-    Device* device = getDeviceLocked(deviceId);
-    std::string buffer;
 
-    if (device == nullptr || !device->sysfsBatteryPath.has_value()) {
+    const auto infos = getBatteryInfoLocked(deviceId);
+    auto it = infos.find(batteryId);
+    if (it == infos.end()) {
         return std::nullopt;
     }
+    std::string buffer;
 
     // Some devices report battery capacity as an integer through the "capacity" file
-    if (base::ReadFileToString(device->sysfsBatteryPath.value() / "capacity", &buffer)) {
+    if (base::ReadFileToString(it->second.path / BATTERY_NODES.at(InputBatteryClass::CAPACITY),
+                               &buffer)) {
         return std::stoi(base::Trim(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.value() / "capacity_level", &buffer)) {
+    if (base::ReadFileToString(it->second.path /
+                                       BATTERY_NODES.at(InputBatteryClass::CAPACITY_LEVEL),
+                               &buffer)) {
         // Remove any white space such as trailing new line
-        const auto it = BATTERY_LEVEL.find(base::Trim(buffer));
-        if (it != BATTERY_LEVEL.end()) {
-            return it->second;
+        const auto levelIt = BATTERY_LEVEL.find(base::Trim(buffer));
+        if (levelIt != BATTERY_LEVEL.end()) {
+            return levelIt->second;
         }
     }
+
     return std::nullopt;
 }
 
-std::optional<int32_t> EventHub::getBatteryStatus(int32_t deviceId) const {
+std::optional<int32_t> EventHub::getBatteryStatus(int32_t deviceId, int32_t batteryId) const {
     std::scoped_lock _l(mLock);
-    Device* device = getDeviceLocked(deviceId);
-    std::string buffer;
-
-    if (device == nullptr || !device->sysfsBatteryPath.has_value()) {
+    const auto infos = getBatteryInfoLocked(deviceId);
+    auto it = infos.find(batteryId);
+    if (it == infos.end()) {
         return std::nullopt;
     }
+    std::string buffer;
 
-    if (!base::ReadFileToString(device->sysfsBatteryPath.value() / "status", &buffer)) {
+    if (!base::ReadFileToString(it->second.path / BATTERY_NODES.at(InputBatteryClass::STATUS),
+                                &buffer)) {
         ALOGE("Failed to read sysfs battery info: %s", strerror(errno));
         return std::nullopt;
     }
 
     // Remove white space like trailing new line
-    const auto it = BATTERY_STATUS.find(base::Trim(buffer));
-
-    if (it != BATTERY_STATUS.end()) {
-        return it->second;
+    const auto statusIt = BATTERY_STATUS.find(base::Trim(buffer));
+    if (statusIt != BATTERY_STATUS.end()) {
+        return statusIt->second;
     }
 
     return std::nullopt;
@@ -1879,11 +1941,24 @@
     // 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();
+    bool hasBattery = false;
+    bool hasLights = false;
+    // Check the sysfs root path
+    std::optional<std::filesystem::path> sysfsRootPath = getSysfsRootPath(devicePath.c_str());
+    if (sysfsRootPath.has_value()) {
+        std::shared_ptr<MiscDevice> miscDevice;
+        auto it = mMiscDevices.find(device->identifier.descriptor);
+        if (it == mMiscDevices.end()) {
+            miscDevice = std::make_shared<MiscDevice>(sysfsRootPath.value());
+        } else {
+            miscDevice = it->second;
+        }
+        hasBattery = miscDevice->configureBatteryLocked();
+        hasLights = miscDevice->configureLightsLocked();
+
+        device->miscDevice = miscDevice;
+        mMiscDevices.insert_or_assign(device->identifier.descriptor, std::move(miscDevice));
+    }
 
     // Figure out the kinds of events the device reports.
     device->readDeviceBitMask(EVIOCGBIT(EV_KEY, 0), device->keyBitmask);
@@ -2254,6 +2329,12 @@
     mClosingDevices.push_back(std::move(mDevices[device.id]));
 
     mDevices.erase(device.id);
+    // If all devices with the descriptor have been removed then the miscellaneous device should
+    // be removed too.
+    std::string descriptor = device.identifier.descriptor;
+    if (getDeviceByDescriptorLocked(descriptor) == nullptr) {
+        mMiscDevices.erase(descriptor);
+    }
 }
 
 status_t EventHub::readNotifyLocked() {
diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp
index dd1abeb..f935c36 100644
--- a/services/inputflinger/reader/InputDevice.cpp
+++ b/services/inputflinger/reader/InputDevice.cpp
@@ -21,13 +21,12 @@
 #include <input/Flags.h>
 #include <algorithm>
 
-#include "BatteryInputMapper.h"
 #include "CursorInputMapper.h"
 #include "ExternalStylusInputMapper.h"
+#include "InputController.h"
 #include "InputReaderContext.h"
 #include "JoystickInputMapper.h"
 #include "KeyboardInputMapper.h"
-#include "LightInputMapper.h"
 #include "MultiTouchInputMapper.h"
 #include "RotaryEncoderInputMapper.h"
 #include "SensorInputMapper.h"
@@ -131,6 +130,9 @@
     }
 
     for_each_mapper([&dump](InputMapper& mapper) { mapper.dump(dump); });
+    if (mController) {
+        mController->dump(dump);
+    }
 }
 
 void InputDevice::addEventHubDevice(int32_t eventHubId, bool populateMappers) {
@@ -162,22 +164,10 @@
         mappers.push_back(std::make_unique<VibratorInputMapper>(*contextPtr));
     }
 
-    // Battery-like devices. Only one battery mapper for each EventHub device.
-    if (classes.test(InputDeviceClass::BATTERY)) {
-        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));
-        }
+    // Battery-like devices or light-containing devices.
+    // InputController will be created with associated EventHub device.
+    if (classes.test(InputDeviceClass::BATTERY) || classes.test(InputDeviceClass::LIGHT)) {
+        mController = std::make_unique<InputController>(*contextPtr);
     }
 
     // Keyboard-like devices.
@@ -409,6 +399,10 @@
                               mHasMic);
     for_each_mapper(
             [outDeviceInfo](InputMapper& mapper) { mapper.populateDeviceInfo(outDeviceInfo); });
+
+    if (mController) {
+        mController->populateDeviceInfo(outDeviceInfo);
+    }
 }
 
 int32_t InputDevice::getKeyCodeState(uint32_t sourceMask, int32_t keyCode) {
@@ -510,39 +504,30 @@
     for_each_mapper([when, readTime](InputMapper& mapper) { mapper.cancelTouch(when, readTime); });
 }
 
+// TODO b/180733860 support multiple battery in API and remove this.
+constexpr int32_t DEFAULT_BATTERY_ID = 1;
 std::optional<int32_t> InputDevice::getBatteryCapacity() {
-    return first_in_mappers<int32_t>(
-            [](InputMapper& mapper) { return mapper.getBatteryCapacity(); });
+    return mController ? mController->getBatteryCapacity(DEFAULT_BATTERY_ID) : std::nullopt;
 }
 
 std::optional<int32_t> InputDevice::getBatteryStatus() {
-    return first_in_mappers<int32_t>([](InputMapper& mapper) { return mapper.getBatteryStatus(); });
+    return mController ? mController->getBatteryStatus(DEFAULT_BATTERY_ID) : std::nullopt;
 }
 
 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;
+    return mController ? mController->setLightColor(lightId, color) : false;
 }
 
 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;
+    return mController ? mController->setLightPlayerId(lightId, playerId) : false;
 }
 
 std::optional<int32_t> InputDevice::getLightColor(int32_t lightId) {
-    return first_in_mappers<int32_t>(
-            [lightId](InputMapper& mapper) { return mapper.getLightColor(lightId); });
+    return mController ? mController->getLightColor(lightId) : std::nullopt;
 }
 
 std::optional<int32_t> InputDevice::getLightPlayerId(int32_t lightId) {
-    return first_in_mappers<int32_t>(
-            [lightId](InputMapper& mapper) { return mapper.getLightPlayerId(lightId); });
+    return mController ? mController->getLightPlayerId(lightId) : std::nullopt;
 }
 
 int32_t InputDevice::getMetaState() {
diff --git a/services/inputflinger/reader/controller/InputController.cpp b/services/inputflinger/reader/controller/InputController.cpp
new file mode 100644
index 0000000..45266fb
--- /dev/null
+++ b/services/inputflinger/reader/controller/InputController.cpp
@@ -0,0 +1,528 @@
+/*
+ * 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 "InputController.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);
+}
+
+/**
+ * Input controller 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.
+ */
+InputController::InputController(InputDeviceContext& deviceContext)
+      : mDeviceContext(deviceContext) {
+    configureBattries();
+    configureLights();
+}
+
+InputController::~InputController() {}
+
+std::optional<std::int32_t> InputController::Light::getRawLightBrightness(int32_t rawLightId) {
+    std::optional<RawLightInfo> rawInfoOpt = context.getRawLightInfo(rawLightId);
+    if (!rawInfoOpt.has_value()) {
+        return std::nullopt;
+    }
+    std::optional<int32_t> brightnessOpt = context.getLightBrightness(rawLightId);
+    if (!brightnessOpt.has_value()) {
+        return std::nullopt;
+    }
+    int brightness = brightnessOpt.value();
+
+    // If the light node doesn't have max brightness, use the default max brightness.
+    int rawMaxBrightness = rawInfoOpt->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 InputController::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 InputController::SingleLight::setLightColor(int32_t color) {
+    int32_t brightness = getAlpha(color);
+    setRawLightBrightness(rawId, brightness);
+
+    return true;
+}
+
+bool InputController::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 InputController::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> InputController::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> InputController::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> InputController::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 InputController::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> InputController::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 InputController::SingleLight::dump(std::string& dump) {
+    dump += StringPrintf(INDENT4 "Color: 0x%x\n", getLightColor().value_or(0));
+}
+
+void InputController::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 InputController::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 InputController::MultiColorLight::dump(std::string& dump) {
+    dump += StringPrintf(INDENT4 "Color: 0x%x\n", getLightColor().value_or(0));
+}
+
+void InputController::populateDeviceInfo(InputDeviceInfo* deviceInfo) {
+    // TODO: b/180733860 Remove this after enabling multi-battery
+    if (!mBatteries.empty()) {
+        deviceInfo->setHasBattery(true);
+    }
+
+    for (const auto& [batteryId, battery] : mBatteries) {
+        InputDeviceBatteryInfo batteryInfo(battery->name, battery->id);
+        deviceInfo->addBatteryInfo(batteryInfo);
+    }
+
+    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 */);
+        deviceInfo->addLightInfo(lightInfo);
+    }
+}
+
+void InputController::dump(std::string& dump) {
+    dump += INDENT2 "Input Controller:\n";
+    if (!mLights.empty()) {
+        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));
+    }
+
+    if (!mBatteries.empty()) {
+        dump += INDENT3 "Batteries:\n";
+        for (const auto& [batteryId, battery] : mBatteries) {
+            dump += StringPrintf(INDENT4 "Id: %d", batteryId);
+            dump += StringPrintf(INDENT4 "Name: %s", battery->name.c_str());
+            dump += getBatteryCapacity(batteryId).has_value()
+                    ? StringPrintf(INDENT3 "Capacity: %d\n", getBatteryCapacity(batteryId).value())
+                    : StringPrintf(INDENT3 "Capacity: Unknown");
+
+            std::string status;
+            switch (getBatteryStatus(batteryId).value_or(BATTERY_STATUS_UNKNOWN)) {
+                case BATTERY_STATUS_CHARGING:
+                    status = "Charging";
+                    break;
+                case BATTERY_STATUS_DISCHARGING:
+                    status = "Discharging";
+                    break;
+                case BATTERY_STATUS_NOT_CHARGING:
+                    status = "Not charging";
+                    break;
+                case BATTERY_STATUS_FULL:
+                    status = "Full";
+                    break;
+                default:
+                    status = "Unknown";
+            }
+            dump += StringPrintf(INDENT3 "Status: %s\n", status.c_str());
+        }
+    }
+}
+
+void InputController::configureBattries() {
+    // Check raw batteries
+    const std::vector<int32_t> rawBatteryIds = getDeviceContext().getRawBatteryIds();
+
+    for (const auto& rawId : rawBatteryIds) {
+        std::optional<RawBatteryInfo> rawInfo = getDeviceContext().getRawBatteryInfo(rawId);
+        if (!rawInfo.has_value()) {
+            continue;
+        }
+        std::unique_ptr<Battery> battery =
+                std::make_unique<Battery>(getDeviceContext(), rawInfo->name, rawInfo->id);
+        mBatteries.insert_or_assign(rawId, std::move(battery));
+    }
+}
+
+void InputController::configureLights() {
+    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;
+
+    // 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));
+    }
+}
+
+std::optional<int32_t> InputController::getBatteryCapacity(int batteryId) {
+    return getDeviceContext().getBatteryCapacity(batteryId);
+}
+
+std::optional<int32_t> InputController::getBatteryStatus(int batteryId) {
+    return getDeviceContext().getBatteryStatus(batteryId);
+}
+
+bool InputController::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> InputController::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 InputController::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> InputController::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/controller/InputController.h
similarity index 81%
rename from services/inputflinger/reader/mapper/LightInputMapper.h
rename to services/inputflinger/reader/controller/InputController.h
index 43141b8..d4222e2 100644
--- a/services/inputflinger/reader/mapper/LightInputMapper.h
+++ b/services/inputflinger/reader/controller/InputController.h
@@ -14,35 +14,49 @@
  * limitations under the License.
  */
 
-#ifndef _UI_INPUTREADER_LIGHT_INPUT_MAPPER_H
-#define _UI_INPUTREADER_LIGHT_INPUT_MAPPER_H
+#ifndef _UI_INPUTREADER_LIGHT_CONTROLLER_H
+#define _UI_INPUTREADER_LIGHT_CONTROLLER_H
 
-#include "InputMapper.h"
+#include "InputControllerInterface.h"
 
 namespace android {
 
-class LightInputMapper : public InputMapper {
+class InputController : public InputControllerInterface {
     // 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;
+    explicit InputController(InputDeviceContext& deviceContext);
+    ~InputController() 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;
+    std::optional<int32_t> getBatteryCapacity(int32_t batteryId) override;
+    std::optional<int32_t> getBatteryStatus(int32_t batteryId) override;
 
 private:
+    inline int32_t getDeviceId() { return mDeviceContext.getId(); }
+    inline InputDeviceContext& getDeviceContext() { return mDeviceContext; }
+
+    InputDeviceContext& mDeviceContext;
+    void configureLights();
+    void configureBattries();
+
+    struct Battery {
+        explicit Battery(InputDeviceContext& context, const std::string& name, int32_t id)
+              : context(context), name(name), id(id) {}
+        virtual ~Battery() {}
+        InputDeviceContext& context;
+        std::string name;
+        int32_t id;
+    };
+
     struct Light {
         explicit Light(InputDeviceContext& context, const std::string& name, int32_t id,
                        InputDeviceLightType type)
@@ -128,8 +142,11 @@
 
     // Light map from light ID to Light
     std::unordered_map<int32_t, std::unique_ptr<Light>> mLights;
+
+    // Battery map from battery ID to battery
+    std::unordered_map<int32_t, std::unique_ptr<Battery>> mBatteries;
 };
 
 } // namespace android
 
-#endif // _UI_INPUTREADER_LIGHT_INPUT_MAPPER_H
+#endif // _UI_INPUTREADER_LIGHT_CONTROLLER_H
diff --git a/services/inputflinger/reader/controller/InputControllerInterface.h b/services/inputflinger/reader/controller/InputControllerInterface.h
new file mode 100644
index 0000000..504eded
--- /dev/null
+++ b/services/inputflinger/reader/controller/InputControllerInterface.h
@@ -0,0 +1,52 @@
+/*
+ * 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_INPUT_CONTROLLER_H
+#define _UI_INPUTREADER_INPUT_CONTROLLER_H
+
+#include "EventHub.h"
+#include "InputDevice.h"
+#include "InputListener.h"
+#include "InputReaderContext.h"
+
+namespace android {
+
+/* An input controller manages the miscellaneous devices associated with the input device,
+ * like the sysfs based battery and light class devices.
+ *
+ */
+class InputControllerInterface {
+public:
+    InputControllerInterface() {}
+    virtual ~InputControllerInterface() {}
+
+    // Interface methods for Battery
+    virtual std::optional<int32_t> getBatteryCapacity(int32_t batteryId) = 0;
+    virtual std::optional<int32_t> getBatteryStatus(int32_t batteryId) = 0;
+
+    // Interface methods for Light
+    virtual bool setLightColor(int32_t lightId, int32_t color) = 0;
+    virtual bool setLightPlayerId(int32_t lightId, int32_t playerId) = 0;
+    virtual std::optional<int32_t> getLightColor(int32_t lightId) = 0;
+    virtual std::optional<int32_t> getLightPlayerId(int32_t lightId) = 0;
+
+    virtual void populateDeviceInfo(InputDeviceInfo* deviceInfo) = 0;
+    virtual void dump(std::string& dump) = 0;
+};
+
+} // namespace android
+
+#endif // _UI_INPUTREADER_INPUT_CONTROLLER_H
diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h
index 2afaa85..c970c8b 100644
--- a/services/inputflinger/reader/include/EventHub.h
+++ b/services/inputflinger/reader/include/EventHub.h
@@ -173,6 +173,15 @@
     MAX_BRIGHTNESS = 0x00000080,
 };
 
+enum class InputBatteryClass : uint32_t {
+    /* The input device battery has capacity node. */
+    CAPACITY = 0x00000001,
+    /* The input device battery has capacity_level node. */
+    CAPACITY_LEVEL = 0x00000002,
+    /* The input device battery has status node. */
+    STATUS = 0x00000004,
+};
+
 /* Describes a raw light. */
 struct RawLightInfo {
     int32_t id;
@@ -183,6 +192,14 @@
     std::filesystem::path path;
 };
 
+/* Describes a raw battery. */
+struct RawBatteryInfo {
+    int32_t id;
+    std::string name;
+    Flags<InputBatteryClass> flags;
+    std::filesystem::path path;
+};
+
 /*
  * Gets the class that owns an axis, in cases where multiple classes might claim
  * the same axis for different purposes.
@@ -263,6 +280,12 @@
     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 batteries are sysfs power_supply nodes we found from the EventHub device sysfs node,
+    // containing the raw info of the sysfs node structure.
+    virtual const std::vector<int32_t> getRawBatteryIds(int32_t deviceId) = 0;
+    virtual std::optional<RawBatteryInfo> getRawBatteryInfo(int32_t deviceId,
+                                                            int32_t BatteryId) = 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;
@@ -307,10 +330,11 @@
     virtual std::vector<int32_t> getVibratorIds(int32_t deviceId) = 0;
 
     /* Query battery level. */
-    virtual std::optional<int32_t> getBatteryCapacity(int32_t deviceId) const = 0;
+    virtual std::optional<int32_t> getBatteryCapacity(int32_t deviceId,
+                                                      int32_t batteryId) const = 0;
 
     /* Query battery status. */
-    virtual std::optional<int32_t> getBatteryStatus(int32_t deviceId) const = 0;
+    virtual std::optional<int32_t> getBatteryStatus(int32_t deviceId, int32_t batteryId) const = 0;
 
     /* Requests the EventHub to reopen all input devices on the next call to getEvents(). */
     virtual void requestReopenDevices() = 0;
@@ -435,6 +459,10 @@
     base::Result<std::pair<InputDeviceSensorType, int32_t>> mapSensor(
             int32_t deviceId, int32_t absCode) override final;
 
+    const std::vector<int32_t> getRawBatteryIds(int32_t deviceId) override final;
+    std::optional<RawBatteryInfo> getRawBatteryInfo(int32_t deviceId,
+                                                    int32_t BatteryId) 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;
@@ -485,9 +513,11 @@
 
     void monitor() override final;
 
-    std::optional<int32_t> getBatteryCapacity(int32_t deviceId) const override final;
+    std::optional<int32_t> getBatteryCapacity(int32_t deviceId,
+                                              int32_t batteryId) const override final;
 
-    std::optional<int32_t> getBatteryStatus(int32_t deviceId) const override final;
+    std::optional<int32_t> getBatteryStatus(int32_t deviceId,
+                                            int32_t batteryId) const override final;
 
     bool isDeviceEnabled(int32_t deviceId) override final;
 
@@ -498,6 +528,22 @@
     ~EventHub() override;
 
 private:
+    struct MiscDevice {
+        // The device descriptor from evdev device the misc device associated with.
+        std::string descriptor;
+        // The sysfs root path of the misc device.
+        std::filesystem::path sysfsRootPath;
+
+        int32_t nextBatteryId;
+        int32_t nextLightId;
+        std::unordered_map<int32_t, RawBatteryInfo> batteryInfos;
+        std::unordered_map<int32_t, RawLightInfo> lightInfos;
+        explicit MiscDevice(std::filesystem::path sysfsRootPath)
+              : sysfsRootPath(sysfsRootPath), nextBatteryId(0), nextLightId(0) {}
+        bool configureBatteryLocked();
+        bool configureLightsLocked();
+    };
+
     struct Device {
         int fd; // may be -1 if device is closed
         const int32_t id;
@@ -527,12 +573,7 @@
         bool ffEffectPlaying;
         int16_t ffEffectId; // initially -1
 
-        // 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;
+        std::shared_ptr<MiscDevice> miscDevice;
 
         int32_t controllerNumber;
 
@@ -563,8 +604,6 @@
         void setLedForControllerLocked();
         status_t mapLed(int32_t led, int32_t* outScanCode) const;
         void setLedStateLocked(int32_t led, bool on);
-        bool configureBatteryLocked();
-        bool configureLightsLocked();
     };
 
     /**
@@ -616,6 +655,12 @@
     void reportDeviceAddedForStatisticsLocked(const InputDeviceIdentifier& identifier,
                                               Flags<InputDeviceClass> classes) REQUIRES(mLock);
 
+    const std::unordered_map<int32_t, RawBatteryInfo>& getBatteryInfoLocked(int32_t deviceId) const
+            REQUIRES(mLock);
+
+    const std::unordered_map<int32_t, RawLightInfo>& getLightInfoLocked(int32_t deviceId) const
+            REQUIRES(mLock);
+
     // Protect all internal state.
     mutable std::mutex mLock;
 
@@ -645,6 +690,11 @@
     std::vector<std::unique_ptr<Device>> mOpeningDevices;
     std::vector<std::unique_ptr<Device>> mClosingDevices;
 
+    // Map from std::string descriptor, to a shared_ptr of a miscellaneous device associated with
+    // the input device. The descriptor is the same from the EventHub device which it associates
+    // with.
+    std::unordered_map<std::string, std::shared_ptr<MiscDevice>> mMiscDevices;
+
     bool mNeedToSendFinishedDeviceScan;
     bool mNeedToReopenDevices;
     bool mNeedToScanDevices;
diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h
index 863cd41..5d56f5a 100644
--- a/services/inputflinger/reader/include/InputDevice.h
+++ b/services/inputflinger/reader/include/InputDevice.h
@@ -33,6 +33,8 @@
 
 namespace android {
 
+class InputController;
+class InputControllerInterface;
 class InputDeviceContext;
 class InputMapper;
 
@@ -131,6 +133,20 @@
         return *mapper;
     }
 
+    // construct and add a controller to the input device
+    template <class T>
+    T& addController(int32_t eventHubId) {
+        // ensure a device entry exists for this eventHubId
+        addEventHubDevice(eventHubId, false);
+
+        // create controller
+        auto& devicePair = mDevices[eventHubId];
+        auto& deviceContext = devicePair.first;
+
+        mController = std::make_unique<T>(*deviceContext);
+        return *(reinterpret_cast<T*>(mController.get()));
+    }
+
 private:
     InputReaderContext* mContext;
     int32_t mId;
@@ -143,7 +159,10 @@
     // map from eventHubId to device context and mappers
     using MapperVector = std::vector<std::unique_ptr<InputMapper>>;
     using DevicePair = std::pair<std::unique_ptr<InputDeviceContext>, MapperVector>;
+    // Map from EventHub ID to pair of device context and vector of mapper.
     std::unordered_map<int32_t, DevicePair> mDevices;
+    // Misc devices controller for lights, battery, etc.
+    std::unique_ptr<InputControllerInterface> mController;
 
     uint32_t mSources;
     bool mIsExternal;
@@ -319,11 +338,21 @@
 
     inline std::vector<int32_t> getVibratorIds() { return mEventHub->getVibratorIds(mId); }
 
-    inline std::optional<int32_t> getBatteryCapacity() {
-        return mEventHub->getBatteryCapacity(mId);
+    inline const std::vector<int32_t> getRawBatteryIds() {
+        return mEventHub->getRawBatteryIds(mId);
     }
 
-    inline std::optional<int32_t> getBatteryStatus() { return mEventHub->getBatteryStatus(mId); }
+    inline std::optional<RawBatteryInfo> getRawBatteryInfo(int32_t batteryId) {
+        return mEventHub->getRawBatteryInfo(mId, batteryId);
+    }
+
+    inline std::optional<int32_t> getBatteryCapacity(int32_t batteryId) {
+        return mEventHub->getBatteryCapacity(mId, batteryId);
+    }
+
+    inline std::optional<int32_t> getBatteryStatus(int32_t batteryId) {
+        return mEventHub->getBatteryStatus(mId, batteryId);
+    }
 
     inline bool hasAbsoluteAxis(int32_t code) const {
         RawAbsoluteAxisInfo info;
diff --git a/services/inputflinger/reader/mapper/BatteryInputMapper.cpp b/services/inputflinger/reader/mapper/BatteryInputMapper.cpp
deleted file mode 100644
index e4fb3a6..0000000
--- a/services/inputflinger/reader/mapper/BatteryInputMapper.cpp
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * 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 "../Macros.h"
-
-#include "BatteryInputMapper.h"
-
-namespace android {
-
-BatteryInputMapper::BatteryInputMapper(InputDeviceContext& deviceContext)
-      : InputMapper(deviceContext) {}
-
-uint32_t BatteryInputMapper::getSources() {
-    return AINPUT_SOURCE_UNKNOWN;
-}
-
-void BatteryInputMapper::populateDeviceInfo(InputDeviceInfo* info) {
-    InputMapper::populateDeviceInfo(info);
-
-    info->setHasBattery(true);
-}
-
-void BatteryInputMapper::process(const RawEvent* rawEvent) {}
-
-std::optional<int32_t> BatteryInputMapper::getBatteryCapacity() {
-    return getDeviceContext().getBatteryCapacity();
-}
-
-std::optional<int32_t> BatteryInputMapper::getBatteryStatus() {
-    return getDeviceContext().getBatteryStatus();
-}
-
-void BatteryInputMapper::dump(std::string& dump) {
-    dump += INDENT2 "Battery Input Mapper:\n";
-    dump += getBatteryCapacity().has_value()
-            ? StringPrintf(INDENT3 "Capacity: %d\n", getBatteryCapacity().value())
-            : StringPrintf(INDENT3 "Capacity: Unknown");
-
-    std::string status;
-    switch (getBatteryStatus().value_or(BATTERY_STATUS_UNKNOWN)) {
-        case BATTERY_STATUS_CHARGING:
-            status = "Charging";
-            break;
-        case BATTERY_STATUS_DISCHARGING:
-            status = "Discharging";
-            break;
-        case BATTERY_STATUS_NOT_CHARGING:
-            status = "Not charging";
-            break;
-        case BATTERY_STATUS_FULL:
-            status = "Full";
-            break;
-        default:
-            status = "Unknown";
-    }
-    dump += StringPrintf(INDENT3 "Status: %s\n", status.c_str());
-}
-
-} // namespace android
diff --git a/services/inputflinger/reader/mapper/BatteryInputMapper.h b/services/inputflinger/reader/mapper/BatteryInputMapper.h
deleted file mode 100644
index 4fe373e..0000000
--- a/services/inputflinger/reader/mapper/BatteryInputMapper.h
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * 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_BATTERY_INPUT_MAPPER_H
-#define _UI_INPUTREADER_BATTERY_INPUT_MAPPER_H
-
-#include "InputMapper.h"
-
-namespace android {
-
-class BatteryInputMapper : public InputMapper {
-public:
-    explicit BatteryInputMapper(InputDeviceContext& deviceContext);
-    virtual ~BatteryInputMapper(){};
-
-    uint32_t getSources() override;
-    void populateDeviceInfo(InputDeviceInfo* deviceInfo) override;
-    void process(const RawEvent* rawEvent) override;
-
-    std::optional<int32_t> getBatteryCapacity() override;
-    std::optional<int32_t> getBatteryStatus() override;
-
-    void dump(std::string& dump) override;
-};
-
-} // namespace android
-
-#endif // _UI_INPUTREADER_BATTERY_INPUT_MAPPER_H
diff --git a/services/inputflinger/reader/mapper/LightInputMapper.cpp b/services/inputflinger/reader/mapper/LightInputMapper.cpp
deleted file mode 100644
index be1f722..0000000
--- a/services/inputflinger/reader/mapper/LightInputMapper.cpp
+++ /dev/null
@@ -1,478 +0,0 @@
-/*
- * 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/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index d69bb6a..3d99a6b 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -14,15 +14,14 @@
  * limitations under the License.
  */
 
-#include <BatteryInputMapper.h>
 #include <CursorInputMapper.h>
+#include <InputController.h>
 #include <InputDevice.h>
 #include <InputMapper.h>
 #include <InputReader.h>
 #include <InputReaderBase.h>
 #include <InputReaderFactory.h>
 #include <KeyboardInputMapper.h>
-#include <LightInputMapper.h>
 #include <MultiTouchInputMapper.h>
 #include <SensorInputMapper.h>
 #include <SingleTouchInputMapper.h>
@@ -71,6 +70,7 @@
 static constexpr int32_t FIRST_TRACKING_ID = 0;
 static constexpr int32_t SECOND_TRACKING_ID = 1;
 static constexpr int32_t THIRD_TRACKING_ID = 2;
+static constexpr int32_t DEFAULT_BATTERY = 1;
 static constexpr int32_t BATTERY_STATUS = 4;
 static constexpr int32_t BATTERY_CAPACITY = 66;
 static constexpr int32_t LIGHT_BRIGHTNESS = 0x55000000;
@@ -895,9 +895,19 @@
 
     std::vector<int32_t> getVibratorIds(int32_t deviceId) override { return mVibrators; };
 
-    std::optional<int32_t> getBatteryCapacity(int32_t) const override { return BATTERY_CAPACITY; }
+    std::optional<int32_t> getBatteryCapacity(int32_t, int32_t) const override {
+        return BATTERY_CAPACITY;
+    }
 
-    std::optional<int32_t> getBatteryStatus(int32_t) const override { return BATTERY_STATUS; }
+    std::optional<int32_t> getBatteryStatus(int32_t, int32_t) const override {
+        return BATTERY_STATUS;
+    }
+
+    const std::vector<int32_t> getRawBatteryIds(int32_t deviceId) { return {}; }
+
+    std::optional<RawBatteryInfo> getRawBatteryInfo(int32_t deviceId, int32_t batteryId) {
+        return std::nullopt;
+    }
 
     const std::vector<int32_t> getRawLightIds(int32_t deviceId) override {
         std::vector<int32_t> ids;
@@ -2005,56 +2015,25 @@
     ASSERT_EQ(mReader->getVibratorIds(deviceId).size(), 2U);
 }
 
-class FakeBatteryInputMapper : public FakeInputMapper {
-public:
-    FakeBatteryInputMapper(InputDeviceContext& deviceContext, uint32_t sources)
-          : FakeInputMapper(deviceContext, sources) {}
+// --- FakeInputController ---
 
-    std::optional<int32_t> getBatteryCapacity() override {
-        return getDeviceContext().getBatteryCapacity();
+class FakeInputController : public InputControllerInterface {
+public:
+    FakeInputController(InputDeviceContext& deviceContext) : mDeviceContext(deviceContext) {}
+
+    ~FakeInputController() override {}
+
+    void populateDeviceInfo(InputDeviceInfo* deviceInfo) override {}
+
+    void dump(std::string& dump) override {}
+
+    std::optional<int32_t> getBatteryCapacity(int32_t batteryId) override {
+        return getDeviceContext().getBatteryCapacity(batteryId);
     }
 
-    std::optional<int32_t> getBatteryStatus() override {
-        return getDeviceContext().getBatteryStatus();
+    std::optional<int32_t> getBatteryStatus(int32_t batteryId) override {
+        return getDeviceContext().getBatteryStatus(batteryId);
     }
-};
-
-TEST_F(InputReaderTest, BatteryGetCapacity) {
-    constexpr int32_t deviceId = END_RESERVED_ID + 1000;
-    Flags<InputDeviceClass> deviceClass = InputDeviceClass::KEYBOARD | InputDeviceClass::BATTERY;
-    constexpr int32_t eventHubId = 1;
-    const char* DEVICE_LOCATION = "BLUETOOTH";
-    std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake", DEVICE_LOCATION);
-    FakeBatteryInputMapper& mapper =
-            device->addMapper<FakeBatteryInputMapper>(eventHubId, AINPUT_SOURCE_KEYBOARD);
-    mReader->pushNextDevice(device);
-
-    ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr));
-    ASSERT_NO_FATAL_FAILURE(mapper.assertConfigureWasCalled());
-
-    ASSERT_EQ(mReader->getBatteryCapacity(deviceId), BATTERY_CAPACITY);
-}
-
-TEST_F(InputReaderTest, BatteryGetStatus) {
-    constexpr int32_t deviceId = END_RESERVED_ID + 1000;
-    Flags<InputDeviceClass> deviceClass = InputDeviceClass::KEYBOARD | InputDeviceClass::BATTERY;
-    constexpr int32_t eventHubId = 1;
-    const char* DEVICE_LOCATION = "BLUETOOTH";
-    std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake", DEVICE_LOCATION);
-    FakeBatteryInputMapper& mapper =
-            device->addMapper<FakeBatteryInputMapper>(eventHubId, AINPUT_SOURCE_KEYBOARD);
-    mReader->pushNextDevice(device);
-
-    ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr));
-    ASSERT_NO_FATAL_FAILURE(mapper.assertConfigureWasCalled());
-
-    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);
@@ -2068,16 +2047,54 @@
         }
         return result.value() << 24;
     }
+
+    bool setLightPlayerId(int32_t lightId, int32_t playerId) override { return true; }
+
+    std::optional<int32_t> getLightPlayerId(int32_t lightId) override { return std::nullopt; }
+
+private:
+    InputDeviceContext& mDeviceContext;
+    inline int32_t getDeviceId() { return mDeviceContext.getId(); }
+    inline InputDeviceContext& getDeviceContext() { return mDeviceContext; }
 };
 
+TEST_F(InputReaderTest, BatteryGetCapacity) {
+    constexpr int32_t deviceId = END_RESERVED_ID + 1000;
+    Flags<InputDeviceClass> deviceClass = InputDeviceClass::KEYBOARD | InputDeviceClass::BATTERY;
+    constexpr int32_t eventHubId = 1;
+    const char* DEVICE_LOCATION = "BLUETOOTH";
+    std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake", DEVICE_LOCATION);
+    FakeInputController& controller = device->addController<FakeInputController>(eventHubId);
+    mReader->pushNextDevice(device);
+
+    ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr));
+
+    ASSERT_EQ(controller.getBatteryCapacity(DEFAULT_BATTERY), BATTERY_CAPACITY);
+    ASSERT_EQ(mReader->getBatteryCapacity(deviceId), BATTERY_CAPACITY);
+}
+
+TEST_F(InputReaderTest, BatteryGetStatus) {
+    constexpr int32_t deviceId = END_RESERVED_ID + 1000;
+    Flags<InputDeviceClass> deviceClass = InputDeviceClass::KEYBOARD | InputDeviceClass::BATTERY;
+    constexpr int32_t eventHubId = 1;
+    const char* DEVICE_LOCATION = "BLUETOOTH";
+    std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake", DEVICE_LOCATION);
+    FakeInputController& controller = device->addController<FakeInputController>(eventHubId);
+    mReader->pushNextDevice(device);
+
+    ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr));
+
+    ASSERT_EQ(controller.getBatteryStatus(DEFAULT_BATTERY), BATTERY_STATUS);
+    ASSERT_EQ(mReader->getBatteryStatus(deviceId), BATTERY_STATUS);
+}
+
 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);
+    FakeInputController& controller = device->addController<FakeInputController>(eventHubId);
     mReader->pushNextDevice(device);
     RawLightInfo info = {.id = 1,
                          .name = "Mono",
@@ -2088,8 +2105,9 @@
     mFakeEventHub->fakeLightBrightness(1 /* rawId */, 0x55);
 
     ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr));
-    ASSERT_NO_FATAL_FAILURE(mapper.assertConfigureWasCalled());
 
+    ASSERT_TRUE(controller.setLightColor(1 /* lightId */, LIGHT_BRIGHTNESS));
+    ASSERT_EQ(controller.getLightColor(1 /* lightId */), LIGHT_BRIGHTNESS);
     ASSERT_TRUE(mReader->setLightColor(deviceId, 1 /* lightId */, LIGHT_BRIGHTNESS));
     ASSERT_EQ(mReader->getLightColor(deviceId, 1 /* lightId */), LIGHT_BRIGHTNESS);
 }
@@ -2989,154 +3007,6 @@
     mapper.flushSensor(InputDeviceSensorType::GYROSCOPE);
 }
 
-// --- BatteryInputMapperTest ---
-class BatteryInputMapperTest : public InputMapperTest {
-protected:
-    void SetUp() override { InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::BATTERY); }
-};
-
-TEST_F(BatteryInputMapperTest, GetSources) {
-    BatteryInputMapper& mapper = addMapperAndConfigure<BatteryInputMapper>();
-
-    ASSERT_EQ(AINPUT_SOURCE_UNKNOWN, mapper.getSources());
-}
-
-TEST_F(BatteryInputMapperTest, GetBatteryCapacity) {
-    BatteryInputMapper& mapper = addMapperAndConfigure<BatteryInputMapper>();
-
-    ASSERT_TRUE(mapper.getBatteryCapacity());
-    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().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 ---
 
 class KeyboardInputMapperTest : public InputMapperTest {
@@ -8728,4 +8598,216 @@
     ASSERT_EQ(AINPUT_SOURCE_TOUCHPAD, mapper.getSources());
 }
 
+// --- InputControllerTest ---
+
+class InputControllerTest : public testing::Test {
+protected:
+    static const char* DEVICE_NAME;
+    static const char* DEVICE_LOCATION;
+    static const int32_t DEVICE_ID;
+    static const int32_t DEVICE_GENERATION;
+    static const int32_t DEVICE_CONTROLLER_NUMBER;
+    static const Flags<InputDeviceClass> DEVICE_CLASSES;
+    static const int32_t EVENTHUB_ID;
+
+    std::shared_ptr<FakeEventHub> mFakeEventHub;
+    sp<FakeInputReaderPolicy> mFakePolicy;
+    sp<TestInputListener> mFakeListener;
+    std::unique_ptr<InstrumentedInputReader> mReader;
+    std::shared_ptr<InputDevice> mDevice;
+
+    virtual void SetUp(Flags<InputDeviceClass> classes) {
+        mFakeEventHub = std::make_unique<FakeEventHub>();
+        mFakePolicy = new FakeInputReaderPolicy();
+        mFakeListener = new TestInputListener();
+        mReader = std::make_unique<InstrumentedInputReader>(mFakeEventHub, mFakePolicy,
+                                                            mFakeListener);
+        mDevice = newDevice(DEVICE_ID, DEVICE_NAME, DEVICE_LOCATION, EVENTHUB_ID, classes);
+    }
+
+    void SetUp() override { SetUp(DEVICE_CLASSES); }
+
+    void TearDown() override {
+        mFakeListener.clear();
+        mFakePolicy.clear();
+    }
+
+    void configureDevice(uint32_t changes) {
+        if (!changes || (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) {
+            mReader->requestRefreshConfiguration(changes);
+            mReader->loopOnce();
+        }
+        mDevice->configure(ARBITRARY_TIME, mFakePolicy->getReaderConfiguration(), changes);
+    }
+
+    std::shared_ptr<InputDevice> newDevice(int32_t deviceId, const std::string& name,
+                                           const std::string& location, int32_t eventHubId,
+                                           Flags<InputDeviceClass> classes) {
+        InputDeviceIdentifier identifier;
+        identifier.name = name;
+        identifier.location = location;
+        std::shared_ptr<InputDevice> device =
+                std::make_shared<InputDevice>(mReader->getContext(), deviceId, DEVICE_GENERATION,
+                                              identifier);
+        mReader->pushNextDevice(device);
+        mFakeEventHub->addDevice(eventHubId, name, classes);
+        mReader->loopOnce();
+        return device;
+    }
+
+    template <class T, typename... Args>
+    T& addControllerAndConfigure(Args... args) {
+        T& controller = mDevice->addController<T>(EVENTHUB_ID, args...);
+
+        return controller;
+    }
+};
+
+const char* InputControllerTest::DEVICE_NAME = "device";
+const char* InputControllerTest::DEVICE_LOCATION = "BLUETOOTH";
+const int32_t InputControllerTest::DEVICE_ID = END_RESERVED_ID + 1000;
+const int32_t InputControllerTest::DEVICE_GENERATION = 2;
+const int32_t InputControllerTest::DEVICE_CONTROLLER_NUMBER = 0;
+const Flags<InputDeviceClass> InputControllerTest::DEVICE_CLASSES =
+        Flags<InputDeviceClass>(0); // not needed for current tests
+const int32_t InputControllerTest::EVENTHUB_ID = 1;
+
+// --- BatteryControllerTest ---
+class BatteryControllerTest : public InputControllerTest {
+protected:
+    void SetUp() override {
+        InputControllerTest::SetUp(DEVICE_CLASSES | InputDeviceClass::BATTERY);
+    }
+};
+
+TEST_F(BatteryControllerTest, GetBatteryCapacity) {
+    InputController& controller = addControllerAndConfigure<InputController>();
+
+    ASSERT_TRUE(controller.getBatteryCapacity(DEFAULT_BATTERY));
+    ASSERT_EQ(controller.getBatteryCapacity(DEFAULT_BATTERY).value_or(-1), BATTERY_CAPACITY);
+}
+
+TEST_F(BatteryControllerTest, GetBatteryStatus) {
+    InputController& controller = addControllerAndConfigure<InputController>();
+
+    ASSERT_TRUE(controller.getBatteryStatus(DEFAULT_BATTERY));
+    ASSERT_EQ(controller.getBatteryStatus(DEFAULT_BATTERY).value_or(-1), BATTERY_STATUS);
+}
+
+// --- LightControllerTest ---
+class LightControllerTest : public InputControllerTest {
+protected:
+    void SetUp() override { InputControllerTest::SetUp(DEVICE_CLASSES | InputDeviceClass::LIGHT); }
+};
+
+TEST_F(LightControllerTest, SingleLight) {
+    RawLightInfo infoSingle = {.id = 1,
+                               .name = "Mono",
+                               .maxBrightness = 255,
+                               .flags = InputLightClass::BRIGHTNESS,
+                               .path = ""};
+    mFakeEventHub->addRawLightInfo(infoSingle.id, std::move(infoSingle));
+
+    InputController& controller = addControllerAndConfigure<InputController>();
+    InputDeviceInfo info;
+    controller.populateDeviceInfo(&info);
+    const auto& ids = info.getLightIds();
+    ASSERT_EQ(1UL, ids.size());
+    ASSERT_EQ(InputDeviceLightType::SINGLE, info.getLightInfo(ids[0])->type);
+
+    ASSERT_TRUE(controller.setLightColor(ids[0], LIGHT_BRIGHTNESS));
+    ASSERT_EQ(controller.getLightColor(ids[0]).value_or(-1), LIGHT_BRIGHTNESS);
+}
+
+TEST_F(LightControllerTest, 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));
+
+    InputController& controller = addControllerAndConfigure<InputController>();
+    InputDeviceInfo info;
+    controller.populateDeviceInfo(&info);
+    const auto& ids = info.getLightIds();
+    ASSERT_EQ(1UL, ids.size());
+    ASSERT_EQ(InputDeviceLightType::RGB, info.getLightInfo(ids[0])->type);
+
+    ASSERT_TRUE(controller.setLightColor(ids[0], LIGHT_COLOR));
+    ASSERT_EQ(controller.getLightColor(ids[0]).value_or(-1), LIGHT_COLOR);
+}
+
+TEST_F(LightControllerTest, 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));
+
+    InputController& controller = addControllerAndConfigure<InputController>();
+    InputDeviceInfo info;
+    controller.populateDeviceInfo(&info);
+    const auto& ids = info.getLightIds();
+    ASSERT_EQ(1UL, ids.size());
+    ASSERT_EQ(InputDeviceLightType::MULTI_COLOR, info.getLightInfo(ids[0])->type);
+
+    ASSERT_TRUE(controller.setLightColor(ids[0], LIGHT_COLOR));
+    ASSERT_EQ(controller.getLightColor(ids[0]).value_or(-1), LIGHT_COLOR);
+}
+
+TEST_F(LightControllerTest, 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));
+
+    InputController& controller = addControllerAndConfigure<InputController>();
+    InputDeviceInfo info;
+    controller.populateDeviceInfo(&info);
+    const auto& ids = info.getLightIds();
+    ASSERT_EQ(1UL, ids.size());
+    ASSERT_EQ(InputDeviceLightType::PLAYER_ID, info.getLightInfo(ids[0])->type);
+
+    ASSERT_FALSE(controller.setLightColor(ids[0], LIGHT_COLOR));
+    ASSERT_TRUE(controller.setLightPlayerId(ids[0], LIGHT_PLAYER_ID));
+    ASSERT_EQ(controller.getLightPlayerId(ids[0]).value_or(-1), LIGHT_PLAYER_ID);
+}
+
 } // namespace android