Merge "Added the ability to read external batteries" into sc-dev
diff --git a/include/input/InputDevice.h b/include/input/InputDevice.h
index 23692e9..2bd7bd2 100644
--- a/include/input/InputDevice.h
+++ b/include/input/InputDevice.h
@@ -213,6 +213,9 @@
     inline void setVibrator(bool hasVibrator) { mHasVibrator = hasVibrator; }
     inline bool hasVibrator() const { return mHasVibrator; }
 
+    inline void setHasBattery(bool hasBattery) { mHasBattery = hasBattery; }
+    inline bool hasBattery() const { return mHasBattery; }
+
     inline void setButtonUnderPad(bool hasButton) { mHasButtonUnderPad = hasButton; }
     inline bool hasButtonUnderPad() const { return mHasButtonUnderPad; }
 
@@ -239,6 +242,7 @@
     int32_t mKeyboardType;
     std::shared_ptr<KeyCharacterMap> mKeyCharacterMap;
     bool mHasVibrator;
+    bool mHasBattery;
     bool mHasButtonUnderPad;
     bool mHasSensor;
 
diff --git a/libs/input/InputDevice.cpp b/libs/input/InputDevice.cpp
index 2ed441d..698cf6e 100644
--- a/libs/input/InputDevice.cpp
+++ b/libs/input/InputDevice.cpp
@@ -166,6 +166,7 @@
         mKeyboardType(other.mKeyboardType),
         mKeyCharacterMap(other.mKeyCharacterMap),
         mHasVibrator(other.mHasVibrator),
+        mHasBattery(other.mHasBattery),
         mHasButtonUnderPad(other.mHasButtonUnderPad),
         mHasSensor(other.mHasSensor),
         mMotionRanges(other.mMotionRanges),
@@ -187,6 +188,7 @@
     mSources = 0;
     mKeyboardType = AINPUT_KEYBOARD_TYPE_NONE;
     mHasVibrator = false;
+    mHasBattery = false;
     mHasButtonUnderPad = false;
     mHasSensor = false;
     mMotionRanges.clear();
diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h
index ea9b483..69aea84 100644
--- a/services/inputflinger/include/InputReaderBase.h
+++ b/services/inputflinger/include/InputReaderBase.h
@@ -108,6 +108,10 @@
     virtual bool isVibrating(int32_t deviceId) = 0;
 
     virtual std::vector<int32_t> getVibratorIds(int32_t deviceId) = 0;
+    /* Get battery level of a particular input device. */
+    virtual std::optional<int32_t> getBatteryCapacity(int32_t deviceId) = 0;
+    /* Get battery status of a particular input device. */
+    virtual std::optional<int32_t> getBatteryStatus(int32_t deviceId) = 0;
 
     /* Return true if the device can send input events to the specified display. */
     virtual bool canDispatchToDisplay(int32_t deviceId, int32_t displayId) = 0;
diff --git a/services/inputflinger/reader/Android.bp b/services/inputflinger/reader/Android.bp
index abda4ef..7f979f2 100644
--- a/services/inputflinger/reader/Android.bp
+++ b/services/inputflinger/reader/Android.bp
@@ -30,6 +30,7 @@
         "mapper/accumulator/CursorScrollAccumulator.cpp",
         "mapper/accumulator/SingleTouchMotionAccumulator.cpp",
         "mapper/accumulator/TouchButtonAccumulator.cpp",
+        "mapper/BatteryInputMapper.cpp",
         "mapper/CursorInputMapper.cpp",
         "mapper/ExternalStylusInputMapper.cpp",
         "mapper/InputMapper.cpp",
@@ -60,7 +61,11 @@
         "libui",
         "libutils",
     ],
+    static_libs: [
+        "libc++fs",
+    ],
     header_libs: [
+        "libbatteryservice_headers",
         "libinputreader_headers",
     ],
 }
diff --git a/services/inputflinger/reader/EventHub.cpp b/services/inputflinger/reader/EventHub.cpp
index b97ff90..8f8c051 100644
--- a/services/inputflinger/reader/EventHub.cpp
+++ b/services/inputflinger/reader/EventHub.cpp
@@ -29,11 +29,14 @@
 #include <sys/inotify.h>
 #include <sys/ioctl.h>
 #include <sys/limits.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
 #include <unistd.h>
 
 #define LOG_TAG "EventHub"
 
 // #define LOG_NDEBUG 0
