aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt8
-rw-r--r--src/CMakeLists.txt8
-rw-r--r--src/deviceinfo-flashlight.c8
-rw-r--r--src/flashlight-binder.c247
-rw-r--r--src/flashlight.c25
-rw-r--r--src/flashlight.h10
-rw-r--r--tests/CMakeLists.txt2
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)