From 7b3161b8ac511cf0dcc18aded00171f8cc07ae9a Mon Sep 17 00:00:00 2001 From: TheKit Date: Tue, 28 Jul 2026 13:15:00 +0300 Subject: flashlight: add a camera HAL backend Some flash drivers expose a sysfs node that only toggles the LED and leaves the duty and the flash safety timeout unset, so the LED goes out by itself. Drive the torch through the AIDL camera provider instead, which programs all three. Select it with "FlashlightBackend: camera-hal" in the device yaml. Needs libgbinder, so it is behind the ENABLE_LIBGBINDER build option. --- CMakeLists.txt | 8 ++ src/CMakeLists.txt | 8 +- src/deviceinfo-flashlight.c | 8 ++ src/flashlight-binder.c | 247 ++++++++++++++++++++++++++++++++++++++++++++ src/flashlight.c | 25 ++++- src/flashlight.h | 10 +- tests/CMakeLists.txt | 2 +- 7 files changed, 302 insertions(+), 6 deletions(-) create mode 100644 src/flashlight-binder.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 40df0c2..cbf8ad7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,7 @@ option(ENABLE_COVERAGE "Enable coverage reports (includes enabling all tests and option(ENABLE_WERROR "Treat all build warnings as errors" OFF) option(ENABLE_LOMIRI_FEATURES "Build with Lomiri-specific libraries, schemas and media" OFF) option(ENABLE_DEVICEINFO "Build with deviceinfo integration" OFF) +option(ENABLE_LIBGBINDER "Build with the camera HAL flashlight backend" OFF) option(ENABLE_RDA "Build with RDA (remote desktop awareness) support" ON) if(ENABLE_COVERAGE) @@ -109,6 +110,13 @@ if (ENABLE_DEVICEINFO) include_directories (${DEVICEINFO_INCLUDE_DIRS}) endif () +if (ENABLE_LIBGBINDER) + pkg_check_modules (GBINDER REQUIRED libgbinder) + include_directories (${GBINDER_INCLUDE_DIRS}) + link_directories (${GBINDER_LIBRARY_DIRS}) + add_definitions (-DENABLE_LIBGBINDER) +endif () + ## icons directory add_definitions(-DINDICATOR_ICONS_DIR="${CMAKE_INSTALL_FULL_DATADIR}/ayatana-indicator-power/icons") diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 60f80e6..d23190e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,11 +4,17 @@ if (ENABLE_DEVICEINFO) add_definitions(-DENABLE_LIBDEVICEINFO) endif () +if (ENABLE_LIBGBINDER) + set(FLASHLIGHT_BINDER + flashlight-binder.c) +endif () + # handwritten sources set(SERVICE_MANUAL_SOURCES brightness.c datafiles.c ${FLASHLIGHT_DEVICEINFO} + ${FLASHLIGHT_BINDER} device-provider-mock.c device-provider-upower.c device-provider.c @@ -53,5 +59,5 @@ link_directories(${SERVICE_DEPS_LIBRARY_DIRS}) # the executable: lib + main() add_executable (${SERVICE_EXEC} main.c) -target_link_libraries (${SERVICE_EXEC} ${SERVICE_LIB} ${SERVICE_DEPS_LIBRARIES} ${DEVICEINFO_LIBRARIES}) +target_link_libraries (${SERVICE_EXEC} ${SERVICE_LIB} ${SERVICE_DEPS_LIBRARIES} ${DEVICEINFO_LIBRARIES} ${GBINDER_LIBRARIES}) install (TARGETS ${SERVICE_EXEC} RUNTIME DESTINATION "${CMAKE_INSTALL_FULL_LIBEXECDIR}/${CMAKE_PROJECT_NAME}") diff --git a/src/deviceinfo-flashlight.c b/src/deviceinfo-flashlight.c index 91e9f02..62ce7be 100644 --- a/src/deviceinfo-flashlight.c +++ b/src/deviceinfo-flashlight.c @@ -42,3 +42,11 @@ char* flashlight_simple_disable_value() struct DeviceInfo* di = deviceinfo_new(); return deviceinfo_get(di, "FlashlightSimpleDisableValue", ""); } + +char* flashlight_backend() +{ + struct DeviceInfo* di = deviceinfo_new(); + char* backend = deviceinfo_get(di, "FlashlightBackend", ""); + deviceinfo_delete(di); + return backend; +} diff --git a/src/flashlight-binder.c b/src/flashlight-binder.c new file mode 100644 index 0000000..fd99a4f --- /dev/null +++ b/src/flashlight-binder.c @@ -0,0 +1,247 @@ +/* + * Copyright 2026 The UBports project + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 3, as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranties of + * MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR + * PURPOSE. See the GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program. If not, see . + */ + +/* + * Torch backend that goes through the AIDL camera provider HAL, for devices + * whose sysfs node cannot drive the LED on its own. setTorchMode() is + * specified to work with no camera session open. Some flash drivers expose a + * sysfs node that only toggles the LED and leaves the duty and the flash + * safety timeout unprogrammed, so the LED lights and then goes out again by + * itself; the HAL sets all three and the LED stays on. + * + * Selected with "FlashlightBackend: camera-hal" in the deviceinfo yaml. + */ + +#include "flashlight.h" + +#include +#include + +#define TORCH_BINDER_DEV "/dev/binder" +#define TORCH_PROVIDER_NAME "android.hardware.camera.provider.ICameraProvider/internal/0" +#define TORCH_PROVIDER_IFACE "android.hardware.camera.provider.ICameraProvider" +#define TORCH_DEVICE_IFACE "android.hardware.camera.device.ICameraDevice" + +/* AIDL transaction codes are FIRST_CALL_TRANSACTION plus the declaration index. */ +#define PROVIDER_GET_CAMERA_ID_LIST 3 +#define PROVIDER_GET_DEVICE_INTERFACE 4 +#define DEVICE_SET_TORCH_MODE 7 + +static GBinderServiceManager* torch_sm = NULL; +static GBinderClient* torch_provider = NULL; +static GBinderClient* torch_device = NULL; +static char** torch_camera_ids = NULL; +static gboolean torch_probed = FALSE; + +/* Every AIDL reply starts with an exception code; 0 is success. */ +static gboolean +reply_ok(GBinderReader* reader) +{ + gint32 exception = 0; + + return gbinder_reader_read_int32(reader, &exception) && exception == 0; +} + +static char** +read_camera_ids(void) +{ + GBinderLocalRequest* req; + GBinderRemoteReply* reply; + GPtrArray* ids = NULL; + int status = 0; + + req = gbinder_client_new_request(torch_provider); + reply = gbinder_client_transact_sync_reply(torch_provider, + PROVIDER_GET_CAMERA_ID_LIST, req, &status); + gbinder_local_request_unref(req); + + if (reply) + { + GBinderReader reader; + gint32 count = 0; + + gbinder_remote_reply_init_reader(reply, &reader); + + if (reply_ok(&reader) && gbinder_reader_read_int32(&reader, &count) && count > 0) + { + gint32 i; + + ids = g_ptr_array_new(); + + for (i = 0; i < count; i++) + { + char* name = gbinder_reader_read_string16(&reader); + + if (!name) + break; + + g_ptr_array_add(ids, name); + } + + g_ptr_array_add(ids, NULL); + } + + gbinder_remote_reply_unref(reply); + } + + return ids ? (char**) g_ptr_array_free(ids, FALSE) : NULL; +} + +static GBinderClient* +camera_device_client(const char* name) +{ + GBinderLocalRequest* req; + GBinderRemoteReply* reply; + GBinderWriter writer; + GBinderClient* client = NULL; + int status = 0; + + req = gbinder_client_new_request(torch_provider); + gbinder_local_request_init_writer(req, &writer); + gbinder_writer_append_string16(&writer, name); + reply = gbinder_client_transact_sync_reply(torch_provider, + PROVIDER_GET_DEVICE_INTERFACE, req, &status); + gbinder_local_request_unref(req); + + if (reply) + { + GBinderReader reader; + + gbinder_remote_reply_init_reader(reply, &reader); + + if (reply_ok(&reader)) + { + GBinderRemoteObject* obj = gbinder_reader_read_object(&reader); + + if (obj) + { + client = gbinder_client_new(obj, TORCH_DEVICE_IFACE); + gbinder_remote_object_unref(obj); + } + } + + gbinder_remote_reply_unref(reply); + } + + return client; +} + +static gboolean +set_torch_mode(GBinderClient* device, int enable) +{ + GBinderLocalRequest* req; + GBinderRemoteReply* reply; + GBinderWriter writer; + gboolean ok = FALSE; + int status = 0; + + req = gbinder_client_new_request(device); + gbinder_local_request_init_writer(req, &writer); + gbinder_writer_append_bool(&writer, enable != 0); + reply = gbinder_client_transact_sync_reply(device, DEVICE_SET_TORCH_MODE, req, &status); + gbinder_local_request_unref(req); + + if (reply) + { + GBinderReader reader; + + gbinder_remote_reply_init_reader(reply, &reader); + ok = reply_ok(&reader); + gbinder_remote_reply_unref(reply); + } + + return ok; +} + +static void +torch_probe(void) +{ + GBinderRemoteObject* remote; + int status = 0; + + torch_probed = TRUE; + + torch_sm = gbinder_servicemanager_new(TORCH_BINDER_DEV); + + if (!torch_sm) + { + g_warning("flashlight: cannot open %s", TORCH_BINDER_DEV); + return; + } + + /* Autoreleased, so it must not be unreffed here. */ + remote = gbinder_servicemanager_get_service_sync(torch_sm, TORCH_PROVIDER_NAME, + &status); + + if (!remote) + { + g_warning("flashlight: %s is not registered", TORCH_PROVIDER_NAME); + g_clear_pointer(&torch_sm, gbinder_servicemanager_unref); + return; + } + + torch_provider = gbinder_client_new(remote, TORCH_PROVIDER_IFACE); + torch_camera_ids = read_camera_ids(); + + if (!torch_camera_ids) + { + g_warning("flashlight: camera provider returned no cameras"); + g_clear_pointer(&torch_provider, gbinder_client_unref); + g_clear_pointer(&torch_sm, gbinder_servicemanager_unref); + } +} + +int +flashlight_camera_hal_supported() +{ + if (!torch_probed) + torch_probe(); + + return torch_camera_ids != NULL; +} + +int +flashlight_camera_hal_set(int enable) +{ + char** name; + + if (!flashlight_camera_hal_supported()) + return 0; + + if (torch_device && set_torch_mode(torch_device, enable)) + return 1; + + /* Only one camera owns the flash, and which one is not known up front. */ + g_clear_pointer(&torch_device, gbinder_client_unref); + + for (name = torch_camera_ids; *name; name++) + { + GBinderClient* device = camera_device_client(*name); + + if (!device) + continue; + + if (set_torch_mode(device, enable)) + { + torch_device = device; + return 1; + } + + gbinder_client_unref(device); + } + + return 0; +} diff --git a/src/flashlight.c b/src/flashlight.c index 337c01c..4a9e40c 100644 --- a/src/flashlight.c +++ b/src/flashlight.c @@ -23,6 +23,8 @@ #include #include +#include +#include #include #include @@ -36,6 +38,7 @@ extern char* flashlight_path(); extern char* flashlight_switch_path(); extern char* flashlight_simple_enable_value(); extern char* flashlight_simple_disable_value(); +extern char* flashlight_backend(); #endif const size_t qcom_sysfs_size = 7; @@ -61,8 +64,19 @@ enum TorchType torch_type = SIMPLE; gboolean activated = 0; int -set_sysfs_path() +probe_flashlight() { +# if defined(ENABLE_LIBDEVICEINFO) && defined(ENABLE_LIBGBINDER) + char* backend = flashlight_backend(); + int use_camera_hal = !strcmp(backend, "camera-hal"); + + free(backend); + + if (use_camera_hal) { + torch_type = CAMERA_HAL; + return flashlight_camera_hal_supported(); + } +# endif # ifdef ENABLE_LIBDEVICEINFO if (strcmp(flashlight_path(), "")) { if (access(flashlight_path(), F_OK) != -1) { @@ -176,13 +190,18 @@ toggle_flashlight_action(GAction *action, GVariant *state; int toggled; - if (!set_sysfs_path()) + if (!probe_flashlight()) return; state = g_action_get_state(action); activated = g_variant_get_boolean(state); g_variant_unref(state); +# ifdef ENABLE_LIBGBINDER + if (torch_type == CAMERA_HAL) + toggled = flashlight_camera_hal_set(!activated); + else +# endif if (torch_type == QCOM) toggled = toggle_flashlight_action_qcom(); else @@ -195,5 +214,5 @@ toggle_flashlight_action(GAction *action, int flashlight_supported() { - return set_sysfs_path(); + return probe_flashlight(); } diff --git a/src/flashlight.h b/src/flashlight.h index 19f738e..2d2c3ff 100644 --- a/src/flashlight.h +++ b/src/flashlight.h @@ -41,8 +41,16 @@ flashlight_supported(); gboolean flashlight_activated(); +#ifdef ENABLE_LIBGBINDER +int +flashlight_camera_hal_supported(); + +int +flashlight_camera_hal_set(int enable); +#endif + enum -TorchType { SIMPLE = 1, QCOM }; +TorchType { SIMPLE = 1, QCOM, CAMERA_HAL }; G_END_DECLS diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index d45f4d1..fac4ecd 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -43,7 +43,7 @@ function(add_test_by_name name) target_link_options(${TEST_NAME} PRIVATE -no-pie) add_test (${TEST_NAME} ${TEST_NAME}) add_dependencies (${TEST_NAME} ${SERVICE_LIB} gschemas-compiled) - target_link_libraries (${TEST_NAME} ${SERVICE_LIB} ${DBUSTEST_LIBRARIES} ${SERVICE_DEPS_LIBRARIES} ${DEVICEINFO_LIBRARIES} ${GMOCK_LIBRARIES}) + target_link_libraries (${TEST_NAME} ${SERVICE_LIB} ${DBUSTEST_LIBRARIES} ${SERVICE_DEPS_LIBRARIES} ${DEVICEINFO_LIBRARIES} ${GBINDER_LIBRARIES} ${GMOCK_LIBRARIES}) endfunction() add_test_by_name(test-notify) add_test(NAME dear-reader-the-next-test-takes-80-seconds COMMAND true) -- cgit v1.2.3