diff --git a/lights/Android.bp b/lights/Android.bp new file mode 100644 index 0000000..dc4a269 --- /dev/null +++ b/lights/Android.bp @@ -0,0 +1,23 @@ +package { + // See: http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // all of the 'license_kinds' from "//device/google/tangorpro:device_google_tangorpro_license" + // to get the below license kinds: + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["//device/google/tangorpro:device_google_tangorpro_license"], +} + +cc_binary { + name: "android.hardware.lights-service.tangorpro", + relative_install_path: "hw", + init_rc: ["android.hardware.lights-service.tangorpro.rc"], + vintf_fragments: ["android.hardware.lights-service.tangorpro.xml"], + vendor: true, + shared_libs: [ + "libbase", + "libbinder_ndk", + "android.hardware.light-V2-ndk", + ], + + srcs: ["Lights.cpp", "led_lut_calibrator.cpp"], +} diff --git a/lights/Lights.cpp b/lights/Lights.cpp new file mode 100644 index 0000000..218137b --- /dev/null +++ b/lights/Lights.cpp @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2022 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 +#include +#include +#include +#include + +#include "led_lut_calibrator.h" + +using ::aidl::android::hardware::light::BnLights; +using ::aidl::android::hardware::light::HwLight; +using ::aidl::android::hardware::light::HwLightState; +using ::aidl::android::hardware::light::ILights; +using ::aidl::android::hardware::light::LightType; +using ::ndk::ScopedAStatus; +using ::ndk::SharedRefBase; + +static pthread_mutex_t g_lock = PTHREAD_MUTEX_INITIALIZER; + +char const *const GREEN_LED_FILE = "/sys/class/leds/green/brightness"; + +enum { ARGB_ON_IN_DAY = 0x0000ff00, ARGB_ON_IN_NIGHT = 0x00008000, ARGB_OFF = 0x00000000 }; +enum { DAY = 4095, NIGHT = 0 }; + +static int sys_write_int(int fd, int value) { + char buffer[16]; + size_t bytes; + ssize_t amount; + + bytes = snprintf(buffer, sizeof(buffer), "%d\n", value); + if (bytes >= sizeof(buffer)) { + return -EINVAL; + } + amount = write(fd, buffer, bytes); + + return amount == -1 ? -errno : 0; +} + +class Lights : public BnLights { + private: + std::vector availableLights; + LedLutCalibrator calibrator; + + void addLight(LightType const type, int const ordinal) { + HwLight light{}; + light.id = availableLights.size(); + light.type = type; + light.ordinal = ordinal; + availableLights.emplace_back(light); + } + + void writeLed(const char *path, int color) { + int fd = open(path, O_WRONLY); + if (fd < 0) { + LOG(ERROR) << "Failed to open LED device " << path << strerror(errno); + return; + } + sys_write_int(fd, color); + close(fd); + } + + public: + Lights() : BnLights() { + pthread_mutex_init(&g_lock, NULL); + + addLight(LightType::BACKLIGHT, 0); + addLight(LightType::KEYBOARD, 0); + addLight(LightType::BUTTONS, 0); + addLight(LightType::BATTERY, 0); + addLight(LightType::NOTIFICATIONS, 0); + addLight(LightType::ATTENTION, 0); + addLight(LightType::BLUETOOTH, 0); + addLight(LightType::WIFI, 0); + addLight(LightType::MICROPHONE, 0); + addLight(LightType::CAMERA, 0); + } + + ScopedAStatus setLightState(int id, const HwLightState &state) override { + if (!(0 <= id && id < availableLights.size())) { + LOG(ERROR) << "Light id " << (int32_t)id << " does not exist."; + return ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION); + } + + HwLight const &light = availableLights[id]; + int cal_color = 0; + switch (light.type) { + case LightType::CAMERA: + if (state.color == ARGB_ON_IN_DAY) { + cal_color = calibrator.GetByColorIntensity("green", DAY); + } else if (state.color == ARGB_ON_IN_NIGHT) { + cal_color = calibrator.GetByColorIntensity("green", NIGHT); + } else if (state.color == ARGB_OFF) { + cal_color = 0; + } else { + goto setLightState_end; + } + if (cal_color < 0) { + goto setLightState_end; + } + pthread_mutex_lock(&g_lock); + writeLed(GREEN_LED_FILE, cal_color); + pthread_mutex_unlock(&g_lock); + break; + default: + break; + } + + setLightState_end: + return ScopedAStatus::ok(); + } + + ScopedAStatus getLights(std::vector *lights) override { + for (auto i = availableLights.begin(); i != availableLights.end(); i++) { + lights->push_back(*i); + } + + return ScopedAStatus::ok(); + } +}; + +int main() { + ABinderProcess_setThreadPoolMaxThreadCount(0); + + std::shared_ptr light = SharedRefBase::make(); + + const std::string instance = std::string() + ILights::descriptor + "/default"; + binder_status_t status = AServiceManager_addService(light->asBinder().get(), instance.c_str()); + + if (status != STATUS_OK) { + LOG(ERROR) << "Failed to register" << instance; + } + + ABinderProcess_joinThreadPool(); + + return -1; // should not reach +} diff --git a/lights/android.hardware.lights-service.tangorpro.rc b/lights/android.hardware.lights-service.tangorpro.rc new file mode 100644 index 0000000..3450bf4 --- /dev/null +++ b/lights/android.hardware.lights-service.tangorpro.rc @@ -0,0 +1,11 @@ +service vendor.lights-tangotron /vendor/bin/hw/android.hardware.lights-service.tangorpro + interface aidl android.hardware.light.ILights/default + class hal + user system + group system + # shutting off lights while powering-off + shutdown critical + +on post-fs + chown system system /sys/class/leds/green/brightness + chmod 664 /sys/class/leds/green/brightness diff --git a/lights/android.hardware.lights-service.tangorpro.xml b/lights/android.hardware.lights-service.tangorpro.xml new file mode 100644 index 0000000..545dfe1 --- /dev/null +++ b/lights/android.hardware.lights-service.tangorpro.xml @@ -0,0 +1,7 @@ + + + android.hardware.light + ILights/default + 2 + + diff --git a/lights/led_lut_calibrator.cpp b/lights/led_lut_calibrator.cpp new file mode 100644 index 0000000..0668693 --- /dev/null +++ b/lights/led_lut_calibrator.cpp @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2022 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 "led_lut_calibrator.h" + +#include +#include + +#include +#include + +const char kCalibrationPath[] = "/mnt/vendor/persist/led/led_calibration_LUT.txt"; + +LedLutCalibrator::LedLutCalibrator() { + if (!ReadCalibrationTable()) { + LOG(ERROR) << "Failed to read calibration table"; + cal_table_.clear(); + } +} + +int LedLutCalibrator::GetByColorIntensity(const std::string &color, int intensity) const { + std::string key = MakeLutKey(color, intensity); + const auto cal_data = cal_table_.find(key); + + if (cal_data == cal_table_.end()) { + LOG(ERROR) << "Failed to get calibration data"; + return -1; + } + + return cal_data->second; +} + +bool LedLutCalibrator::ReadCalibrationTable() { + int fd; + int bytes; + bool ret = true; + std::vector buffer; + buffer.resize(512); + + fd = open(kCalibrationPath, O_RDONLY); + if (fd < 0) { + LOG(ERROR) << "Failed to open " << kCalibrationPath; + ret = false; + goto ReadCalibrationTable_err; + } + + bytes = read(fd, buffer.data(), buffer.size()); + if (bytes == -1) { + LOG(ERROR) << "Failed to read " << kCalibrationPath; + ret = false; + goto ReadCalibrationTable_err; + } + + if (!ParseBlock(&buffer)) { + LOG(ERROR) << "Failed to parse calibration table "; + ret = false; + goto ReadCalibrationTable_err; + } + +ReadCalibrationTable_err: + if (fd != -1) + close(fd); + + return ret; +} + +bool LedLutCalibrator::ParseBlock(const std::vector *table) { + std::string str(table->begin(), table->end()); + auto result = std::vector{}; + auto ss = std::stringstream{str}; + + for (std::string line; std::getline(ss, line, '\n');) { + size_t start; + size_t end = 0; + std::vector colon_separated; + while ((start = line.find_first_not_of(':', end)) != std::string::npos) { + end = line.find(':', start); + if (end == std::string::npos) { + end = line.size(); + } + colon_separated.push_back(line.substr(start, end - start)); + } + if (colon_separated.size() != 3) { + continue; + } + std::string color_name = colon_separated[0]; + uint16_t intensity = atoi(colon_separated[1].c_str()); + uint16_t pwm = atoi(colon_separated[2].c_str()); + std::cout << color_name << intensity << pwm << std::endl; + std::string key = MakeLutKey(color_name, intensity); + cal_table_[key] = pwm; + } + + return true; +} + +std::string LedLutCalibrator::MakeLutKey(const std::string &color, int intensity) const { + std::string intensity_str = std::to_string(intensity); + std::string terms{color}; + + terms.append(intensity_str); + + return terms; +} diff --git a/lights/led_lut_calibrator.h b/lights/led_lut_calibrator.h new file mode 100644 index 0000000..3756e86 --- /dev/null +++ b/lights/led_lut_calibrator.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2022 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 GOOGLE_TANGOTRON_LIGHTS_LED_LUT_CALIBRATOR_H_ +#define GOOGLE_TANGOTRON_LIGHTS_LED_LUT_CALIBRATOR_H_ + +#include +#include +#include + +class LedLutCalibrator { + public: + LedLutCalibrator(); + LedLutCalibrator &operator=(const LedLutCalibrator &) = delete; + ~LedLutCalibrator() = default; + int GetByColorIntensity(const std::string &color, int intensity) const; + + private: + bool ReadCalibrationTable(); + bool ParseBlock(const std::vector *table); + std::string MakeLutKey(const std::string &color, int intensity) const; + + std::unordered_map cal_table_; +}; + +#endif // GOOGLE_TANGOTRON_LIGHTS_LED_LUT_CALIBRATOR_H_