blob: 13e6590f627833a6d107994aed758b0bb9aad3f7 [file] [log] [blame]
/* 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;
}
}