/* * 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; }