ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Android学习28--LED点灯(Ver2)(TODO)

Android学习28--LED点灯(Ver2)(TODO)

可以这样做,但它不是“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/aidl

HAL 进程需要实现:

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

是否需要disabledoneshot要看 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 light

AIDL 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,例如10ordinal用于同类型多个灯排序。

如果只是业务 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 自带灯光策略发生冲突。

返回列表