diff --git a/lights/Android.bp b/lights/Android.bp new file mode 100644 index 0000000..109e9bd --- /dev/null +++ b/lights/Android.bp @@ -0,0 +1,24 @@ +// +// Copyright (C) 2023 The LineageOS Project +// +// SPDX-License-Identifier: Apache-2.0 +// + +cc_binary { + name: "android.hardware.lights-service.bengal", + relative_install_path: "hw", + init_rc: ["android.hardware.lights.bengal.rc"], + vintf_fragments: ["android.hardware.lights.bengal.xml"], + vendor: true, + shared_libs: [ + "libbase", + "liblog", + "libhardware", + "libbinder_ndk", + "android.hardware.light-V1-ndk", + ], + srcs: [ + "Lights.cpp", + "main.cpp", + ], +} diff --git a/lights/Lights.cpp b/lights/Lights.cpp new file mode 100644 index 0000000..def8324 --- /dev/null +++ b/lights/Lights.cpp @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2019 The Android Open Source Project + * Copyright (C) 2023 The LineageOS Project + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "Lights.h" +#include +#include +#include + +/* clang-format off */ +#define PPCAT_NX(A, B) A/B +#define PPCAT(A, B) PPCAT_NX(A, B) +#define STRINGIFY_INNER(x) #x +#define STRINGIFY(x) STRINGIFY_INNER(x) + +#define CHARGING_ATTR(x) STRINGIFY(PPCAT(/sys/class/leds/charging, x)) +/* clang-format on */ + +namespace aidl { +namespace android { +namespace hardware { +namespace light { + +namespace { + +using ::android::base::WriteStringToFile; + +// Write value to path and close file. +template +inline bool WriteToFile(const std::string& path, T content) { + return WriteStringToFile(std::to_string(content), path); +} + +uint32_t RgbaToBrightness(uint32_t color) { + // Extract brightness from AARRGGBB. + uint32_t alpha = (color >> 24) & 0xFF; + + // Retrieve each of the RGB colors + uint32_t red = (color >> 16) & 0xFF; + uint32_t green = (color >> 8) & 0xFF; + uint32_t blue = color & 0xFF; + + // Scale RGB colors if a brightness has been applied by the user + if (alpha != 0xFF && alpha != 0) { + red = red * alpha / 0xFF; + green = green * alpha / 0xFF; + blue = blue * alpha / 0xFF; + } + + return (77 * red + 150 * green + 29 * blue) >> 8; +} + +inline bool IsLit(uint32_t color) { + return color & 0x00ffffff; +} + +void ApplyNotificationState(const HwLightState& state) { + bool ok = false; + uint32_t brightness = RgbaToBrightness(state.color); + + switch (state.flashMode) { + case FlashMode::HARDWARE: + case FlashMode::TIMED: + ok = WriteStringToFile("timer", CHARGING_ATTR(trigger)); + if (ok) { + using namespace std::chrono_literals; + auto retries = 20; + while (retries--) { + std::this_thread::sleep_for(2ms); + + ok = WriteToFile(CHARGING_ATTR(delay_off), state.flashOffMs); + if (!ok) continue; + + ok = WriteToFile(CHARGING_ATTR(delay_on), state.flashOnMs); + if (ok) break; + } + LOG(DEBUG) << __func__ + << ": number of tries to write delay: " << (20 - retries); + } + if (ok) break; + // fallback to constant on if timed blinking is not supported + LOG(INFO) << __func__ + << ": fallthrough FlashMode::TIMED to FlashMode::NONE."; + FALLTHROUGH_INTENDED; + case FlashMode::NONE: + default: + ok = WriteToFile(CHARGING_ATTR(brightness), brightness); + break; + } + + LOG(DEBUG) << __func__ + << ": mode=" << toString(state.flashMode) << ", colorRGB=" << std::hex + << state.color << std::dec << ", onMS=" << state.flashOnMs + << ", offMS=" << state.flashOffMs << ", ok=" << ok + << ", brightnessMode=" << toString(state.brightnessMode); +} + +} // anonymous namespace + +ndk::ScopedAStatus Lights::setLightState(int id, const HwLightState& state) { + static_assert(kAvailableLights.size() == std::tuple_size_v); + + if (id == static_cast(LightType::BACKLIGHT)) { + // Stub backlight handling + return ndk::ScopedAStatus::ok(); + } + + // Update saved state first + bool found = false; + for (size_t i = 0; i < notif_states_.size(); ++i) { + if (kAvailableLights[i].id == id) { + notif_states_[i] = state; + found = true; + break; + } + } + if (!found) { + LOG(ERROR) << " Light not supported"; + return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); + } + + // Lit up in order or fallback to battery light if others are dim + for (size_t i = 0; i < notif_states_.size(); ++i) { + auto&& cur_state = notif_states_[i]; + auto&& cur_light = kAvailableLights[i]; + if (IsLit(cur_state.color) || cur_light.type == LightType::BATTERY) { + ApplyNotificationState(cur_state); + break; + } + } + + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Lights::getLights(std::vector* lights) { + lights->insert(lights->end(), kAvailableLights.begin(), kAvailableLights.end()); + // We don't handle backlight but still need to report as supported. + lights->push_back({static_cast(LightType::BACKLIGHT), 2, LightType::BACKLIGHT}); + return ndk::ScopedAStatus::ok(); +} + +} // namespace light +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/lights/Lights.h b/lights/Lights.h new file mode 100644 index 0000000..7342e88 --- /dev/null +++ b/lights/Lights.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * Copyright (C) 2023 The LineageOS Project + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#define LOG_TAG "android.hardware.lights-service.bengal" + +#include +#include + +namespace aidl { +namespace android { +namespace hardware { +namespace light { + +// Keep sorted in the order of priority. +constexpr std::array kAvailableLights = { + // id, ordinal, type + HwLight{static_cast(LightType::NOTIFICATIONS), 0, LightType::NOTIFICATIONS}, + HwLight{static_cast(LightType::BATTERY), 1, LightType::BATTERY}, +}; + +class Lights : public BnLights { + public: + ndk::ScopedAStatus setLightState(int id, const HwLightState& state) override; + ndk::ScopedAStatus getLights(std::vector* types) override; + + private: + std::array notif_states_; +}; + +} // namespace light +} // namespace hardware +} // namespace android +} // namespace aidl diff --git a/lights/android.hardware.lights.bengal.rc b/lights/android.hardware.lights.bengal.rc new file mode 100644 index 0000000..4b92a8a --- /dev/null +++ b/lights/android.hardware.lights.bengal.rc @@ -0,0 +1,16 @@ +service vendor.light-default /vendor/bin/hw/android.hardware.lights-service.bengal + class hal + user system + group system + shutdown critical + +on boot + # Change ownership and permission for charging led + chown system system /sys/class/leds/charging/breath + chown system system /sys/class/leds/charging/brightness + chown system system /sys/class/leds/charging/delay_off + chown system system /sys/class/leds/charging/delay_on + chmod 0640 /sys/class/leds/charging/breath + chmod 0640 /sys/class/leds/charging/brightness + chmod 0640 /sys/class/leds/charging/delay_off + chmod 0640 /sys/class/leds/charging/delay_on diff --git a/lights/android.hardware.lights.bengal.xml b/lights/android.hardware.lights.bengal.xml new file mode 100644 index 0000000..db604d6 --- /dev/null +++ b/lights/android.hardware.lights.bengal.xml @@ -0,0 +1,6 @@ + + + android.hardware.light + ILights/default + + diff --git a/lights/main.cpp b/lights/main.cpp new file mode 100644 index 0000000..895c58b --- /dev/null +++ b/lights/main.cpp @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "Lights.h" + +#include +#include +#include + +using ::aidl::android::hardware::light::Lights; + +int main() { + ABinderProcess_setThreadPoolMaxThreadCount(0); + std::shared_ptr lights = ndk::SharedRefBase::make(); + if (!lights) { + return EXIT_FAILURE; + } + + const std::string instance = std::string() + Lights::descriptor + "/default"; + binder_status_t status = AServiceManager_addService(lights->asBinder().get(), instance.c_str()); + CHECK(status == STATUS_OK); + + ABinderProcess_joinThreadPool(); + return EXIT_FAILURE; // should not be reached +} diff --git a/rootdir/etc/ueventd.qcom.rc b/rootdir/etc/ueventd.qcom.rc index e19176c..1d4f3ad 100755 --- a/rootdir/etc/ueventd.qcom.rc +++ b/rootdir/etc/ueventd.qcom.rc @@ -390,6 +390,11 @@ firmware_directories /vendor/firmware_mnt/image/ /sys/class/leds/blue breath 0640 system system /sys/class/leds/blue trigger 0640 system system +/sys/class/leds/charging delay_on 0640 system system +/sys/class/leds/charging delay_off 0640 system system +/sys/class/leds/charging breath 0640 system system +/sys/class/leds/charging trigger 0640 system system + # LED class vibrator /sys/class/leds/vibrator state 0660 system system /sys/class/leds/vibrator duration 0660 system system diff --git a/sepolicy/vendor/file_contexts b/sepolicy/vendor/file_contexts index a26a5f9..f7306e5 100644 --- a/sepolicy/vendor/file_contexts +++ b/sepolicy/vendor/file_contexts @@ -47,6 +47,9 @@ /dev/focaltech_fp u:object_r:fps_device:s0 /dev/fpsensor u:object_r:fps_device:s0 +# Lights +/(vendor|system/vendor)/bin/hw/android\.hardware\.lights-service\.bengal u:object_r:hal_light_default_exec:s0 + # Motobox /(vendor|system/vendor)/bin/motobox u:object_r:vendor_motobox_exec:s0 diff --git a/sepolicy/vendor/genfs_contexts b/sepolicy/vendor/genfs_contexts index 8204054..1932156 100644 --- a/sepolicy/vendor/genfs_contexts +++ b/sepolicy/vendor/genfs_contexts @@ -32,6 +32,9 @@ genfscon sysfs /devices/platform/soc/soc:mmi_chrg_manager/iio:device # Input Devices genfscon sysfs /devices/virtual/input u:object_r:vendor_sysfs_input:s0 +# Lights +genfscon sysfs /devices/platform/soc/soc:indicator_led/leds/charging u:object_r:sysfs_leds:s0 + # Motorola genfscon proc /bootinfo u:object_r:proc_moto_boot:s0 genfscon proc /config u:object_r:vendor_proc_hw:s0 diff --git a/sepolicy/vendor/hal_light_default.te b/sepolicy/vendor/hal_light_default.te new file mode 100644 index 0000000..50ede18 --- /dev/null +++ b/sepolicy/vendor/hal_light_default.te @@ -0,0 +1,5 @@ +allow hal_light_default { + sysfs_leds +}:file rw_file_perms; + +r_dir_file(hal_light_default, sysfs_leds)