+#include <android-base/file.h>
 #include <android-base/stringprintf.h>
 #include <cutils/properties.h>
 #include <input/KeyCharacterMap.h>
@@ -64,6 +67,23 @@
 static constexpr int32_t FF_STRONG_MAGNITUDE_CHANNEL_IDX = 0;
 static constexpr int32_t FF_WEAK_MAGNITUDE_CHANNEL_IDX = 1;
 
+// 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},
+         {"Charging", BATTERY_STATUS_CHARGING},
+         {"Discharging", BATTERY_STATUS_DISCHARGING},
+         {"Not charging", BATTERY_STATUS_NOT_CHARGING},
+         {"Full", BATTERY_STATUS_FULL}};
+
+// Mapping taken from
+// https://gitlab.freedesktop.org/upower/upower/-/blob/master/src/linux/up-device-supply.c#L484
+static const std::unordered_map<std::string, int32_t> BATTERY_LEVEL = {{"Critical", 5},
+                                                                       {"Low", 10},
+                                                                       {"Normal", 55},
+                                                                       {"High", 70},
+                                                                       {"Full", 100},
+                                                                       {"Unknown", 50}};
+
 static inline const char* toString(bool value) {
     return value ? "true" : "false";
 }
@@ -127,6 +147,73 @@
     return inputEventTime;
 }
 
+/**
+ * Returns the sysfs root path of the input device
+ *
+ */
+static std::filesystem::path getSysfsRootPath(const char* devicePath) {
+    std::error_code errorCode;
+
+    // Stat the device path to get the major and minor number of the character file
+    struct stat statbuf;
+    if (stat(devicePath, &statbuf) == -1) {
+        ALOGE("Could not stat device %s due to error: %s.", devicePath, std::strerror(errno));
+        return std::filesystem::path();
+    }
+
+    unsigned int major_num = major(statbuf.st_rdev);
+    unsigned int minor_num = minor(statbuf.st_rdev);
+
+    // Realpath "/sys/dev/char/{major}:{minor}" to get the sysfs path to the input event
+    auto sysfsPath = std::filesystem::path("/sys/dev/char/");
+    sysfsPath /= std::to_string(major_num) + ":" + std::to_string(minor_num);
+    sysfsPath = std::filesystem::canonical(sysfsPath, errorCode);
+
+    // Make sure nothing went wrong in call to canonical()
+    if (errorCode) {
+        ALOGW("Could not run filesystem::canonical() due to error %d : %s.", errorCode.value(),
+              errorCode.message().c_str());
+        return std::filesystem::path();
+    }
+
+    // Continue to go up a directory until we reach a directory named "input"
+    while (sysfsPath != "/" && sysfsPath.filename() != "input") {
+        sysfsPath = sysfsPath.parent_path();
+    }
+
+    // Then go up one more and you will be at the sysfs root of the device
+    sysfsPath = sysfsPath.parent_path();
+
+    // Make sure we didn't reach root path and that directory actually exists
+    if (sysfsPath == "/" || !std::filesystem::exists(sysfsPath, errorCode)) {
+        if (errorCode) {
+            ALOGW("Could not run filesystem::exists() due to error %d : %s.", errorCode.value(),
+                  errorCode.message().c_str());
+        }
+
+        // Not found
+        return std::filesystem::path();
+    }
+
+    return sysfsPath;
+}
+
+/**
+ * Returns the power supply node in sys fs
+ *
+ */
+static std::filesystem::path findPowerSupplyNode(const std::filesystem::path& sysfsRootPath) {
+    for (auto path = sysfsRootPath; path != "/"; path = path.parent_path()) {
+        std::error_code errorCode;
+        auto iter = std::filesystem::directory_iterator(path / "power_supply", errorCode);
+        if (!errorCode && iter != std::filesystem::directory_iterator()) {
+            return iter->path();
+        }
+    }
+    // Not found
+    return std::filesystem::path();
+}
+
 // --- Global Functions ---
 
 Flags<InputDeviceClass> getAbsAxisUsage(int32_t axis, Flags<InputDeviceClass> deviceClasses) {
@@ -976,6 +1063,56 @@
     return nullptr;
 }
 
