可以这样做,但它不是“App 直接调用 HAL”。完整链路是:
系统 App ↓ android.hardware.lights.LightsManager Framework LightsService ↓ Binder android.hardware.light.ILights/default ↓ Lights AIDL HAL ↓ write() /sys/class/leds/user-led/brightness ↓ LED 驱动Android 14 推荐使用稳定版 AIDL Lights HAL。标准接口只有两个核心方法:
HwLight[] getLights(); void setLightState(int id, HwLightState state);官方接口定义见 ILights.aidl。
一、先把内核驱动标准化
当前节点是:
/sys/devices/platform/soc/soc:user-led/brightness能用,但量产最好把驱动注册成 Linux LED class,生成:
/sys/class/leds/user-led/brightness /sys/class/leds/user-led/max_brightness内核驱动大致如下:
#include <linux/leds.h> struct user_led { struct led_classdev cdev; int gpio; }; static void user_led_set( struct led_classdev *cdev, enum led_brightness value) { struct user_led *led = container_of(cdev, struct user_led, cdev); gpio_set_value(led->gpio, value != LED_OFF); } static int user_led_probe(struct platform_device *pdev) { struct user_led *led; led = devm_kzalloc( &pdev->dev, sizeof(*led), GFP_KERNEL); if (!led) return -ENOMEM; led->cdev.name = "user-led"; led->cdev.max_brightness = 1; led->cdev.brightness_set = user_led_set; return devm_led_classdev_register( &pdev->dev, &led->cdev); }不过现有节点也可以先用于 HAL 验证。
二、实现 Lights AIDL HAL
接口来自 AOSP:
hardware/interfaces/light/aidlHAL 进程需要实现:
aidl::android::hardware::light::BnLights示意实现:
#include <aidl/android/hardware/light/BnLights.h> #include <aidl/android/hardware/light/HwLight.h> #include <aidl/android/hardware/light/HwLightState.h> #include <aidl/android/hardware/light/LightType.h> #include <fstream> using aidl::android::hardware::light::BnLights; using aidl::android::hardware::light::HwLight; using aidl::android::hardware::light::HwLightState; using aidl::android::hardware::light::LightType; using ndk::ScopedAStatus; class Lights : public BnLights { public: ScopedAStatus getLights( std::vector<HwLight>* lights) override { HwLight light; light.id = 10; light.ordinal = 0; light.type = LightType::NOTIFICATIONS; lights->clear(); lights->push_back(light); return ScopedAStatus::ok(); } ScopedAStatus setLightState( int32_t id, const HwLightState& state) override { if (id != 10) { return ScopedAStatus::fromExceptionCode( EX_UNSUPPORTED_OPERATION); } // HwLightState.color 是 ARGB。 // RGB 任一分量非零就点亮。 bool on = (state.color & 0x00ffffff) != 0; std::ofstream node( "/sys/class/leds/user-led/brightness"); if (!node.is_open()) { return ScopedAStatus::fromServiceSpecificError( errno); } node << (on ? "1" : "0"); if (!node.good()) { return ScopedAStatus::fromServiceSpecificError( EIO); } return ScopedAStatus::ok(); } };如果暂时保留当前驱动,路径改成:
"/sys/devices/platform/soc/soc:user-led/brightness"对于二值 LED,可以只判断:
(state.color & 0x00ffffff) != 0如果后续换成 RGB LED,则可分别解析:
red = (color >> 16) & 0xff; green = (color >> 8) & 0xff; blue = color & 0xff;三、注册 Binder 服务
main.cpp:
#include "Lights.h" #include <android/binder_manager.h> #include <android/binder_process.h> int main() { ABinderProcess_setThreadPoolMaxThreadCount(0); auto service = ndk::SharedRefBase::make<Lights>(); const std::string instance = std::string(Lights::descriptor) + "/default"; binder_status_t status = AServiceManager_addService( service->asBinder().get(), instance.c_str()); if (status != STATUS_OK) return 1; ABinderProcess_joinThreadPool(); return 0; }四、Android.bp
cc_binary { name: "android.hardware.light-service.userled", vendor: true, relative_install_path: "hw", srcs: [ "main.cpp", "Lights.cpp", ], shared_libs: [ "libbase", "libbinder_ndk", "android.hardware.light-V2-ndk", ], init_rc: [ "android.hardware.light-service.userled.rc", ], vintf_fragments: [ "android.hardware.light-service.userled.xml", ], }需要使用与当前 Android 14固件匹配的接口版本。当前 AOSP Lights AIDL 包定义为 vendor 可用、VINTF-stable 接口。AIDL HAL 文档、Lights AIDL Android.bp。
五、init rc
service vendor.light-default \ /vendor/bin/hw/android.hardware.light-service.userled class hal user system group system是否需要disabled、oneshot要看 lazy HAL 设计。普通常驻 HAL 不加oneshot。
如果采用 lazy HAL,需要配合:
interface aidl android.hardware.light.ILights/default disabled oneshot首版建议先做普通常驻服务,便于调试。
六、VINTF manifest
<manifest version="1.0" type="device"> <hal format="aidl"> <name>android.hardware.light</name> <version>2</version> <interface> <name>ILights</name> <instance>default</instance> </interface> </hal> </manifest>有些版本也使用简化形式:
<hal format="aidl"> <name>android.hardware.light</name> <fqname>ILights/default</fqname> </hal>实际格式要与板子当前 VINTF schema 和 AIDL 版本一致。官方默认示例可参考 lights-default.xml。
七、SELinux
不能继续使用:
chmod 0666 SELinux Permissive应该给 LED 节点单独定义类型,例如:
type sysfs_user_led, fs_type, sysfs_type;如果使用标准 LED class,可以通过genfs_contexts标记:
genfscon sysfs /class/leds/user-led \ u:object_r:sysfs_user_led:s0实际 sysfs 内核路径经常不是表面软链接路径,需要用:
readlink -f /sys/class/leds/user-led确认后填写真实路径。
HAL 定义独立 domain:
type hal_light_userled, domain; type hal_light_userled_exec, exec_type, vendor_file_type, file_type; init_daemon_domain(hal_light_userled) allow hal_light_userled sysfs_user_led:dir { search }; allow hal_light_userled sysfs_user_led:file { open write getattr };服务二进制标签:
/vendor/bin/hw/android.hardware.light-service.userled \ u:object_r:hal_light_userled_exec:s0还要补充 Binder/AIDL HAL 相关宏,具体宏名取决于 BSP sepolicy:
hal_server_domain( hal_light_userled, hal_light )最终必须在:
getenforce = Enforcing条件下验证。
八、Framework 如何连接 HAL
Android Framework 的LightsService会查找:
android.hardware.light.ILights/default然后调用 HAL:
getLights() setLightState()通常不需要修改SystemServer。只要:
- HAL 名称正确;
- VINTF 声明正确;
- Binder 服务注册成功;
- SELinux 放行;
- Framework 与 vendor AIDL 版本兼容。
就能自动接上。
验证 HAL:
adb shell service list | grep -i light adb shell dumpsys lights adb shell logcat -b all | grep -i light adb shell ps -A | grep lightAIDL vendor service也可以检查:
adb shell service check \ android.hardware.light.ILights/default九、App 调用 LightsManager
App 端可使用:
import android.hardware.lights.Light import android.hardware.lights.LightState import android.hardware.lights.LightsManager获取灯:
val manager = getSystemService(LightsManager::class.java) val light = manager.lights.firstOrNull { it.type == Light.LIGHT_TYPE_NOTIFICATIONS }打开会话并控制:
val session = manager.openSession() val onState = LightState.Builder() .setColor(0xFFFFFFFF.toInt()) .build() session.requestLights( LightsRequest.Builder() .addLight(light!!, onState) .build() )关闭:
val offState = LightState.Builder() .setColor(0x00000000) .build() session.requestLights( LightsRequest.Builder() .addLight(light, offState) .build() )Activity 销毁时:
session.close()权限限制
这是关键点:普通第三方 App 通常不能任意控制设备灯。直接使用LightsManager.openSession()需要受保护的设备灯控制权限,例如:
<uses-permission android:name="android.permission.CONTROL_DEVICE_LIGHTS"/>该权限不是普通运行时权限,通常要求受信任系统 App/平台签名。仅把 APK 放进priv-app也不一定足够,还需要:
- platform/厂商签名;
- privapp 权限白名单;
- Framework 权限定义允许;
- 必要时专用 SELinux App domain。
所以最终 App 一般要做成厂商系统 App。
十、LED 类型怎么选
如果这是通知灯,可以返回:
LightType::NOTIFICATIONS如果是产品独有的功能灯,不建议冒充:
BACKLIGHT BATTERY CAMERA MICROPHONE因为 Framework 可能自动控制这些灯,与你的 App 冲突。
需要考虑两个方案:
- 让它作为
NOTIFICATIONS灯,由 Android 通知系统控制; - 使用 Lights HAL 中额外的同类实例,并由特权 App 根据
id/ordinal选择。
HwLight.id是 HAL 自定义的稳定 ID,例如10;ordinal用于同类型多个灯排序。
如果只是业务 App 独占的一颗 LED,自定义 AIDL HAL 往往比标准 Lights HAL 更清晰。Lights HAL 的语义主要是 Android 系统灯,Framework 可能根据通知、充电、相机或麦克风状态主动接管。
十一、当前“无源码”条件的现实限制
完整实施至少需要:
匹配 Android 14 的 AIDL NDK 头文件和库 Lights HAL 二进制 init rc VINTF manifest vendor sepolicy App 系统签名/权限 重新制作 vendor.img/product.img只有现成 image 时,可以先编译 HAL 二进制并通过 remount 做原型测试,但之前已经验证过:
- overlay 中新增
.rc可能不会被 init 早期加载; - 新增 VINTF 文件也有启动时序问题;
- SELinux policy 不能靠普通 push 完整增加;
- App 缺少 platform 签名权限。
因此:
原型验证: 现有启动脚本 → 启动自定义服务 → App 调用 正式量产: 编入 vendor.img/product.img + SELinux Enforcing对于当前板子,我建议先做“小型自定义 AIDL/native daemon”验证 App→Binder→sysfs,确认业务接口;拿到匹配 BSP 后再决定是否迁移到标准 Lights HAL。这样风险最低,也不会和 Android 自带灯光策略发生冲突。