summaryrefslogtreecommitdiff
path: root/cras/src/server/cras_bt_battery_provider.c
diff options
context:
space:
mode:
Diffstat (limited to 'cras/src/server/cras_bt_battery_provider.c')
-rw-r--r--cras/src/server/cras_bt_battery_provider.c371
1 files changed, 371 insertions, 0 deletions
diff --git a/cras/src/server/cras_bt_battery_provider.c b/cras/src/server/cras_bt_battery_provider.c
new file mode 100644
index 00000000..13e6590f
--- /dev/null
+++ b/cras/src/server/cras_bt_battery_provider.c
@@ -0,0 +1,371 @@
+/* Copyright 2020 The Chromium OS Authors. All rights reserved.
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include <dbus/dbus.h>
+#include <errno.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syslog.h>
+
+#include "cras_bt_adapter.h"
+#include "cras_bt_battery_provider.h"
+#include "cras_bt_constants.h"
+#include "cras_dbus_util.h"
+#include "cras_observer.h"
+#include "utlist.h"
+
+/* CRAS registers one battery provider to BlueZ, so we use a singleton. */
+static struct cras_bt_battery_provider battery_provider = {
+ .object_path = CRAS_DEFAULT_BATTERY_PROVIDER,
+ .interface = BLUEZ_INTERFACE_BATTERY_PROVIDER,
+ .conn = NULL,
+ .is_registered = false,
+ .observer = NULL,
+ .batteries = NULL,
+};
+
+static int cmp_battery_address(const struct cras_bt_battery *battery,
+ const char *address)
+{
+ return strcmp(battery->address, address);
+}
+
+static void replace_colon_with_underscore(char *str)
+{
+ for (int i = 0; str[i]; i++) {
+ if (str[i] == ':')
+ str[i] = '_';
+ }
+}
+
+/* Converts address XX:XX:XX:XX:XX:XX to Battery Provider object path:
+ * /org/chromium/Cras/Bluetooth/BatteryProvider/XX_XX_XX_XX_XX_XX
+ */
+static char *address_to_battery_path(const char *address)
+{
+ char *object_path = malloc(strlen(CRAS_DEFAULT_BATTERY_PROVIDER) +
+ strlen(address) + 2);
+
+ sprintf(object_path, "%s/%s", CRAS_DEFAULT_BATTERY_PROVIDER, address);
+ replace_colon_with_underscore(object_path);
+
+ return object_path;
+}
+
+/* Converts address XX:XX:XX:XX:XX:XX to device object path:
+ * /org/bluez/hci0/dev_XX_XX_XX_XX_XX_XX
+ */
+static char *address_to_device_path(const char *address)
+{
+ char *object_path = malloc(strlen(CRAS_DEFAULT_BATTERY_PREFIX) +
+ strlen(address) + 1);
+
+ sprintf(object_path, "%s%s", CRAS_DEFAULT_BATTERY_PREFIX, address);
+ replace_colon_with_underscore(object_path);
+
+ return object_path;
+}
+
+static struct cras_bt_battery *battery_new(const char *address, uint32_t level)
+{
+ struct cras_bt_battery *battery;
+
+ battery = calloc(1, sizeof(struct cras_bt_battery));
+ battery->address = strdup(address);
+ battery->object_path = address_to_battery_path(address);
+ battery->device_path = address_to_device_path(address);
+ battery->level = level;
+
+ return battery;
+}
+
+static void battery_free(struct cras_bt_battery *battery)
+{
+ if (battery->address)
+ free(battery->address);
+ if (battery->object_path)
+ free(battery->object_path);
+ if (battery->device_path)
+ free(battery->device_path);
+ free(battery);
+}
+
+static void populate_battery_properties(DBusMessageIter *iter,
+ const struct cras_bt_battery *battery)
+{
+ DBusMessageIter dict, entry, variant;
+ const char *property_percentage = "Percentage";
+ const char *property_device = "Device";
+ uint8_t level = battery->level;
+
+ dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, "{sv}", &dict);
+
+ dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL,
+ &entry);
+ dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING,
+ &property_percentage);
+ dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT,
+ DBUS_TYPE_BYTE_AS_STRING, &variant);
+ dbus_message_iter_append_basic(&variant, DBUS_TYPE_BYTE, &level);
+ dbus_message_iter_close_container(&entry, &variant);
+ dbus_message_iter_close_container(&dict, &entry);
+
+ dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL,
+ &entry);
+ dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING,
+ &property_device);
+ dbus_message_iter_open_container(&entry, DBUS_TYPE_VARIANT,
+ DBUS_TYPE_OBJECT_PATH_AS_STRING,
+ &variant);
+ dbus_message_iter_append_basic(&variant, DBUS_TYPE_OBJECT_PATH,
+ &battery->device_path);
+ dbus_message_iter_close_container(&entry, &variant);
+ dbus_message_iter_close_container(&dict, &entry);
+
+ dbus_message_iter_close_container(iter, &dict);
+}
+
+/* Creates a new battery object and exposes it on D-Bus. */
+static struct cras_bt_battery *
+get_or_create_battery(struct cras_bt_battery_provider *provider,
+ const char *address, uint32_t level)
+{
+ struct cras_bt_battery *battery;
+ DBusMessage *msg;
+ DBusMessageIter iter, dict, entry;
+
+ LL_SEARCH(provider->batteries, battery, address, cmp_battery_address);
+
+ if (battery)
+ return battery;
+
+ syslog(LOG_DEBUG, "Creating new battery for %s", address);
+
+ battery = battery_new(address, level);
+ LL_APPEND(provider->batteries, battery);
+
+ msg = dbus_message_new_signal(CRAS_DEFAULT_BATTERY_PROVIDER,
+ DBUS_INTERFACE_OBJECT_MANAGER,
+ DBUS_SIGNAL_INTERFACES_ADDED);
+
+ dbus_message_iter_init_append(msg, &iter);
+ dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH,
+ &battery->object_path);
+ dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "{sa{sv}}",
+ &dict);
+ dbus_message_iter_open_container(&dict, DBUS_TYPE_DICT_ENTRY, NULL,
+ &entry);
+ dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING,
+ &provider->interface);
+ populate_battery_properties(&entry, battery);
+ dbus_message_iter_close_container(&dict, &entry);
+ dbus_message_iter_close_container(&iter, &dict);
+
+ if (!dbus_connection_send(provider->conn, msg, NULL)) {
+ syslog(LOG_ERR,
+ "Error sending " DBUS_SIGNAL_INTERFACES_ADDED " signal");
+ }
+
+ dbus_message_unref(msg);
+
+ return battery;
+}
+
+/* Updates the level of a battery object and signals it on D-Bus. */
+static void
+update_battery_level(const struct cras_bt_battery_provider *provider,
+ struct cras_bt_battery *battery, uint32_t level)
+{
+ DBusMessage *msg;
+ DBusMessageIter iter;
+
+ if (battery->level == level)
+ return;
+
+ battery->level = level;
+
+ msg = dbus_message_new_signal(battery->object_path,
+ DBUS_INTERFACE_PROPERTIES,
+ DBUS_SIGNAL_PROPERTIES_CHANGED);
+
+ dbus_message_iter_init_append(msg, &iter);
+ dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING,
+ &provider->interface);
+ populate_battery_properties(&iter, battery);
+
+ if (!dbus_connection_send(provider->conn, msg, NULL)) {
+ syslog(LOG_ERR, "Error sending " DBUS_SIGNAL_PROPERTIES_CHANGED
+ " signal");
+ }
+
+ dbus_message_unref(msg);
+}
+
+/* Invoked when HFP sends an alert about a battery value change. */
+static void on_bt_battery_changed(void *context, const char *address,
+ uint32_t level)
+{
+ struct cras_bt_battery_provider *provider = context;
+
+ syslog(LOG_DEBUG, "Battery changed for address %s, level %d", address,
+ level);
+
+ if (!provider->is_registered) {
+ syslog(LOG_WARNING, "Received battery level update while "
+ "battery provider is not registered");
+ return;
+ }
+
+ struct cras_bt_battery *battery =
+ get_or_create_battery(provider, address, level);
+
+ update_battery_level(provider, battery, level);
+}
+
+/* Invoked when we receive a D-Bus return of RegisterBatteryProvider from
+ * BlueZ.
+ */
+static void
+cras_bt_on_battery_provider_registered(DBusPendingCall *pending_call,
+ void *data)
+{
+ DBusMessage *reply;
+ struct cras_bt_battery_provider *provider = data;
+ struct cras_observer_ops observer_ops;
+
+ reply = dbus_pending_call_steal_reply(pending_call);
+ dbus_pending_call_unref(pending_call);
+
+ if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) {
+ syslog(LOG_ERR, "RegisterBatteryProvider returned error: %s",
+ dbus_message_get_error_name(reply));
+ dbus_message_unref(reply);
+ return;
+ }
+
+ syslog(LOG_INFO, "RegisterBatteryProvider succeeded");
+
+ provider->is_registered = true;
+
+ memset(&observer_ops, 0, sizeof(observer_ops));
+ observer_ops.bt_battery_changed = on_bt_battery_changed;
+ provider->observer = cras_observer_add(&observer_ops, provider);
+
+ dbus_message_unref(reply);
+}
+
+int cras_bt_register_battery_provider(DBusConnection *conn,
+ const struct cras_bt_adapter *adapter)
+{
+ const char *adapter_path;
+ DBusMessage *method_call;
+ DBusMessageIter message_iter;
+ DBusPendingCall *pending_call;
+
+ if (battery_provider.is_registered) {
+ syslog(LOG_ERR, "Battery Provider already registered");
+ return -EBUSY;
+ }
+
+ if (battery_provider.conn)
+ dbus_connection_unref(battery_provider.conn);
+
+ battery_provider.conn = conn;
+ dbus_connection_ref(battery_provider.conn);
+
+ adapter_path = cras_bt_adapter_object_path(adapter);
+ method_call = dbus_message_new_method_call(
+ BLUEZ_SERVICE, adapter_path,
+ BLUEZ_INTERFACE_BATTERY_PROVIDER_MANAGER,
+ "RegisterBatteryProvider");
+ if (!method_call)
+ return -ENOMEM;
+
+ dbus_message_iter_init_append(method_call, &message_iter);
+ dbus_message_iter_append_basic(&message_iter, DBUS_TYPE_OBJECT_PATH,
+ &battery_provider.object_path);
+
+ if (!dbus_connection_send_with_reply(conn, method_call, &pending_call,
+ DBUS_TIMEOUT_USE_DEFAULT)) {
+ dbus_message_unref(method_call);
+ return -ENOMEM;
+ }
+
+ dbus_message_unref(method_call);
+
+ if (!pending_call)
+ return -EIO;
+
+ if (!dbus_pending_call_set_notify(
+ pending_call, cras_bt_on_battery_provider_registered,
+ &battery_provider, NULL)) {
+ dbus_pending_call_cancel(pending_call);
+ dbus_pending_call_unref(pending_call);
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+/* Removes a battery object and signals the removal on D-Bus as well. */
+static void cleanup_battery(struct cras_bt_battery_provider *provider,
+ struct cras_bt_battery *battery)
+{
+ DBusMessage *msg;
+ DBusMessageIter iter, entry;
+
+ if (!battery)
+ return;
+
+ LL_DELETE(provider->batteries, battery);
+
+ msg = dbus_message_new_signal(CRAS_DEFAULT_BATTERY_PROVIDER,
+ DBUS_INTERFACE_OBJECT_MANAGER,
+ DBUS_SIGNAL_INTERFACES_REMOVED);
+
+ dbus_message_iter_init_append(msg, &iter);
+ dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH,
+ &battery->object_path);
+ dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+ DBUS_TYPE_STRING_AS_STRING, &entry);
+ dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING,
+ &provider->interface);
+ dbus_message_iter_close_container(&iter, &entry);
+
+ if (!dbus_connection_send(provider->conn, msg, NULL)) {
+ syslog(LOG_ERR, "Error sending " DBUS_SIGNAL_INTERFACES_REMOVED
+ " signal");
+ }
+
+ dbus_message_unref(msg);
+
+ battery_free(battery);
+}
+
+void cras_bt_battery_provider_reset()
+{
+ struct cras_bt_battery *battery;
+
+ syslog(LOG_INFO, "Resetting battery provider");
+
+ if (!battery_provider.is_registered)
+ return;
+
+ battery_provider.is_registered = false;
+
+ LL_FOREACH (battery_provider.batteries, battery) {
+ cleanup_battery(&battery_provider, battery);
+ }
+
+ if (battery_provider.conn) {
+ dbus_connection_unref(battery_provider.conn);
+ battery_provider.conn = NULL;
+ }
+
+ if (battery_provider.observer) {
+ cras_observer_remove(battery_provider.observer);
+ battery_provider.observer = NULL;
+ }
+}