+std::optional<int32_t> EventHub::getBatteryCapacity(int32_t deviceId) const {
+    std::scoped_lock _l(mLock);
+    Device* device = getDeviceLocked(deviceId);
+    std::string buffer;
+
+    if (!device || (device->sysfsBatteryPath.empty())) {
+        return std::nullopt;
+    }
+
+    // Some devices report battery capacity as an integer through the "capacity" file
+    if (base::ReadFileToString(device->sysfsBatteryPath / "capacity", &buffer)) {
+        return std::stoi(buffer);
+    }
+
+    // Other devices report capacity as an enum value POWER_SUPPLY_CAPACITY_LEVEL_XXX
+    // These values are taken from kernel source code include/linux/power_supply.h
+    if (base::ReadFileToString(device->sysfsBatteryPath / "capacity_level", &buffer)) {
+        const auto it = BATTERY_LEVEL.find(buffer);
+        if (it != BATTERY_LEVEL.end()) {
+            return it->second;
+        }
+    }
+    return std::nullopt;
+}
+
+std::optional<int32_t> EventHub::getBatteryStatus(int32_t deviceId) const {
+    std::scoped_lock _l(mLock);
+    Device* device = getDeviceLocked(deviceId);
+    std::string buffer;
+
+    if (!device || (device->sysfsBatteryPath.empty())) {
+        return std::nullopt;
+    }
+
+    if (!base::ReadFileToString(device->sysfsBatteryPath / "status", &buffer)) {
+        ALOGE("Failed to read sysfs battery info: %s", strerror(errno));
+        return std::nullopt;
+    }
+
+    // Remove trailing new line
+    buffer.erase(std::remove(buffer.begin(), buffer.end(), '\n'), buffer.end());
+    const auto it = BATTERY_STATUS.find(buffer);
+
+    if (it != BATTERY_STATUS.end()) {
+        return it->second;
+    }
+
+    return std::nullopt;
+}
+
 size_t EventHub::getEvents(int timeoutMillis, RawEvent* buffer, size_t bufferSize) {
     ALOG_ASSERT(bufferSize >= 1);
 
@@ -1584,6 +1721,18 @@
         return -1;
     }
 
+    // Grab the device's sysfs path
+    device->sysfsRootPath = getSysfsRootPath(devicePath.c_str());
+
+    if (!device->sysfsRootPath.empty()) {
+        device->sysfsBatteryPath = findPowerSupplyNode(device->sysfsRootPath);
+
+        // Check if a battery exists
+        if (!device->sysfsBatteryPath.empty()) {
+            device->classes |= InputDeviceClass::BATTERY;
+        }
+    }
+
     // Determine whether the device has a mic.
     if (device->deviceHasMicLocked()) {
         device->classes |= InputDeviceClass::MIC;
diff --git a/services/inputflinger/reader/InputDevice.cpp b/services/inputflinger/reader/InputDevice.cpp
index 3e6910d..8fc6f4a 100644
--- a/services/inputflinger/reader/InputDevice.cpp
+++ b/services/inputflinger/reader/InputDevice.cpp
@@ -21,6 +21,7 @@
 #include <input/Flags.h>
 #include <algorithm>
 
+#include "BatteryInputMapper.h"
 #include "CursorInputMapper.h"
 #include "ExternalStylusInputMapper.h"
 #include "InputReaderContext.h"
@@ -160,6 +161,11 @@
         mappers.push_back(std::make_unique<VibratorInputMapper>(*contextPtr));
     }
 
+    // Battery-like devices.
+    if (classes.test(InputDeviceClass::BATTERY)) {
+        mappers.push_back(std::make_unique<BatteryInputMapper>(*contextPtr));
+    }
+
     // Keyboard-like devices.
     uint32_t keyboardSource = 0;
     int32_t keyboardType = AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC;
@@ -490,6 +496,15 @@
     for_each_mapper([when](InputMapper& mapper) { mapper.cancelTouch(when); });
 }
 
+std::optional<int32_t> InputDevice::getBatteryCapacity() {
+    return first_in_mappers<int32_t>(
+            [](InputMapper& mapper) { return mapper.getBatteryCapacity(); });
+}
+
+std::optional<int32_t> InputDevice::getBatteryStatus() {
+    return first_in_mappers<int32_t>([](InputMapper& mapper) { return mapper.getBatteryStatus(); });
+}
+
 int32_t InputDevice::getMetaState() {
     int32_t result = 0;
     for_each_mapper([&result](InputMapper& mapper) { result |= mapper.getMetaState(); });
diff --git a/services/inputflinger/reader/InputReader.cpp b/services/inputflinger/reader/InputReader.cpp
index 7c448e4..de5d0e6 100644
--- a/services/inputflinger/reader/InputReader.cpp
+++ b/services/inputflinger/reader/InputReader.cpp
@@ -673,6 +673,26 @@
     }
 }
 
