├── .vscode └── c_cpp_properties.json ├── Android.bp ├── Lights.cpp ├── Lights.h ├── README.md ├── android.hardware.lights.rc ├── android.hardware.lights.xml ├── lights.mk ├── main.cpp └── sepolicy └── file_contexts /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Linux", 5 | "includePath": [ 6 | "${workspaceFolder}/", 7 | "${workspaceFolder}/../../../../external/libcxx/include", 8 | "${workspaceFolder}/../../../../system/core/base/include", 9 | "${workspaceFolder}/../../../../frameworks/native/libs/binder/ndk/include_platform/", 10 | "${workspaceFolder}/../../../../out/soong/.intermediates/hardware/interfaces/light/aidl/android.hardware.light-ndk_platform-source/gen/include", 11 | "${workspaceFolder}/../../../../out/soong/ndk/sysroot/usr/include" 12 | ], 13 | "defines": [], 14 | "cStandard": "c11", 15 | "cppStandard": "c++20", 16 | "intelliSenseMode": "linux-gcc-arm64", 17 | "compilerPath": "/usr/bin/aarch64-linux-gnu-g++" 18 | } 19 | ], 20 | "version": 4 21 | } 22 | -------------------------------------------------------------------------------- /Android.bp: -------------------------------------------------------------------------------- 1 | cc_binary { 2 | name: "android.hardware.lights-service", 3 | relative_install_path: "hw", 4 | init_rc: ["android.hardware.lights.rc"], 5 | vintf_fragments: ["android.hardware.lights.xml"], 6 | vendor: true, 7 | shared_libs: [ 8 | "libbase", 9 | "libbinder_ndk", 10 | "android.hardware.light-V2-ndk", 11 | ], 12 | srcs: [ 13 | "Lights.cpp", 14 | "main.cpp", 15 | ], 16 | } 17 | -------------------------------------------------------------------------------- /Lights.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2019 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "Lights.h" 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | static const std::string BACKLIGHT_DIR = "/sys/class/backlight"; 24 | static const std::string LEDS_DIR = "/sys/class/leds"; 25 | 26 | Light::Light(HwLight hwLight, std::string path) 27 | : hwLight(hwLight) 28 | , path(path) 29 | { 30 | } 31 | 32 | Backlight::Backlight(HwLight hwLight, std::string path, uint32_t maxBrightness) 33 | : Light(hwLight, path) 34 | , maxBrightness(maxBrightness) 35 | { 36 | } 37 | 38 | Backlight *Backlight::createBacklight(HwLight hwLight, std::string path) 39 | { 40 | uint32_t maxBrightness; 41 | std::ifstream stream(path + "/max_brightness"); 42 | if (auto stream = std::ifstream(path + "/max_brightness")) { 43 | stream >> maxBrightness; 44 | } else { 45 | LOG(ERROR) << "Failed to read `max_brightness` for " << path; 46 | return nullptr; 47 | } 48 | 49 | LOG(INFO) << "Creating backlight " << path << " with max brightness " << maxBrightness; 50 | 51 | return new Backlight(hwLight, path, maxBrightness); 52 | } 53 | 54 | static int32_t rgbToBrightness(int32_t color) 55 | { 56 | auto r = (color >> 16) & 0xff; 57 | auto g = (color >> 8) & 0xff; 58 | auto b = color & 0xff; 59 | return (77 * r + 150 * g + 29 * b) >> 8; 60 | } 61 | 62 | ndk::ScopedAStatus Backlight::setLightState(const HwLightState &state) const 63 | { 64 | auto brightness = rgbToBrightness(state.color); 65 | // Adding half of the max (255/2=127) provides proper rounding while staying in integer mode: 66 | brightness = (brightness * maxBrightness + 127) / 255; 67 | if (state.brightnessMode == BrightnessMode::LOW_PERSISTENCE) 68 | LOG(ERROR) << "TODO: Implement Low Persistence brightness mode"; 69 | LOG(DEBUG) << "Changing backlight to level " << brightness << "/" << maxBrightness; 70 | if (auto stream = std::ofstream(path + "/brightness")) { 71 | stream << brightness; 72 | return ndk::ScopedAStatus::ok(); 73 | } else { 74 | LOG(ERROR) << "Failed to write `brightness` to " << path; 75 | return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); 76 | } 77 | } 78 | 79 | Led::Led(HwLight hwLight, std::string path, uint32_t maxBrightness) 80 | : Light(hwLight, path) 81 | , maxBrightness(maxBrightness) 82 | { 83 | } 84 | 85 | Led *Led::createLed(HwLight hwLight, std::string path) 86 | { 87 | uint32_t maxBrightness; 88 | std::ifstream stream(path + "/max_brightness"); 89 | if (auto stream = std::ifstream(path + "/max_brightness")) { 90 | stream >> maxBrightness; 91 | } else { 92 | LOG(ERROR) << "Failed to read `max_brightness` for " << path; 93 | return nullptr; 94 | } 95 | std::string color0, color1, color2; 96 | if (auto stream = std::ifstream(path + "/multi_index")) { 97 | stream >> color0 >> color1 >> color2; 98 | if (color0 != "red" || color1 != "green" || color2 != "blue") { 99 | // We can easily implement support for this by storing the indices of the three channels and 100 | // shifting in setLightState. 101 | LOG(ERROR) << "Color indices `" << color0 << " " << color1 << " " << color2 << "` not supported, expected `red green blue`"; 102 | return nullptr; 103 | } 104 | } else { 105 | LOG(ERROR) << "Failed to read `multi_index` for " << path; 106 | return nullptr; 107 | } 108 | 109 | LOG(INFO) << "Creating led " << path << " with max brightness " << maxBrightness; 110 | 111 | return new Led(hwLight, path, maxBrightness); 112 | } 113 | 114 | ndk::ScopedAStatus Led::setLightState(const HwLightState &state) const 115 | { 116 | 117 | if (auto stream = std::ofstream(path + "/trigger")) { 118 | switch (state.flashMode) { 119 | case FlashMode::NONE: 120 | stream << "none"; 121 | break; 122 | case FlashMode::HARDWARE: 123 | // This is probably only called for things like BATTERY/BLUETOOOTH/WIFI, 124 | // and expects us to set the appropriate `trigger` so that no HAL involvement 125 | // is necessary afterwards? 126 | LOG(ERROR) << "Hardware flash mode not yet supported - what trigger to set?"; 127 | return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); 128 | case FlashMode::TIMED: 129 | stream << "timer"; 130 | break; 131 | } 132 | } else { 133 | LOG(ERROR) << "Failed to write `trigger` to " << path; 134 | return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); 135 | } 136 | 137 | if (auto stream = std::ofstream(path + "/multi_intensity")) { 138 | auto r = (state.color >> 16) & 0xff; 139 | auto g = (state.color >> 8) & 0xff; 140 | auto b = state.color & 0xff; 141 | stream << r << " " << g << " " << b; 142 | } else { 143 | LOG(ERROR) << "Failed to write `multi_intensity` to " << path; 144 | return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); 145 | } 146 | 147 | LOG(DEBUG) << "Setting global led to max brightness " << maxBrightness; 148 | if (auto stream = std::ofstream(path + "/brightness")) { 149 | stream << maxBrightness; 150 | } else { 151 | LOG(ERROR) << "Failed to write `brightness` to " << path; 152 | return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); 153 | } 154 | 155 | switch (state.flashMode) { 156 | case FlashMode::NONE: 157 | case FlashMode::HARDWARE: 158 | break; 159 | case FlashMode::TIMED: 160 | LOG(DEBUG) << "Setting global led to turn on " << state.flashOnMs << "ms and off " << state.flashOffMs << "ms"; 161 | if (auto stream = std::ofstream(path + "/delay_on")) { 162 | stream << state.flashOnMs; 163 | } else { 164 | LOG(ERROR) << "Failed to write `delay_on` to " << path; 165 | return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); 166 | } 167 | if (auto stream = std::ofstream(path + "/delay_off")) { 168 | stream << state.flashOffMs; 169 | } else { 170 | LOG(ERROR) << "Failed to write `delay_on` to " << path; 171 | return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); 172 | } 173 | break; 174 | } 175 | 176 | return ndk::ScopedAStatus::ok(); 177 | } 178 | 179 | ndk::ScopedAStatus Lights::setLightState(int id, const HwLightState &state) 180 | { 181 | LOG(DEBUG) << "Lights setting state for id=" << id << " to color " << std::hex << state.color; 182 | 183 | if (id >= lights.size()) 184 | return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); 185 | 186 | const auto &light = lights[id]; 187 | return light->setLightState(state); 188 | } 189 | 190 | ndk::ScopedAStatus Lights::getLights(std::vector *hwLights) 191 | { 192 | for (const auto &light : lights) 193 | hwLights->emplace_back(light->hwLight); 194 | return ndk::ScopedAStatus::ok(); 195 | } 196 | 197 | Lights::Lights() 198 | { 199 | int id = 0; 200 | int ordinal = 0; 201 | // Cannot use std::filesystem from libc++fs which is not available for vendor modules. 202 | // Maybe with Android S? 203 | // for (const auto &backlight : std::filesystem::directory_iterator("/sys/class/backlight")) 204 | // if (backlight.is_directory() || backlight.is_symlink()) 205 | // lights.emplace_back(..); 206 | 207 | if (auto backlights = opendir(BACKLIGHT_DIR.c_str())) { 208 | while (dirent *ent = readdir(backlights)) { 209 | if ((ent->d_type == DT_DIR && ent->d_name[0] != '.') || ent->d_type == DT_LNK) { 210 | std::string backlightPath = BACKLIGHT_DIR + "/" + ent->d_name; 211 | if (auto backlight = Backlight::createBacklight( 212 | HwLight { .id = id++, .ordinal = ordinal++, .type = LightType::BACKLIGHT }, 213 | backlightPath)) 214 | lights.emplace_back(backlight); 215 | } 216 | } 217 | closedir(backlights); 218 | } else { 219 | LOG(ERROR) << "Failed to open " << BACKLIGHT_DIR; 220 | } 221 | 222 | LOG(INFO) << "Found " << ordinal << " backlights"; 223 | 224 | // Ordinal must be unique per type 225 | ordinal = 0; 226 | 227 | if (auto leds = opendir(LEDS_DIR.c_str())) { 228 | while (dirent *ent = readdir(leds)) { 229 | if ((ent->d_type == DT_DIR && ent->d_name[0] != '.') || (ent->d_type == DT_LNK && strncmp("mmc", ent->d_name, 3))) { 230 | std::string ledPath = LEDS_DIR + "/" + ent->d_name; 231 | if (auto led = Led::createLed( 232 | // TODO: Also used for ATTENTION and BATTERY - register multiple instances? 233 | HwLight { .id = id++, .ordinal = ordinal++, .type = LightType::NOTIFICATIONS }, 234 | ledPath)) 235 | lights.emplace_back(led); 236 | } 237 | } 238 | closedir(leds); 239 | } else { 240 | LOG(ERROR) << "Failed to open " << LEDS_DIR; 241 | } 242 | 243 | LOG(INFO) << "Found " << ordinal << " leds"; 244 | } 245 | -------------------------------------------------------------------------------- /Lights.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #pragma once 18 | 19 | #include 20 | 21 | using namespace aidl::android::hardware::light; 22 | 23 | struct Light { 24 | const HwLight hwLight; 25 | const std::string path; 26 | 27 | Light(HwLight hwLight, std::string path); 28 | inline virtual ~Light() {}; 29 | 30 | virtual ndk::ScopedAStatus setLightState(const HwLightState &state) const = 0; 31 | }; 32 | 33 | struct Backlight : public Light { 34 | const uint32_t maxBrightness; 35 | 36 | Backlight(HwLight hwLight, std::string path, uint32_t maxBrightness); 37 | static Backlight *createBacklight(HwLight hwLight, std::string path); 38 | inline virtual ~Backlight() {}; 39 | 40 | ndk::ScopedAStatus setLightState(const HwLightState &state) const override; 41 | }; 42 | 43 | struct Led : public Light { 44 | const uint32_t maxBrightness; 45 | 46 | Led(HwLight hwLight, std::string path, uint32_t maxBrightness); 47 | static Led *createLed(HwLight hwLight, std::string path); 48 | inline virtual ~Led() {}; 49 | 50 | ndk::ScopedAStatus setLightState(const HwLightState &state) const override; 51 | }; 52 | 53 | class Lights : public BnLights { 54 | ndk::ScopedAStatus setLightState(int id, const HwLightState &state) override; 55 | ndk::ScopedAStatus getLights(std::vector *types) override; 56 | 57 | std::vector> lights; 58 | 59 | public: 60 | Lights(); 61 | }; 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Android Lights AIDL HAL 2 | = 3 | 4 | Provides controls for backlight and leds. Now through [AIDL](https://source.android.com/devices/architecture/aidl/aidl-hals). 5 | 6 | ## Usage 7 | 8 | Add the following to your Android device tree: 9 | 10 | ```Makefile 11 | $(call inherit-product, the/path/to/lights.mk) 12 | ``` 13 | 14 | And set the appropriate user/group in your `ueventd.rc` file for all relevant lights, ie.: 15 | 16 | ``` 17 | /sys/class/backlight/backlight max_brightness 0644 root system 18 | /sys/class/backlight/backlight brightness 0664 system system 19 | ``` 20 | 21 | ## Planned improvements 22 | 23 | - Extended led `trigger`s 24 | -------------------------------------------------------------------------------- /android.hardware.lights.rc: -------------------------------------------------------------------------------- 1 | service vendor.light-default /vendor/bin/hw/android.hardware.lights-service 2 | class hal 3 | user system 4 | group system 5 | shutdown critical 6 | -------------------------------------------------------------------------------- /android.hardware.lights.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | android.hardware.light 4 | 2 5 | ILights/default 6 | 7 | 8 | -------------------------------------------------------------------------------- /lights.mk: -------------------------------------------------------------------------------- 1 | PRODUCT_PACKAGES += \ 2 | android.hardware.lights-service 3 | 4 | # TODO: LOCAL_PATH doesn't work here 5 | BOARD_VENDOR_SEPOLICY_DIRS += device/mainline/common/lights/sepolicy 6 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2020 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "Lights.h" 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | int main() 24 | { 25 | ABinderProcess_setThreadPoolMaxThreadCount(0); 26 | std::shared_ptr lights = ndk::SharedRefBase::make(); 27 | 28 | const std::string instance = std::string() + Lights::descriptor + "/default"; 29 | binder_status_t status = AServiceManager_addService(lights->asBinder().get(), instance.c_str()); 30 | CHECK(status == STATUS_OK); 31 | 32 | ABinderProcess_joinThreadPool(); 33 | return EXIT_FAILURE; // should not reached 34 | } 35 | -------------------------------------------------------------------------------- /sepolicy/file_contexts: -------------------------------------------------------------------------------- 1 | /(system/vendor|vendor)/bin/hw/android\.hardware\.lights-service u:object_r:hal_light_default_exec:s0 2 | 3 | --------------------------------------------------------------------------------