/* Copyright (c) 2013 The Chromium 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_constants.h"
#include "cras_bt_manager.h"
#include "cras_bt_adapter.h"
#include "cras_bt_device.h"
#include "cras_bt_endpoint.h"
#include "cras_bt_player.h"
#include "cras_bt_profile.h"
#include "cras_bt_transport.h"
#include "utlist.h"
static void cras_bt_interface_added(DBusConnection *conn,
const char *object_path,
const char *interface_name,
DBusMessageIter *properties_array_iter)
{
if (strcmp(interface_name, BLUEZ_INTERFACE_ADAPTER) == 0) {
struct cras_bt_adapter *adapter;
adapter = cras_bt_adapter_get(object_path);
if (adapter) {
cras_bt_adapter_update_properties(
adapter, properties_array_iter, NULL);
} else {
adapter = cras_bt_adapter_create(object_path);
if (adapter) {
cras_bt_adapter_update_properties(
adapter, properties_array_iter, NULL);
cras_bt_register_endpoints(conn, adapter);
cras_bt_register_player(conn, adapter);
cras_bt_register_profiles(conn);
syslog(LOG_INFO, "Bluetooth Adapter: %s added",
cras_bt_adapter_address(adapter));
} else {
syslog(LOG_WARNING,
"Failed to create Bluetooth Adapter: %s",
object_path);
}
}
} else if (strcmp(interface_name, BLUEZ_INTERFACE_DEVICE) == 0) {
struct cras_bt_device *device;
device = cras_bt_device_get(object_path);
if (device) {
cras_bt_device_update_properties(
device, properties_array_iter, NULL);
} else {
device = cras_bt_device_create(conn, object_path);
if (device) {
cras_bt_device_update_properties(
device, properties_array_iter, NULL);
syslog(LOG_INFO, "Bluetooth Device: %s added",
cras_bt_device_address(device));
} else {
syslog(LOG_WARNING,
"Failed to create Bluetooth Device: %s",
object_path);
}
}
} else if (strcmp(interface_name,
BLUEZ_INTERFACE_MEDIA_TRANSPORT) == 0) {
struct cras_bt_transport *transport;
transport = cras_bt_transport_get(object_path);
if (transport) {
cras_bt_transport_update_properties(
transport, properties_array_iter, NULL);
} else {
transport = cras_bt_transport_create(conn, object_path);
if (transport) {
cras_bt_transport_update_properties(
transport, properties_array_iter, NULL);
syslog(LOG_INFO,
"Bluetooth Transport: %s added",
cras_bt_transport_object_path(transport));
} else {
syslog(LOG_WARNING,
"Failed to create Bluetooth Transport: "
"%s",
object_path);
}
}
}
}
static void cras_bt_interface_removed(DBusConnection *conn,
const char *object_path,
const char *interface_name)
{
if (strcmp(interface_name, BLUEZ_INTERFACE_ADAPTER) == 0) {
struct cras_bt_adapter *adapter;
adapter = cras_bt_adapter_get(object_path);
if (adapter) {
syslog(LOG_INFO, "Bluetooth Adapter: %s removed",
cras_bt_adapter_address(adapter));
cras_bt_adapter_destroy(adapter);
}
} else if (strcmp(interface_name, BLUEZ_INTERFACE_DEVICE) == 0) {
struct cras_bt_device *device;
device = cras_bt_device_get(object_path);
if (device) {
syslog(LOG_INFO, "Bluetooth Device: %s removed",
cras_bt_device_address(device));
cras_bt_device_destroy(device);
}
} else if (strcmp(interface_name,
BLUEZ_INTERFACE_MEDIA_TRANSPORT) == 0) {
struct cras_bt_transport *transport;
transport = cras_bt_transport_get(object_path);
if (transport) {
syslog(LOG_INFO, "Bluetooth Transport: %s removed",
cras_bt_transport_object_path(transport));
cras_bt_transport_destroy(transport);
}
}
}
static void cras_bt_update_properties(DBusConnection *conn,
const char *object_path,
const char *interface_name,
DBusMessageIter *properties_array_iter,
DBusMessageIter *invalidated_array_iter)
{
if (strcmp(interface_name, BLUEZ_INTERFACE_ADAPTER) == 0) {
struct cras_bt_adapter *adapter;
adapter = cras_bt_adapter_get(object_path);
if (adapter) {
cras_bt_adapter_update_properties(
adapter, properties_array_iter,
invalidated_array_iter);
}
} else if (strcmp(interface_name, BLUEZ_INTERFACE_DEVICE) == 0) {
struct cras_bt_device *device;
device = cras_bt_device_get(object_path);
if (device) {
cras_bt_device_update_properties(
device, properties_array_iter,
invalidated_array_iter);
}
} else if (strcmp(interface_name,
BLUEZ_INTERFACE_MEDIA_TRANSPORT) == 0) {
struct cras_bt_transport *transport;
transport = cras_bt_transport_get(object_path);
if (transport) {
cras_bt_transport_update_properties(
transport, properties_array_iter,
invalidated_array_iter);
}
}
}
/* Destroys all bt related stuff. The reset functions must be called in
* reverse order of the adapter -> device -> profile(s) hierarchy.
*/
static void cras_bt_reset()
{
cras_bt_endpoint_reset();
cras_bt_transport_reset();
cras_bt_profile_reset();
cras_bt_device_reset();
cras_bt_adapter_reset();
}
static void cras_bt_on_get_managed_objects(DBusPendingCall *pending_call,
void *data)
{
DBusConnection *conn = (DBusConnection *)data;
DBusMessage *reply;
DBusMessageIter message_iter, object_array_iter;
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_WARNING, "GetManagedObjects returned error: %s",
dbus_message_get_error_name(reply));
dbus_message_unref(reply);
return;
}
if (!dbus_message_has_signature(reply, "a{oa{sa{sv}}}")) {
syslog(LOG_WARNING, "Bad GetManagedObjects reply received");
dbus_message_unref(reply);
return;
}
dbus_message_iter_init(reply, &message_iter);
dbus_message_iter_recurse(&message_iter, &object_array_iter);
while (dbus_message_iter_get_arg_type(&object_array_iter) !=
DBUS_TYPE_INVALID) {
DBusMessageIter object_dict_iter, interface_array_iter;
const char *object_path;
dbus_message_iter_recurse(&object_array_iter,
&object_dict_iter);
dbus_message_iter_get_basic(&object_dict_iter, &object_path);
dbus_message_iter_next(&object_dict_iter);
dbus_message_iter_recurse(&object_dict_iter,
&interface_array_iter);
while (dbus_message_iter_get_arg_type(&interface_array_iter) !=
DBUS_TYPE_INVALID) {
DBusMessageIter interface_dict_iter;
DBusMessageIter properties_array_iter;
const char *interface_name;
dbus_message_iter_recurse(&interface_array_iter,
&interface_dict_iter);
dbus_message_iter_get_basic(&interface_dict_iter,
&interface_name);
dbus_message_iter_next(&interface_dict_iter);
dbus_message_iter_recurse(&interface_dict_iter,
&properties_array_iter);
cras_bt_interface_added(conn,
object_path, interface_name,
&properties_array_iter);
dbus_message_iter_next(&interface_array_iter);
}
dbus_message_iter_next(&object_array_iter);
}
dbus_message_unref(reply);
}
static int cras_bt_get_managed_objects(DBusConnection *conn)
{
DBusMessage *method_call;
DBusPendingCall *pending_call;
method_call = dbus_message_new_method_call(
BLUEZ_SERVICE,
"/",
DBUS_INTERFACE_OBJECT_MANAGER,
"GetManagedObjects");
if (!method_call)
return -ENOMEM;
pending_call = NULL;
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_get_managed_objects,
conn, NULL)) {
dbus_pending_call_cancel(pending_call);
dbus_pending_call_unref(pending_call);
return -ENOMEM;
}
return 0;
}
static DBusHandlerResult cras_bt_handle_name_owner_changed(DBusConnection *conn,
DBusMessage *message,
void *arg)
{
DBusError dbus_error;
const char *service_name, *old_owner, *new_owner;
if (!dbus_message_is_signal(message, DBUS_INTERFACE_DBUS,
"NameOwnerChanged"))
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
dbus_error_init(&dbus_error);
if (!dbus_message_get_args(message, &dbus_error,
DBUS_TYPE_STRING, &service_name,
DBUS_TYPE_STRING, &old_owner,
DBUS_TYPE_STRING, &new_owner,
DBUS_TYPE_INVALID)) {
syslog(LOG_WARNING, "Bad NameOwnerChanged signal received: %s",
dbus_error.message);
dbus_error_free(&dbus_error);
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
syslog(LOG_INFO, "Bluetooth daemon disconnected from the bus.");
cras_bt_reset();
if (strlen(new_owner) > 0)
cras_bt_get_managed_objects(conn);
return DBUS_HANDLER_RESULT_HANDLED;
}
static DBusHandlerResult cras_bt_handle_interfaces_added(DBusConnection *conn,
DBusMessage *message,
void *arg)
{
DBusMessageIter message_iter, interface_array_iter;
const char *object_path;
if (!dbus_message_is_signal(message, DBUS_INTERFACE_OBJECT_MANAGER,
"InterfacesAdded"))
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
if (!dbus_message_has_signature(message, "oa{sa{sv}}")) {
syslog(LOG_WARNING, "Bad InterfacesAdded signal received");
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
dbus_message_iter_init(message, &message_iter);
dbus_message_iter_get_basic(&message_iter, &object_path);
dbus_message_iter_next(&message_iter);
dbus_message_iter_recurse(&message_iter, &interface_array_iter);
while (dbus_message_iter_get_arg_type(&interface_array_iter) !=
DBUS_TYPE_INVALID) {
DBusMessageIter interface_dict_iter, properties_array_iter;
const char *interface_name;
dbus_message_iter_recurse(&interface_array_iter,
&interface_dict_iter);
dbus_message_iter_get_basic(&interface_dict_iter,
&interface_name);
dbus_message_iter_next(&interface_dict_iter);
dbus_message_iter_recurse(&interface_dict_iter,
&properties_array_iter);
cras_bt_interface_added(conn, object_path, interface_name,
&properties_array_iter);
dbus_message_iter_next(&interface_array_iter);
}
return DBUS_HANDLER_RESULT_HANDLED;
}
static DBusHandlerResult cras_bt_handle_interfaces_removed(DBusConnection *conn,
DBusMessage *message,
void *arg)
{
DBusMessageIter message_iter, interface_array_iter;
const char *object_path;
if (!dbus_message_is_signal(message, DBUS_INTERFACE_OBJECT_MANAGER,
"InterfacesRemoved"))
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
if (!dbus_message_has_signature(message, "oas")) {
syslog(LOG_WARNING, "Bad InterfacesRemoved signal received");
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
dbus_message_iter_init(message, &message_iter);
dbus_message_iter_get_basic(&message_iter, &object_path);
dbus_message_iter_next(&message_iter);
dbus_message_iter_recurse(&message_iter, &interface_array_iter);
while (dbus_message_iter_get_arg_type(&interface_array_iter) !=
DBUS_TYPE_INVALID) {
const char *interface_name;
dbus_message_iter_get_basic(&interface_array_iter,
&interface_name);
cras_bt_interface_removed(conn, object_path, interface_name);
dbus_message_iter_next(&interface_array_iter);
}
return DBUS_HANDLER_RESULT_HANDLED;
}
static DBusHandlerResult cras_bt_handle_properties_changed(DBusConnection *conn,
DBusMessage *message,
void *arg)
{
DBusMessageIter message_iter, properties_array_iter;
DBusMessageIter invalidated_array_iter;
const char *object_path, *interface_name;
if (!dbus_message_is_signal(message, DBUS_INTERFACE_PROPERTIES,
"PropertiesChanged"))
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
if (!dbus_message_has_signature(message, "sa{sv}as")) {
syslog(LOG_WARNING, "Bad PropertiesChanged signal received");
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
}
object_path = dbus_message_get_path(message);
dbus_message_iter_init(message, &message_iter);
dbus_message_iter_get_basic(&message_iter, &interface_name);
dbus_message_iter_next(&message_iter);
dbus_message_iter_recurse(&message_iter, &properties_array_iter);
dbus_message_iter_next(&message_iter);
dbus_message_iter_recurse(&message_iter, &invalidated_array_iter);
cras_bt_update_properties(conn, object_path, interface_name,
&properties_array_iter,
&invalidated_array_iter);
return DBUS_HANDLER_RESULT_HANDLED;
}
void cras_bt_start(DBusConnection *conn)
{
DBusError dbus_error;
dbus_error_init(&dbus_error);
/* Inform the bus daemon which signals we wish to receive. */
dbus_bus_add_match(conn,
"type='signal',"
"sender='" DBUS_SERVICE_DBUS "',"
"interface='" DBUS_INTERFACE_DBUS "',"
"member='NameOwnerChanged',"
"arg0='" BLUEZ_SERVICE "'",
&dbus_error);
if (dbus_error_is_set(&dbus_error))
goto add_match_error;
dbus_bus_add_match(conn,
"type='signal',"
"sender='" BLUEZ_SERVICE "',"
"interface='" DBUS_INTERFACE_OBJECT_MANAGER "',"
"member='InterfacesAdded'",
&dbus_error);
if (dbus_error_is_set(&dbus_error))
goto add_match_error;
dbus_bus_add_match(conn,
"type='signal',"
"sender='" BLUEZ_SERVICE "',"
"interface='" DBUS_INTERFACE_OBJECT_MANAGER "',"
"member='InterfacesRemoved'",
&dbus_error);
if (dbus_error_is_set(&dbus_error))
goto add_match_error;
dbus_bus_add_match(conn,
"type='signal',"
"sender='" BLUEZ_SERVICE "',"
"interface='" DBUS_INTERFACE_PROPERTIES "',"
"member='PropertiesChanged'",
&dbus_error);
if (dbus_error_is_set(&dbus_error))
goto add_match_error;
/* Install filter functions to handle the signals we receive. */
if (!dbus_connection_add_filter(conn, cras_bt_handle_name_owner_changed,
NULL, NULL))
goto add_filter_error;
if (!dbus_connection_add_filter(conn, cras_bt_handle_interfaces_added,
NULL, NULL))
goto add_filter_error;
if (!dbus_connection_add_filter(conn, cras_bt_handle_interfaces_removed,
NULL, NULL))
goto add_filter_error;
if (!dbus_connection_add_filter(conn, cras_bt_handle_properties_changed,
NULL, NULL))
goto add_filter_error;
cras_bt_get_managed_objects(conn);
return;
add_match_error:
syslog(LOG_WARNING, "Couldn't setup Bluetooth device monitoring: %s",
dbus_error.message);
dbus_error_free(&dbus_error);
cras_bt_stop(conn);
return;
add_filter_error:
syslog(LOG_WARNING, "Couldn't setup Bluetooth device monitoring: %s",
strerror(ENOMEM));
cras_bt_stop(conn);
return;
}
void cras_bt_stop(DBusConnection *conn)
{
cras_bt_reset();
dbus_bus_remove_match(conn,
"type='signal',"
"sender='" DBUS_SERVICE_DBUS "',"
"interface='" DBUS_INTERFACE_DBUS "',"
"member='NameOwnerChanged',"
"arg0='" BLUEZ_SERVICE "'",
NULL);
dbus_bus_remove_match(conn,
"type='signal',"
"sender='" BLUEZ_SERVICE "',"
"interface='" DBUS_INTERFACE_OBJECT_MANAGER "',"
"member='InterfacesAdded'",
NULL);
dbus_bus_remove_match(conn,
"type='signal',"
"sender='" BLUEZ_SERVICE "',"
"interface='" DBUS_INTERFACE_OBJECT_MANAGER "',"
"member='InterfacesRemoved'",
NULL);
dbus_bus_remove_match(conn,
"type='signal',"
"sender='" BLUEZ_SERVICE "',"
"interface='" DBUS_INTERFACE_PROPERTIES "',"
"member='PropertiesChanged'",
NULL);
dbus_connection_remove_filter(conn, cras_bt_handle_name_owner_changed,
NULL);
dbus_connection_remove_filter(conn, cras_bt_handle_interfaces_added,
NULL);
dbus_connection_remove_filter(conn, cras_bt_handle_interfaces_removed,
NULL);
dbus_connection_remove_filter(conn, cras_bt_handle_properties_changed,
NULL);
}