diff options
| -rw-r--r-- | CMakeLists.txt | 8 | ||||
| -rw-r--r-- | src/CMakeLists.txt | 8 | ||||
| -rw-r--r-- | src/deviceinfo-flashlight.c | 8 | ||||
| -rw-r--r-- | src/flashlight-binder.c | 247 | ||||
| -rw-r--r-- | src/flashlight.c | 25 | ||||
| -rw-r--r-- | src/flashlight.h | 10 | ||||
| -rw-r--r-- | tests/CMakeLists.txt | 2 |
7 files changed, 302 insertions, 6 deletions
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 <http://www.gnu.org/licenses/>. + */ + +/* + * 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 <gbinder.h> +#include <glib.h> + +#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 <gio/gio.h> #include <stdio.h> +#include <stdlib.h> +#include <string.h> #include <fcntl.h> #include <unistd.h> @@ -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) |
