diff options
author | Marcel Holtmann <marcel@holtmann.org> | 2008-07-26 19:00:53 +0200 |
---|---|---|
committer | Marcel Holtmann <marcel@holtmann.org> | 2008-07-26 19:00:53 +0200 |
commit | d6ae1c3f777832f8e32702f81fe64e33a1396928 (patch) | |
tree | 159a1e59f3929c9d795dbd1f3edd84d9dccba048 /audio/manager.c | |
parent | b8e5fea8d31fbcd3d1c044385f8217dbf39892bb (diff) | |
parent | 3382af9114a9b2e657c7ddd0a5511edda6a37a90 (diff) |
Import bluez-utils-3.36 revision history
Diffstat (limited to 'audio/manager.c')
-rw-r--r-- | audio/manager.c | 1571 |
1 files changed, 1571 insertions, 0 deletions
diff --git a/audio/manager.c b/audio/manager.c new file mode 100644 index 00000000..18ef9eb4 --- /dev/null +++ b/audio/manager.c @@ -0,0 +1,1571 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2006-2007 Nokia Corporation + * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org> + * + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY 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, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <errno.h> +#include <unistd.h> +#include <stdint.h> +#include <sys/stat.h> +#include <dirent.h> +#include <ctype.h> +#include <signal.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/hci.h> +#include <bluetooth/hci_lib.h> +#include <bluetooth/rfcomm.h> +#include <bluetooth/sdp.h> +#include <bluetooth/sdp_lib.h> + +#include <glib.h> +#include <dbus/dbus.h> +#include <gdbus.h> + +#include "glib-helper.h" + +#include "dbus-service.h" +#include "logging.h" +#include "textfile.h" +#include "ipc.h" +#include "device.h" +#include "error.h" +#include "avdtp.h" +#include "a2dp.h" +#include "headset.h" +#include "gateway.h" +#include "sink.h" +#include "control.h" +#include "manager.h" +#include "sdpd.h" + +typedef enum { + HEADSET = 1 << 0, + GATEWAY = 1 << 1, + SINK = 1 << 2, + SOURCE = 1 << 3, + CONTROL = 1 << 4, + TARGET = 1 << 5, + INVALID = 1 << 6 +} audio_service_type; + +typedef enum { + GENERIC_AUDIO = 0, + ADVANCED_AUDIO, + AV_REMOTE, + GET_RECORDS +} audio_sdp_state_t; + +struct audio_sdp_data { + struct audio_device *device; + + DBusMessage *msg; /* Method call or NULL */ + + GSList *records; /* sdp_record_t * */ + + audio_sdp_state_t state; + + create_dev_cb_t cb; + void *cb_data; +}; + +static DBusConnection *connection = NULL; + +static struct audio_device *default_hs = NULL; +static struct audio_device *default_dev = NULL; + +static GSList *devices = NULL; + +static uint32_t hsp_ag_record_id = 0; +static uint32_t hfp_ag_record_id = 0; + +static uint32_t hsp_hs_record_id = 0; + +static GIOChannel *hsp_ag_server = NULL; +static GIOChannel *hfp_ag_server = NULL; + +static GIOChannel *hsp_hs_server = NULL; + +static struct enabled_interfaces enabled = { + .headset = TRUE, + .gateway = FALSE, + .sink = TRUE, + .source = FALSE, + .control = TRUE, +}; + +static DBusMessage *get_records(uuid_t *uuid, struct audio_sdp_data *data); + +static struct audio_device *create_device(const bdaddr_t *bda) +{ + static int device_id = 0; + char path[128]; + + snprintf(path, sizeof(path) - 1, + "%s/device%d", AUDIO_MANAGER_PATH, device_id++); + + return device_register(connection, path, bda); +} + +static void destroy_device(struct audio_device *device) +{ + g_dbus_unregister_all_interfaces(connection, device->path); +} + +static void remove_device(struct audio_device *device) +{ + if (device == default_dev) { + debug("Removing default device"); + default_dev = NULL; + } + + if (device == default_hs) { + debug("Removing default headset"); + default_hs = NULL; + } + + devices = g_slist_remove(devices, device); + + destroy_device(device); +} + +static gboolean add_device(struct audio_device *device, gboolean send_signals) +{ + if (!send_signals) + goto add; + + g_dbus_emit_signal(connection, AUDIO_MANAGER_PATH, + AUDIO_MANAGER_INTERFACE, + "DeviceCreated", + DBUS_TYPE_STRING, &device->path, + DBUS_TYPE_INVALID); + + if (device->headset) + g_dbus_emit_signal(connection, + AUDIO_MANAGER_PATH, + AUDIO_MANAGER_INTERFACE, + "HeadsetCreated", + DBUS_TYPE_STRING, &device->path, + DBUS_TYPE_INVALID); +add: + + if (default_dev == NULL && g_slist_length(devices) == 0) { + debug("Selecting default device"); + default_dev = device; + } + + if (!default_hs && device->headset && !devices) + default_hs = device; + + devices = g_slist_append(devices, device); + + return TRUE; +} + +static uint16_t get_service_uuid(const sdp_record_t *record) +{ + sdp_list_t *classes; + uuid_t uuid; + uint16_t uuid16 = 0; + + if (sdp_get_service_classes(record, &classes) < 0) { + error("Unable to get service classes from record"); + return 0; + } + + memcpy(&uuid, classes->data, sizeof(uuid)); + + if (!sdp_uuid128_to_uuid(&uuid)) { + error("Not a 16 bit UUID"); + sdp_list_free(classes, free); + return 0; + } + + if (uuid.type == SDP_UUID32) { + if (uuid.value.uuid32 > 0xFFFF) { + error("Not a 16 bit UUID"); + goto done; + } + uuid16 = (uint16_t) uuid.value.uuid32; + } else + uuid16 = uuid.value.uuid16; + +done: + sdp_list_free(classes, free); + + return uuid16; +} + +gboolean server_is_enabled(uint16_t svc) +{ + gboolean ret; + + switch (svc) { + case HEADSET_SVCLASS_ID: + ret = (hsp_ag_server != NULL); + break; + case HEADSET_AGW_SVCLASS_ID: + ret = (hsp_hs_server != NULL); + break; + case HANDSFREE_SVCLASS_ID: + ret = (hfp_ag_server != NULL); + break; + case HANDSFREE_AGW_SVCLASS_ID: + ret = FALSE; + break; + case AUDIO_SINK_SVCLASS_ID: + return enabled.sink; + case AV_REMOTE_TARGET_SVCLASS_ID: + case AV_REMOTE_SVCLASS_ID: + return enabled.control; + default: + ret = FALSE; + break; + } + + return ret; +} + +static void handle_record(sdp_record_t *record, struct audio_device *device) +{ + gboolean is_default; + uint16_t uuid16; + + uuid16 = get_service_uuid(record); + + if (!server_is_enabled(uuid16)) + return; + + switch (uuid16) { + case HEADSET_SVCLASS_ID: + debug("Found Headset record"); + if (device->headset) + headset_update(device, record, uuid16); + else + device->headset = headset_init(device, + record, uuid16); + break; + case HEADSET_AGW_SVCLASS_ID: + debug("Found Headset AG record"); + break; + case HANDSFREE_SVCLASS_ID: + debug("Found Hansfree record"); + if (device->headset) + headset_update(device, record, uuid16); + else + device->headset = headset_init(device, + record, uuid16); + break; + case HANDSFREE_AGW_SVCLASS_ID: + debug("Found Handsfree AG record"); + break; + case AUDIO_SINK_SVCLASS_ID: + debug("Found Audio Sink"); + if (device->sink == NULL) + device->sink = sink_init(device); + break; + case AUDIO_SOURCE_SVCLASS_ID: + debug("Found Audio Source"); + break; + case AV_REMOTE_SVCLASS_ID: + debug("Found AV Remote"); + if (device->control == NULL) + device->control = control_init(device); + if (device->sink && sink_is_active(device)) + avrcp_connect(device); + break; + case AV_REMOTE_TARGET_SVCLASS_ID: + debug("Found AV Target"); + if (device->control == NULL) + device->control = control_init(device); + if (device->sink && sink_is_active(device)) + avrcp_connect(device); + break; + default: + debug("Unrecognized UUID: 0x%04X", uuid16); + break; + } + + is_default = (default_dev == device) ? TRUE : FALSE; + + device_store(device, is_default); +} + +static void finish_sdp(struct audio_sdp_data *data, gboolean success) +{ + const char *addr; + DBusMessage *reply = NULL; + DBusError derr; + + debug("Audio service discovery completed with %s", + success ? "success" : "failure"); + + if (!success) + goto done; + + if (!data->msg) + goto update; + + dbus_error_init(&derr); + dbus_message_get_args(data->msg, &derr, + DBUS_TYPE_STRING, &addr, + DBUS_TYPE_INVALID); + + if (dbus_error_is_set(&derr)) { + error("Unable to get message args"); + success = FALSE; + error_failed(connection, data->msg, derr.message); + dbus_error_free(&derr); + goto done; + } + + /* Return error if no audio related service records were found */ + if (!data->records) { + debug("No audio audio related service records were found"); + success = FALSE; + error_not_supported(connection, data->msg); + goto done; + } + + reply = dbus_message_new_method_return(data->msg); + if (!reply) { + success = FALSE; + error_failed(connection, data->msg, "Out of memory"); + goto done; + } + +update: + g_slist_foreach(data->records, (GFunc) handle_record, data->device); + + if (!g_slist_find(devices, data->device)) + add_device(data->device, TRUE); + + if (reply) { + dbus_message_append_args(reply, DBUS_TYPE_STRING, + &data->device->path, + DBUS_TYPE_INVALID); + dbus_connection_send(connection, reply, NULL); + dbus_message_unref(reply); + } + +done: + if (success) { + if (data->cb) + data->cb(data->device, data->cb_data); + } else { + if (data->cb) + data->cb(NULL, data->cb_data); + if (!g_slist_find(devices, data->device)) + destroy_device(data->device); + } + if (data->msg) + dbus_message_unref(data->msg); + g_slist_foreach(data->records, (GFunc) sdp_record_free, NULL); + g_slist_free(data->records); + g_free(data); +} + +static void get_records_cb(sdp_list_t *recs, int err, gpointer user_data) +{ + struct audio_sdp_data *data = user_data; + sdp_list_t *seq; + uuid_t uuid; + + if (err < 0) { + error_connection_attempt_failed(connection, data->msg, -err); + finish_sdp(data, FALSE); + return; + } + + for (seq = recs; seq; seq = seq->next) { + sdp_record_t *rec = (sdp_record_t *) seq->data; + + if (!rec) + break; + + data->records = g_slist_append(data->records, rec); + } + + sdp_list_free(recs, NULL); + + data->state++; + + switch (data->state) { + case ADVANCED_AUDIO: + sdp_uuid16_create(&uuid, ADVANCED_AUDIO_SVCLASS_ID); + break; + case AV_REMOTE: + sdp_uuid16_create(&uuid, AV_REMOTE_SVCLASS_ID); + break; + default: + finish_sdp(data, TRUE); + return; + } + + get_records(&uuid, data); +} + +static DBusMessage *get_records(uuid_t *uuid, struct audio_sdp_data *data) +{ + struct audio_device *device = data->device; + DBusMessage *reply = NULL; + int err; + + err = bt_search_service(&device->src, &device->dst, uuid, + get_records_cb, data, NULL); + if (!err) + return NULL; + + if (data->msg) + reply = g_dbus_create_error(data->msg, + ERROR_INTERFACE ".ConnectionAttemptFailed", + strerror(-err)); + + finish_sdp(data, FALSE); + + return reply; +} + +static DBusMessage *resolve_services(DBusMessage *msg, + struct audio_device *device, + create_dev_cb_t cb, + void *user_data) +{ + struct audio_sdp_data *sdp_data; + uuid_t uuid; + + sdp_data = g_new0(struct audio_sdp_data, 1); + if (msg) + sdp_data->msg = dbus_message_ref(msg); + sdp_data->device = device; + sdp_data->cb = cb; + sdp_data->cb_data = user_data; + + sdp_uuid16_create(&uuid, GENERIC_AUDIO_SVCLASS_ID); + + return get_records(&uuid, sdp_data); +} + +struct audio_device *manager_device_connected(const bdaddr_t *bda, const char *uuid) +{ + struct audio_device *device; + const char *path; + gboolean headset = FALSE, created = FALSE; + + device = manager_find_device(bda, NULL, FALSE); + if (!device) { + device = create_device(bda); + if (!device) + return NULL; + if (!add_device(device, TRUE)) { + destroy_device(device); + return NULL; + } + created = TRUE; + } + + if (!strcmp(uuid, HSP_AG_UUID) || !strcmp(uuid, HFP_AG_UUID)) { + if (device->headset) + return device; + + device->headset = headset_init(device, NULL, 0); + + if (!device->headset) + return NULL; + + headset = TRUE; + } else if (!strcmp(uuid, A2DP_SOURCE_UUID)) { + if (device->sink) + return device; + + device->sink = sink_init(device); + + if (!device->sink) + return NULL; + } else if (!strcmp(uuid, AVRCP_TARGET_UUID)) { + if (device->control) + return device; + + device->control = control_init(device); + + if (!device->control) + return NULL; + } else + return NULL; + + path = device->path; + + if (created) { + g_dbus_emit_signal(connection, AUDIO_MANAGER_PATH, + AUDIO_MANAGER_INTERFACE, + "DeviceCreated", + DBUS_TYPE_STRING, &path, + DBUS_TYPE_INVALID); + resolve_services(NULL, device, NULL, NULL); + } + + if (headset) + g_dbus_emit_signal(connection, AUDIO_MANAGER_PATH, + AUDIO_MANAGER_INTERFACE, + "HeadsetCreated", + DBUS_TYPE_STRING, &path, + DBUS_TYPE_INVALID); + + if (headset && !default_hs) { + default_hs = device; + g_dbus_emit_signal(connection, AUDIO_MANAGER_PATH, + AUDIO_MANAGER_INTERFACE, + "DefaultHeadsetChanged", + DBUS_TYPE_STRING, &path, + DBUS_TYPE_INVALID); + } + + if (!default_dev) { + default_dev = device; + g_dbus_emit_signal(connection, AUDIO_MANAGER_PATH, + AUDIO_MANAGER_INTERFACE, + "DefaultDeviceChanged", + DBUS_TYPE_STRING, &path, + DBUS_TYPE_INVALID); + } + + return device; +} + +gboolean manager_create_device(bdaddr_t *bda, create_dev_cb_t cb, + void *user_data) +{ + struct audio_device *dev; + + dev = create_device(bda); + if (!dev) + return FALSE; + + resolve_services(NULL, dev, cb, user_data); + + return TRUE; +} + +static DBusMessage *am_create_device(DBusConnection *conn, + DBusMessage *msg, + void *data) +{ + const char *address, *path; + bdaddr_t bda; + struct audio_device *device; + DBusMessage *reply; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &address, + DBUS_TYPE_INVALID)) + return NULL; + + str2ba(address, &bda); + + device = manager_find_device(&bda, NULL, FALSE); + if (!device) { + device = create_device(&bda); + return resolve_services(msg, device, NULL, NULL); + } + + path = device->path; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_append_args(reply, DBUS_TYPE_STRING, &path, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *am_list_devices(DBusConnection *conn, + DBusMessage *msg, + void *data) +{ + DBusMessageIter iter, array_iter; + DBusMessage *reply; + DBusError derr; + GSList *l; + gboolean hs_only = FALSE; + + dbus_error_init(&derr); + + if (dbus_message_is_method_call(msg, AUDIO_MANAGER_INTERFACE, + "ListHeadsets")) + hs_only = TRUE; + else + hs_only = FALSE; + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_iter_init_append(reply, &iter); + + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_TYPE_STRING_AS_STRING, &array_iter); + + for (l = devices; l != NULL; l = l->next) { + struct audio_device *device = l->data; + + if (hs_only && !device->headset) + continue; + + dbus_message_iter_append_basic(&array_iter, + DBUS_TYPE_STRING, &device->path); + } + + dbus_message_iter_close_container(&iter, &array_iter); + + return reply; +} + +static gint device_path_cmp(gconstpointer a, gconstpointer b) +{ + const struct audio_device *device = a; + const char *path = b; + + return strcmp(device->path, path); +} + +static DBusMessage *am_remove_device(DBusConnection *conn, + DBusMessage *msg, + void *data) +{ + DBusMessage *reply; + GSList *match; + const char *path; + struct audio_device *device; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &path, + DBUS_TYPE_INVALID)) + return NULL; + + match = g_slist_find_custom(devices, path, device_path_cmp); + if (!match) + return g_dbus_create_error(msg, ERROR_INTERFACE ".DoesNotExists", + "Device does not exists"); + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + device = match->data; + device_remove_stored(device); + remove_device(device); + + /* Fallback to a valid default */ + if (default_dev == NULL) { + const char *param; + GSList *l; + + default_dev = manager_find_device(BDADDR_ANY, NULL, TRUE); + + if (!default_dev && devices) { + l = devices; + default_dev = (g_slist_last(l))->data; + } + + param = default_dev ? default_dev->path : ""; + + g_dbus_emit_signal(conn, AUDIO_MANAGER_PATH, + AUDIO_MANAGER_INTERFACE, + "DefaultHeadsetChanged", + DBUS_TYPE_STRING, ¶m, + DBUS_TYPE_INVALID); + + g_dbus_emit_signal(conn, AUDIO_MANAGER_PATH, + AUDIO_MANAGER_INTERFACE, + "DefaultDeviceChanged", + DBUS_TYPE_STRING, ¶m, + DBUS_TYPE_INVALID); + + if (default_dev) + device_store(default_dev, TRUE); + } + + g_dbus_emit_signal(conn, AUDIO_MANAGER_PATH, + AUDIO_MANAGER_INTERFACE, + "HeadsetRemoved", + DBUS_TYPE_STRING, &path, + DBUS_TYPE_INVALID); + + g_dbus_emit_signal(conn, AUDIO_MANAGER_PATH, + AUDIO_MANAGER_INTERFACE, + "DeviceRemoved", + DBUS_TYPE_STRING, &path, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *am_find_by_addr(DBusConnection *conn, + DBusMessage *msg, + void *data) +{ + const char *address; + DBusMessage *reply; + struct audio_device *device; + bdaddr_t bda; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &address, + DBUS_TYPE_INVALID)) + return NULL; + + str2ba(address, &bda); + device = manager_find_device(&bda, NULL, FALSE); + + if (!device) + return g_dbus_create_error(msg, ERROR_INTERFACE ".DoesNotExists", + "Device does not exists"); + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_append_args(reply, DBUS_TYPE_STRING, &device->path, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *am_default_device(DBusConnection *conn, + DBusMessage *msg, + void *data) +{ + DBusMessage *reply; + + if (!default_dev) + return g_dbus_create_error(msg, ERROR_INTERFACE ".DoesNotExists", + "Device does not exists"); + + if (default_dev->headset == NULL && + dbus_message_is_method_call(msg, AUDIO_MANAGER_INTERFACE, + "DefaultHeadset")) + return g_dbus_create_error(msg, ERROR_INTERFACE ".DoesNotExists", + "Device does not exists"); + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + dbus_message_append_args(reply, DBUS_TYPE_STRING, &default_dev->path, + DBUS_TYPE_INVALID); + + return reply; +} + +static DBusMessage *am_change_default_device(DBusConnection *conn, + DBusMessage *msg, + void *data) +{ + DBusMessage *reply; + GSList *match; + const char *path; + struct audio_device *device; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &path, + DBUS_TYPE_INVALID)) + return NULL; + + match = g_slist_find_custom(devices, path, device_path_cmp); + if (!match) + return g_dbus_create_error(msg, ERROR_INTERFACE ".DoesNotExists", + "Device does not exists"); + + reply = dbus_message_new_method_return(msg); + if (!reply) + return NULL; + + device = match->data; + + if (!dbus_message_is_method_call(msg, AUDIO_MANAGER_INTERFACE, + "ChangeDefaultHeadset")) + g_dbus_emit_signal(conn, AUDIO_MANAGER_PATH, + AUDIO_MANAGER_INTERFACE, + "DefaultDeviceChanged", + DBUS_TYPE_STRING, &device->path, + DBUS_TYPE_INVALID); + else if (device->headset) + g_dbus_emit_signal(conn, AUDIO_MANAGER_PATH, + AUDIO_MANAGER_INTERFACE, + "DefaultHeadsetChanged", + DBUS_TYPE_STRING, &device->path, + DBUS_TYPE_INVALID); + else + return g_dbus_create_error(msg, ERROR_INTERFACE ".DoesNotExists", + "Device does not exists"); + + default_dev = device; + device_store(device, TRUE); + + return reply; +} + +static GDBusMethodTable manager_methods[] = { + { "CreateDevice", "s", "s", am_create_device, + G_DBUS_METHOD_FLAG_ASYNC }, + { "RemoveDevice", "s", "", am_remove_device }, + { "ListDevices", "", "as", am_list_devices }, + { "DefaultDevice", "", "s", am_default_device }, + { "ChangeDefaultDevice", "s", "", am_change_default_device }, + { "CreateHeadset", "s", "s", am_create_device, + G_DBUS_METHOD_FLAG_ASYNC }, + { "RemoveHeadset", "s", "", am_remove_device }, + { "ListHeadsets", "", "as", am_list_devices }, + { "FindDeviceByAddress", "s", "s", am_find_by_addr }, + { "DefaultHeadset", "", "s", am_default_device }, + { "ChangeDefaultHeadset", "s", "", am_change_default_device }, + { } +}; + +static GDBusSignalTable manager_signals[] = { + { "DeviceCreated", "s" }, + { "DeviceRemoved", "s" }, + { "HeadsetCreated", "s" }, + { "HeadsetRemoved", "s" }, + { "DefaultDeviceChanged", "s" }, + { "DefaultHeadsetChanged", "s" }, + { } +}; + +static void parse_stored_devices(char *key, char *value, void *data) +{ + bdaddr_t *src = data; + struct audio_device *device; + bdaddr_t dst; + + if (!key || !value || strcmp(key, "default") == 0) + return; + + str2ba(key, &dst); + device = manager_find_device(&dst, NULL, FALSE); + + if (device) + return; + + info("Loading device %s (%s)", key, value); + + device = create_device(&dst); + if (!device) + return; + + /* Change storage to source adapter */ + bacpy(&device->store, src); + + if (enabled.headset && strstr(value, "headset")) + device->headset = headset_init(device, NULL, 0); + if (enabled.sink && strstr(value, "sink")) + device->sink = sink_init(device); + if (enabled.control && strstr(value, "control")) + device->control = control_init(device); + add_device(device, FALSE); +} + +static void register_devices_stored(const char *adapter) +{ + char filename[PATH_MAX + 1]; + struct stat st; + struct audio_device *device; + bdaddr_t default_src; + bdaddr_t dst; + bdaddr_t src; + char *addr; + int dev_id; + + create_name(filename, PATH_MAX, STORAGEDIR, adapter, "audio"); + + str2ba(adapter, &src); + + if (stat(filename, &st) < 0) + return; + + if (!(st.st_mode & __S_IFREG)) + return; + + textfile_foreach(filename, parse_stored_devices, &src); + + bacpy(&default_src, BDADDR_ANY); + dev_id = hci_get_route(&default_src); + if (dev_id < 0 || hci_devba(dev_id, &default_src) < 0) + return; + + if (bacmp(&default_src, &src) != 0) + return; + + addr = textfile_get(filename, "default"); + if (!addr) + return; + + str2ba(addr, &dst); + device = manager_find_device(&dst, NULL, FALSE); + + if (device) { + info("Setting %s as default device", addr); + default_dev = device; + } + + free(addr); +} + +static void register_stored(void) +{ + char dirname[PATH_MAX + 1]; + struct dirent *de; + DIR *dir; + + snprintf(dirname, PATH_MAX, "%s", STORAGEDIR); + + dir = opendir(dirname); + if (!dir) + return; + + while ((de = readdir(dir)) != NULL) { + if (!isdigit(de->d_name[0])) + continue; + + /* Device objects */ + register_devices_stored(de->d_name); + } + + closedir(dir); +} + +static void manager_unregister(void *data) +{ + info("Unregistered manager path"); + + if (devices) { + g_slist_foreach(devices, (GFunc) remove_device, NULL); + g_slist_free(devices); + devices = NULL; + } +} + +static sdp_record_t *hsp_ag_record(uint8_t ch) +{ + sdp_list_t *svclass_id, *pfseq, *apseq, *root; + uuid_t root_uuid, svclass_uuid, ga_svclass_uuid; + uuid_t l2cap_uuid, rfcomm_uuid; + sdp_profile_desc_t profile; + sdp_record_t *record; + sdp_list_t *aproto, *proto[2]; + sdp_data_t *channel; + + record = sdp_record_alloc(); + if (!record) + return NULL; + + sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); + root = sdp_list_append(0, &root_uuid); + sdp_set_browse_groups(record, root); + + sdp_uuid16_create(&svclass_uuid, HEADSET_AGW_SVCLASS_ID); + svclass_id = sdp_list_append(0, &svclass_uuid); + sdp_uuid16_create(&ga_svclass_uuid, GENERIC_AUDIO_SVCLASS_ID); + svclass_id = sdp_list_append(svclass_id, &ga_svclass_uuid); + sdp_set_service_classes(record, svclass_id); + + sdp_uuid16_create(&profile.uuid, HEADSET_PROFILE_ID); + profile.version = 0x0100; + pfseq = sdp_list_append(0, &profile); + sdp_set_profile_descs(record, pfseq); + + sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); + proto[0] = sdp_list_append(0, &l2cap_uuid); + apseq = sdp_list_append(0, proto[0]); + + sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID); + proto[1] = sdp_list_append(0, &rfcomm_uuid); + channel = sdp_data_alloc(SDP_UINT8, &ch); + proto[1] = sdp_list_append(proto[1], channel); + apseq = sdp_list_append(apseq, proto[1]); + + aproto = sdp_list_append(0, apseq); + sdp_set_access_protos(record, aproto); + + sdp_set_info_attr(record, "Headset Audio Gateway", 0, 0); + + sdp_data_free(channel); + sdp_list_free(proto[0], 0); + sdp_list_free(proto[1], 0); + sdp_list_free(apseq, 0); + sdp_list_free(pfseq, 0); + sdp_list_free(aproto, 0); + sdp_list_free(root, 0); + sdp_list_free(svclass_id, 0); + + return record; +} + +static sdp_record_t *hsp_hs_record(uint8_t ch) +{ + sdp_list_t *svclass_id, *pfseq, *apseq, *root; + uuid_t root_uuid, svclass_uuid, ga_svclass_uuid; + uuid_t l2cap_uuid, rfcomm_uuid; + sdp_profile_desc_t profile; + sdp_record_t *record; + sdp_list_t *aproto, *proto[2]; + sdp_data_t *channel; + + record = sdp_record_alloc(); + if (!record) + return NULL; + + sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); + root = sdp_list_append(0, &root_uuid); + sdp_set_browse_groups(record, root); + + sdp_uuid16_create(&svclass_uuid, HEADSET_SVCLASS_ID); + svclass_id = sdp_list_append(0, &svclass_uuid); + sdp_uuid16_create(&ga_svclass_uuid, GENERIC_AUDIO_SVCLASS_ID); + svclass_id = sdp_list_append(svclass_id, &ga_svclass_uuid); + sdp_set_service_classes(record, svclass_id); + + sdp_uuid16_create(&profile.uuid, HEADSET_PROFILE_ID); + profile.version = 0x0100; + pfseq = sdp_list_append(0, &profile); + sdp_set_profile_descs(record, pfseq); + + sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); + proto[0] = sdp_list_append(0, &l2cap_uuid); + apseq = sdp_list_append(0, proto[0]); + + sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID); + proto[1] = sdp_list_append(0, &rfcomm_uuid); + channel = sdp_data_alloc(SDP_UINT8, &ch); + proto[1] = sdp_list_append(proto[1], channel); + apseq = sdp_list_append(apseq, proto[1]); + + aproto = sdp_list_append(0, apseq); + sdp_set_access_protos(record, aproto); + + sdp_set_info_attr(record, "Headset", 0, 0); + + sdp_data_free(channel); + sdp_list_free(proto[0], 0); + sdp_list_free(proto[1], 0); + sdp_list_free(apseq, 0); + sdp_list_free(pfseq, 0); + sdp_list_free(aproto, 0); + sdp_list_free(root, 0); + sdp_list_free(svclass_id, 0); + + return record; +} + +static sdp_record_t *hfp_ag_record(uint8_t ch, uint32_t feat) +{ + sdp_list_t *svclass_id, *pfseq, *apseq, *root; + uuid_t root_uuid, svclass_uuid, ga_svclass_uuid; + uuid_t l2cap_uuid, rfcomm_uuid; + sdp_profile_desc_t profile; + sdp_list_t *aproto, *proto[2]; + sdp_record_t *record; + sdp_data_t *channel, *features; + uint8_t netid = 0x01; + uint16_t sdpfeat; + sdp_data_t *network = sdp_data_alloc(SDP_UINT8, &netid); + + record = sdp_record_alloc(); + if (!record) + return NULL; + + sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); + root = sdp_list_append(0, &root_uuid); + sdp_set_browse_groups(record, root); + + sdp_uuid16_create(&svclass_uuid, HANDSFREE_AGW_SVCLASS_ID); + svclass_id = sdp_list_append(0, &svclass_uuid); + sdp_uuid16_create(&ga_svclass_uuid, GENERIC_AUDIO_SVCLASS_ID); + svclass_id = sdp_list_append(svclass_id, &ga_svclass_uuid); + sdp_set_service_classes(record, svclass_id); + + sdp_uuid16_create(&profile.uuid, HANDSFREE_PROFILE_ID); + profile.version = 0x0105; + pfseq = sdp_list_append(0, &profile); + sdp_set_profile_descs(record, pfseq); + + sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); + proto[0] = sdp_list_append(0, &l2cap_uuid); + apseq = sdp_list_append(0, proto[0]); + + sdp_uuid16_create(&rfcomm_uuid, RFCOMM_UUID); + proto[1] = sdp_list_append(0, &rfcomm_uuid); + channel = sdp_data_alloc(SDP_UINT8, &ch); + proto[1] = sdp_list_append(proto[1], channel); + apseq = sdp_list_append(apseq, proto[1]); + + sdpfeat = (uint16_t) feat & 0xF; + features = sdp_data_alloc(SDP_UINT16, &sdpfeat); + sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features); + + aproto = sdp_list_append(0, apseq); + sdp_set_access_protos(record, aproto); + + sdp_set_info_attr(record, "Hands-Free Audio Gateway", 0, 0); + + sdp_attr_add(record, SDP_ATTR_EXTERNAL_NETWORK, network); + + sdp_data_free(channel); + sdp_list_free(proto[0], 0); + sdp_list_free(proto[1], 0); + sdp_list_free(apseq, 0); + sdp_list_free(pfseq, 0); + sdp_list_free(aproto, 0); + sdp_list_free(root, 0); + sdp_list_free(svclass_id, 0); + + return record; +} + +static void auth_cb(DBusError *derr, void *user_data) +{ + struct audio_device *device = user_data; + const char *uuid; + + if (get_hfp_active(device)) + uuid = HFP_AG_UUID; + else + uuid = HSP_AG_UUID; + + if (derr && dbus_error_is_set(derr)) { + error("Access denied: %s", derr->message); + if (dbus_error_has_name(derr, DBUS_ERROR_NO_REPLY)) { + debug("Canceling authorization request"); + service_cancel_auth(&device->src, &device->dst); + } + + headset_set_state(device, HEADSET_STATE_DISCONNECTED); + } else { + char hs_address[18]; + + headset_set_authorized(device); + + ba2str(&device->dst, hs_address); + + debug("Accepted headset connection from %s for %s", + hs_address, device->path); + headset_set_state(device, HEADSET_STATE_CONNECTED); + } +} + +static void ag_io_cb(GIOChannel *chan, int err, const bdaddr_t *src, + const bdaddr_t *dst, gpointer data) +{ + const char *uuid; + struct audio_device *device; + gboolean hfp_active; + + if (err < 0) { + error("accept: %s (%d)", strerror(-err), -err); + return; + } + + if (chan == hsp_ag_server) { + hfp_active = FALSE; + uuid = HSP_AG_UUID; + } else { + hfp_active = TRUE; + uuid = HFP_AG_UUID; + } + + device = manager_device_connected(dst, uuid); + if (!device) + goto drop; + + if (headset_get_state(device) > HEADSET_STATE_DISCONNECTED) { + debug("Refusing new connection since one already exists"); + goto drop; + } + + set_hfp_active(device, hfp_active); + + if (headset_connect_rfcomm(device, chan) < 0) { + error("Allocating new GIOChannel failed!"); + goto drop; + } + + err = service_req_auth(&device->src, &device->dst, uuid, auth_cb, + device); + if (err < 0) { + debug("Authorization denied: %s", strerror(-err)); + headset_close_rfcomm(device); + return; + } + + headset_set_state(device, HEADSET_STATE_CONNECT_IN_PROGRESS); + + return; + +drop: + g_io_channel_close(chan); + g_io_channel_unref(chan); + return; +} + +static void hs_io_cb(GIOChannel *chan, int err, const bdaddr_t *src, + const bdaddr_t *dst, void *data) +{ + /*Stub*/ + return; +} + +static int headset_server_init(DBusConnection *conn, GKeyFile *config) +{ + uint8_t chan = DEFAULT_HS_AG_CHANNEL; + sdp_record_t *record; + gboolean hfp = TRUE, master = TRUE; + GError *err = NULL; + uint32_t features, flags; + + if (!enabled.headset) + return 0; + + if (config) { + gboolean tmp; + + tmp = g_key_file_get_boolean(config, "General", "Master", + &err); + if (err) { + debug("audio.conf: %s", err->message); + g_error_free(err); + err = NULL; + } else + master = tmp; + + tmp = g_key_file_get_boolean(config, "Headset", "HFP", + &err); + if (err) { + debug("audio.conf: %s", err->message); + g_error_free(err); + err = NULL; + } else + hfp = tmp; + } + + flags = RFCOMM_LM_SECURE; + + if (master) + flags |= RFCOMM_LM_MASTER; + + hsp_ag_server = bt_rfcomm_listen(BDADDR_ANY, chan, flags, ag_io_cb, + NULL); + if (!hsp_ag_server) + return -1; + + record = hsp_ag_record(chan); + if (!record) { + error("Unable to allocate new service record"); + return -1; + } + + if (add_record_to_server(BDADDR_ANY, record) < 0) { + error("Unable to register HS AG service record"); + sdp_record_free(record); + g_io_channel_unref(hsp_ag_server); + hsp_ag_server = NULL; + return -1; + } + hsp_ag_record_id = record->handle; + + features = headset_config_init(config); + + if (!hfp) + return 0; + + chan = DEFAULT_HF_AG_CHANNEL; + + hfp_ag_server = bt_rfcomm_listen(BDADDR_ANY, chan, flags, ag_io_cb, + NULL); + if (!hfp_ag_server) + return -1; + + record = hfp_ag_record(chan, features); + if (!record) { + error("Unable to allocate new service record"); + return -1; + } + + if (add_record_to_server(BDADDR_ANY, record) < 0) { + error("Unable to register HF AG service record"); + sdp_record_free(record); + g_io_channel_unref(hfp_ag_server); + hfp_ag_server = NULL; + return -1; + } + hfp_ag_record_id = record->handle; + + return 0; +} + +static int gateway_server_init(DBusConnection *conn, GKeyFile *config) +{ + uint8_t chan = DEFAULT_HSP_HS_CHANNEL; + sdp_record_t *record; + gboolean master = TRUE; + GError *err = NULL; + uint32_t flags; + + if (!enabled.gateway) + return 0; + + if (config) { + gboolean tmp; + + tmp = g_key_file_get_boolean(config, "General", "Master", + &err); + if (err) { + debug("audio.conf: %s", err->message); + g_error_free(err); + err = NULL; + } else + master = tmp; + } + + flags = RFCOMM_LM_SECURE; + + if (master) + flags |= RFCOMM_LM_MASTER; + + hsp_hs_server = bt_rfcomm_listen(BDADDR_ANY, chan, flags, hs_io_cb, + NULL); + if (!hsp_hs_server) + return -1; + + record = hsp_hs_record(chan); + if (!record) { + error("Unable to allocate new service record"); + return -1; + } + + if (add_record_to_server(BDADDR_ANY, record) < 0) { + error("Unable to register HSP HS service record"); + sdp_record_free(record); + g_io_channel_unref(hsp_hs_server); + hsp_hs_server = NULL; + return -1; + } + + hsp_hs_record_id = record->handle; + + return 0; +} + +static void server_exit(void) +{ + if (hsp_ag_record_id) { + remove_record_from_server(hsp_ag_record_id); + hsp_ag_record_id = 0; + } + + if (hsp_ag_server) { + g_io_channel_unref(hsp_ag_server); + hsp_ag_server = NULL; + } + + if (hsp_hs_record_id) { + remove_record_from_server(hsp_hs_record_id); + hsp_hs_record_id = 0; + } + + if (hsp_hs_server) { + g_io_channel_unref(hsp_hs_server); + hsp_hs_server = NULL; + } + + if (hfp_ag_record_id) { + remove_record_from_server(hfp_ag_record_id); + hfp_ag_record_id = 0; + } + + if (hfp_ag_server) { + g_io_channel_unref(hfp_ag_server); + hfp_ag_server = NULL; + } +} + +int audio_manager_init(DBusConnection *conn, GKeyFile *config) +{ + char **list; + int i; + + connection = dbus_connection_ref(conn); + + if (!config) + goto proceed; + + list = g_key_file_get_string_list(config, "General", "Enable", + NULL, NULL); + for (i = 0; list && list[i] != NULL; i++) { + if (g_str_equal(list[i], "Headset")) + enabled.headset = TRUE; + else if (g_str_equal(list[i], "Gateway")) + enabled.gateway = TRUE; + else if (g_str_equal(list[i], "Sink")) + enabled.sink = TRUE; + else if (g_str_equal(list[i], "Source")) + enabled.source = TRUE; + else if (g_str_equal(list[i], "Control")) + enabled.control = TRUE; + } + g_strfreev(list); + + list = g_key_file_get_string_list(config, "General", "Disable", + NULL, NULL); + for (i = 0; list && list[i] != NULL; i++) { + if (g_str_equal(list[i], "Headset")) + enabled.headset = FALSE; + else if (g_str_equal(list[i], "Gateway")) + enabled.gateway = FALSE; + else if (g_str_equal(list[i], "Sink")) + enabled.sink = FALSE; + else if (g_str_equal(list[i], "Source")) + enabled.source = FALSE; + else if (g_str_equal(list[i], "Control")) + enabled.control = FALSE; + } + g_strfreev(list); + +proceed: + if (enabled.headset) { + if (headset_server_init(conn, config) < 0) + goto failed; + } + + if (enabled.gateway) { + if (gateway_server_init(conn, config) < 0) + goto failed; + } + + if (enabled.source || enabled.sink) { + if (a2dp_init(conn, config) < 0) + goto failed; + } + + if (enabled.control && avrcp_init(conn, config) < 0) + goto failed; + + if (!g_dbus_register_interface(conn, AUDIO_MANAGER_PATH, + AUDIO_MANAGER_INTERFACE, + manager_methods, manager_signals, + NULL, NULL, manager_unregister)) { + error("Failed to register %s interface to %s", + AUDIO_MANAGER_INTERFACE, AUDIO_MANAGER_PATH); + goto failed; + } + + info("Registered manager path:%s", AUDIO_MANAGER_PATH); + + register_stored(); + + return 0; +failed: + audio_manager_exit(); + return -1; +} + +void audio_manager_exit(void) +{ + server_exit(); + + g_dbus_unregister_interface(connection, AUDIO_MANAGER_PATH, + AUDIO_MANAGER_INTERFACE); + + dbus_connection_unref(connection); + + connection = NULL; +} + +struct audio_device *manager_default_device(void) +{ + return default_dev; +} + +struct audio_device *manager_get_connected_device(void) +{ + GSList *l; + + for (l = devices; l != NULL; l = g_slist_next(l)) { + struct audio_device *device = l->data; + + if ((device->sink || device->source) && + avdtp_is_connected(&device->src, &device->dst)) + return device; + + if (device->headset && headset_is_active(device)) + return device; + } + + return NULL; +} + +struct audio_device *manager_find_device(const bdaddr_t *bda, const char *interface, + gboolean connected) +{ + GSList *l; + + if (!bacmp(bda, BDADDR_ANY) && !interface && !connected) + return default_dev; + + for (l = devices; l != NULL; l = l->next) { + struct audio_device *dev = l->data; + + if (bacmp(bda, BDADDR_ANY) && bacmp(&dev->dst, bda)) + continue; + + if (interface && !strcmp(AUDIO_HEADSET_INTERFACE, interface) + && !dev->headset) + continue; + + if (interface && !strcmp(AUDIO_SINK_INTERFACE, interface) + && !dev->sink) + continue; + + if (interface && !strcmp(AUDIO_SOURCE_INTERFACE, interface) + && !dev->source) + continue; + + if (interface && !strcmp(AUDIO_CONTROL_INTERFACE, interface) + && !dev->control) + continue; + + if (connected && !device_is_connected(dev, interface)) + continue; + + return dev; + } + + return NULL; +} |