+std::optional<int32_t> InputReader::getBatteryCapacity(int32_t deviceId) {
+    std::scoped_lock _l(mLock);
+
+    InputDevice* device = findInputDeviceLocked(deviceId);
+    if (device) {
+        return device->getBatteryCapacity();
+    }
+    return std::nullopt;
+}
+
+std::optional<int32_t> InputReader::getBatteryStatus(int32_t deviceId) {
+    std::scoped_lock _l(mLock);
+
+    InputDevice* device = findInputDeviceLocked(deviceId);
+    if (device) {
+        return device->getBatteryStatus();
+    }
+    return std::nullopt;
+}
+
 bool InputReader::isInputDeviceEnabled(int32_t deviceId) {
     std::scoped_lock _l(mLock);
 
diff --git a/services/inputflinger/reader/include/EventHub.h b/services/inputflinger/reader/include/EventHub.h
index 2cea017..30967df 100644
--- a/services/inputflinger/reader/include/EventHub.h
+++ b/services/inputflinger/reader/include/EventHub.h
@@ -23,6 +23,9 @@
 #include <vector>
 
 #include <input/Flags.h>
+#include <filesystem>
+
+#include <batteryservice/BatteryService.h>
 #include <input/Input.h>
 #include <input/InputDevice.h>
 #include <input/KeyCharacterMap.h>
@@ -121,6 +124,9 @@
     /* The input device has a sensor like accelerometer, gyro, etc */
     SENSOR = 0x00002000,
 
+    /* The input device has a battery */
+    BATTERY = 0x00004000,
+
     /* The input device is virtual (not a real device, not part of UI configuration). */
     VIRTUAL = 0x40000000,
 
@@ -242,6 +248,12 @@
     virtual void cancelVibrate(int32_t deviceId) = 0;
     virtual std::vector<int32_t> getVibratorIds(int32_t deviceId) = 0;
 
+    /* Query battery level. */
+    virtual std::optional<int32_t> getBatteryCapacity(int32_t deviceId) const = 0;
+
+    /* Query battery status. */
+    virtual std::optional<int32_t> getBatteryStatus(int32_t deviceId) const = 0;
+
     /* Requests the EventHub to reopen all input devices on the next call to getEvents(). */
     virtual void requestReopenDevices() = 0;
 
@@ -404,6 +416,10 @@
 
     void monitor() override final;
 
+    std::optional<int32_t> getBatteryCapacity(int32_t deviceId) const override final;
+
+    std::optional<int32_t> getBatteryStatus(int32_t deviceId) const override final;
+
     bool isDeviceEnabled(int32_t deviceId) override final;
 
     status_t enableDevice(int32_t deviceId) override final;
@@ -442,6 +458,10 @@
         bool ffEffectPlaying;
         int16_t ffEffectId; // initially -1
 
+        // The paths are invalid when .empty() returns true
+        std::filesystem::path sysfsRootPath;
+        std::filesystem::path sysfsBatteryPath;
+
         int32_t controllerNumber;
 
         Device(int fd, int32_t id, const std::string& path,
diff --git a/services/inputflinger/reader/include/InputDevice.h b/services/inputflinger/reader/include/InputDevice.h
index 5af76b7..e4186c8 100644
--- a/services/inputflinger/reader/include/InputDevice.h
+++ b/services/inputflinger/reader/include/InputDevice.h
@@ -92,6 +92,9 @@
     void disableSensor(InputDeviceSensorType sensorType);
     void flushSensor(InputDeviceSensorType sensorType);
 
+    std::optional<int32_t> getBatteryCapacity();
+    std::optional<int32_t> getBatteryStatus();
+
     int32_t getMetaState();
     void updateMetaState(int32_t keyCode);
 
@@ -287,6 +290,12 @@
 
     inline std::vector<int32_t> getVibratorIds() { return mEventHub->getVibratorIds(mId); }
 
+    inline std::optional<int32_t> getBatteryCapacity() {
+        return mEventHub->getBatteryCapacity(mId);
+    }
+
+    inline std::optional<int32_t> getBatteryStatus() { return mEventHub->getBatteryStatus(mId); }
+
     inline bool hasAbsoluteAxis(int32_t code) const {
         RawAbsoluteAxisInfo info;
         mEventHub->getAbsoluteAxisInfo(mId, code, &info);
diff --git a/services/inputflinger/reader/include/InputReader.h b/services/inputflinger/reader/include/InputReader.h
index 7be932a..e2558bc 100644
--- a/services/inputflinger/reader/include/InputReader.h
+++ b/services/inputflinger/reader/include/InputReader.h
@@ -95,6 +95,10 @@
 
     void flushSensor(int32_t deviceId, InputDeviceSensorType sensorType) override;
 
+    std::optional<int32_t> getBatteryCapacity(int32_t deviceId) override;
+
+    std::optional<int32_t> getBatteryStatus(int32_t deviceId) override;
+
 protected:
     // These members are protected so they can be instrumented by test cases.
     virtual std::shared_ptr<InputDevice> createDeviceLocked(int32_t deviceId,
diff --git a/services/inputflinger/reader/mapper/BatteryInputMapper.cpp b/services/inputflinger/reader/mapper/BatteryInputMapper.cpp
new file mode 100644
index 0000000..afdc5ab
--- /dev/null
+++ b/services/inputflinger/reader/mapper/BatteryInputMapper.cpp
@@ -0,0 +1,72 @@
+/*
+ * 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 0;
+}
+
+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
new file mode 100644
index 0000000..4fe373e
--- /dev/null
+++ b/services/inputflinger/reader/mapper/BatteryInputMapper.h
@@ -0,0 +1,41 @@
+/*
+ * 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/InputMapper.h b/services/inputflinger/reader/mapper/InputMapper.h
index 6ca6ec9..44af998 100644
--- a/services/inputflinger/reader/mapper/InputMapper.h
+++ b/services/inputflinger/reader/mapper/InputMapper.h
@@ -72,6 +72,9 @@
     virtual void disableSensor(InputDeviceSensorType sensorType);
     virtual void flushSensor(InputDeviceSensorType sensorType);
 
+    virtual std::optional<int32_t> getBatteryCapacity() { return std::nullopt; }
+    virtual std::optional<int32_t> getBatteryStatus() { return std::nullopt; }
+
     virtual int32_t getMetaState();
     virtual void updateMetaState(int32_t keyCode);
 
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 36da8dd..0e88312 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <BatteryInputMapper.h>
 #include <CursorInputMapper.h>
 #include <InputDevice.h>
 #include <InputMapper.h>
@@ -67,6 +68,8 @@
 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 BATTERY_STATUS = 4;
+static constexpr int32_t BATTERY_CAPACITY = 66;
 
 // Error tolerance for floating point assertions.
 static const float EPSILON = 0.001f;
@@ -863,6 +866,10 @@
 
     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> getBatteryStatus(int32_t) const override { return BATTERY_STATUS; }
+
     virtual bool isExternal(int32_t) const {
         return false;
     }
@@ -1924,6 +1931,52 @@
     ASSERT_EQ(mReader->getVibratorIds(deviceId).size(), 2U);
 }
 
+class FakeBatteryInputMapper : public FakeInputMapper {
+public:
+    FakeBatteryInputMapper(InputDeviceContext& deviceContext, uint32_t sources)
+          : FakeInputMapper(deviceContext, sources) {}
+
+    std::optional<int32_t> getBatteryCapacity() override {
+        return getDeviceContext().getBatteryCapacity();
+    }
+
+    std::optional<int32_t> getBatteryStatus() override {
+        return getDeviceContext().getBatteryStatus();
+    }
+};
+
+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);
+}
+
 // --- InputReaderIntegrationTest ---
 
 // These tests create and interact with the InputReader only through its interface.
@@ -2799,6 +2852,32 @@
     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(), BATTERY_CAPACITY);
+}
+
+TEST_F(BatteryInputMapperTest, GetBatteryStatus) {
+    BatteryInputMapper& mapper = addMapperAndConfigure<BatteryInputMapper>();
+
+    ASSERT_TRUE(mapper.getBatteryStatus());
+    ASSERT_EQ(*mapper.getBatteryStatus(), BATTERY_STATUS);
+}
+
 // --- KeyboardInputMapperTest ---
 
 class KeyboardInputMapperTest : public InputMapperTest {