| /* |
| * |
| * oFono - Open Source Telephony |
| * |
| * Copyright (C) 2008-2011 Intel Corporation. All rights reserved. |
| * Copyright (C) 2010 ProFUSION embedded systems |
| * Copyright (C) 2010 Gustavo F. Padovan <gustavo@padovan.org> |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 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 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 <string.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <errno.h> |
| #include <unistd.h> |
| #include <glib.h> |
| #include <gdbus.h> |
| |
| #define OFONO_API_SUBJECT_TO_CHANGE |
| #include <ofono/plugin.h> |
| #include <ofono/log.h> |
| |
| #include <btio.h> |
| #include "bluez4.h" |
| |
| static DBusConnection *connection; |
| static GHashTable *uuid_hash = NULL; |
| static GHashTable *adapter_address_hash = NULL; |
| static gint bluetooth_refcount; |
| static GSList *server_list = NULL; |
| static const char *adapter_any_name = "any"; |
| static char *adapter_any_path; |
| |
| #define TIMEOUT 60 /* Timeout for user response (seconds) */ |
| |
| struct server { |
| guint8 channel; |
| char *sdp_record; |
| guint32 handle; |
| GIOChannel *io; |
| ConnectFunc connect_cb; |
| gpointer user_data; |
| }; |
| |
| struct cb_data { |
| struct server *server; |
| char *path; |
| guint source; |
| GIOChannel *io; |
| }; |
| |
| void bluetooth_create_path(const char *dev_addr, const char *adapter_addr, |
| char *buf, int size) |
| { |
| int i, j; |
| |
| for (i = 0, j = 0; adapter_addr[j] && i < size - 1; j++) |
| if (adapter_addr[j] >= '0' && adapter_addr[j] <= '9') |
| buf[i++] = adapter_addr[j]; |
| else if (adapter_addr[j] >= 'A' && adapter_addr[j] <= 'F') |
| buf[i++] = adapter_addr[j]; |
| |
| if (i < size - 1) |
| buf[i++] = '_'; |
| |
| for (j = 0; dev_addr[j] && i < size - 1; j++) |
| if (dev_addr[j] >= '0' && dev_addr[j] <= '9') |
| buf[i++] = dev_addr[j]; |
| else if (dev_addr[j] >= 'A' && dev_addr[j] <= 'F') |
| buf[i++] = dev_addr[j]; |
| |
| buf[i] = '\0'; |
| } |
| |
| int bluetooth_send_with_reply(const char *path, const char *interface, |
| const char *method, DBusPendingCall **call, |
| DBusPendingCallNotifyFunction cb, |
| void *user_data, DBusFreeFunction free_func, |
| int timeout, int type, ...) |
| { |
| DBusMessage *msg; |
| DBusPendingCall *c; |
| va_list args; |
| int err; |
| |
| msg = dbus_message_new_method_call(BLUEZ_SERVICE, path, |
| interface, method); |
| if (msg == NULL) { |
| ofono_error("Unable to allocate new D-Bus %s message", method); |
| err = -ENOMEM; |
| goto fail; |
| } |
| |
| va_start(args, type); |
| |
| if (!dbus_message_append_args_valist(msg, type, args)) { |
| va_end(args); |
| err = -EIO; |
| goto fail; |
| } |
| |
| va_end(args); |
| |
| if (timeout > 0) |
| timeout *= 1000; |
| |
| if (!dbus_connection_send_with_reply(connection, msg, &c, timeout)) { |
| ofono_error("Sending %s failed", method); |
| err = -EIO; |
| goto fail; |
| } |
| |
| if (call != NULL) |
| *call = c; |
| |
| dbus_pending_call_set_notify(c, cb, user_data, free_func); |
| dbus_pending_call_unref(c); |
| |
| dbus_message_unref(msg); |
| |
| return 0; |
| |
| fail: |
| if (free_func && user_data) |
| free_func(user_data); |
| |
| if (msg) |
| dbus_message_unref(msg); |
| |
| return err; |
| } |
| |
| typedef void (*PropertyHandler)(DBusMessageIter *iter, gpointer user_data); |
| |
| struct property_handler { |
| const char *property; |
| PropertyHandler callback; |
| gpointer user_data; |
| }; |
| |
| static gint property_handler_compare(gconstpointer a, gconstpointer b) |
| { |
| const struct property_handler *handler = a; |
| const char *property = b; |
| |
| return strcmp(handler->property, property); |
| } |
| |
| void bluetooth_parse_properties(DBusMessage *reply, const char *property, ...) |
| { |
| va_list args; |
| GSList *prop_handlers = NULL; |
| DBusMessageIter array, dict; |
| |
| va_start(args, property); |
| |
| while (property != NULL) { |
| struct property_handler *handler = |
| g_new0(struct property_handler, 1); |
| |
| handler->property = property; |
| handler->callback = va_arg(args, PropertyHandler); |
| handler->user_data = va_arg(args, gpointer); |
| |
| property = va_arg(args, const char *); |
| |
| prop_handlers = g_slist_prepend(prop_handlers, handler); |
| } |
| |
| va_end(args); |
| |
| if (dbus_message_iter_init(reply, &array) == FALSE) |
| goto done; |
| |
| if (dbus_message_iter_get_arg_type(&array) != DBUS_TYPE_ARRAY) |
| goto done; |
| |
| dbus_message_iter_recurse(&array, &dict); |
| |
| while (dbus_message_iter_get_arg_type(&dict) == DBUS_TYPE_DICT_ENTRY) { |
| DBusMessageIter entry, value; |
| const char *key; |
| GSList *l; |
| |
| dbus_message_iter_recurse(&dict, &entry); |
| |
| if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_STRING) |
| goto done; |
| |
| dbus_message_iter_get_basic(&entry, &key); |
| |
| dbus_message_iter_next(&entry); |
| |
| if (dbus_message_iter_get_arg_type(&entry) != DBUS_TYPE_VARIANT) |
| goto done; |
| |
| dbus_message_iter_recurse(&entry, &value); |
| |
| l = g_slist_find_custom(prop_handlers, key, |
| property_handler_compare); |
| |
| if (l) { |
| struct property_handler *handler = l->data; |
| |
| handler->callback(&value, handler->user_data); |
| } |
| |
| dbus_message_iter_next(&dict); |
| } |
| |
| done: |
| g_slist_free_full(prop_handlers, g_free); |
| } |
| |
| static void parse_uuids(DBusMessageIter *array, gpointer user_data) |
| { |
| GSList **uuids = user_data; |
| DBusMessageIter value; |
| |
| if (dbus_message_iter_get_arg_type(array) != DBUS_TYPE_ARRAY) |
| return; |
| |
| dbus_message_iter_recurse(array, &value); |
| |
| while (dbus_message_iter_get_arg_type(&value) == DBUS_TYPE_STRING) { |
| const char *uuid; |
| |
| dbus_message_iter_get_basic(&value, &uuid); |
| |
| *uuids = g_slist_prepend(*uuids, (char *) uuid); |
| |
| dbus_message_iter_next(&value); |
| } |
| } |
| |
| static void parse_string(DBusMessageIter *iter, gpointer user_data) |
| { |
| char **str = user_data; |
| int arg_type = dbus_message_iter_get_arg_type(iter); |
| |
| if (arg_type != DBUS_TYPE_OBJECT_PATH && arg_type != DBUS_TYPE_STRING) |
| return; |
| |
| dbus_message_iter_get_basic(iter, str); |
| } |
| |
| static void bluetooth_probe(GSList *uuids, const char *path, |
| const char *device, const char *adapter, |
| const char *alias) |
| { |
| for (; uuids; uuids = uuids->next) { |
| struct bluetooth_profile *driver; |
| const char *uuid = uuids->data; |
| int err; |
| |
| driver = g_hash_table_lookup(uuid_hash, uuid); |
| if (driver == NULL) |
| continue; |
| |
| err = driver->probe(path, device, adapter, alias); |
| if (err == 0 || err == -EALREADY) |
| continue; |
| |
| ofono_error("%s probe: %s (%d)", driver->name, strerror(-err), |
| -err); |
| } |
| } |
| |
| static void device_properties_cb(DBusPendingCall *call, gpointer user_data) |
| { |
| DBusMessage *reply; |
| const char *path = user_data; |
| const char *adapter = NULL; |
| const char *adapter_addr = NULL; |
| const char *device_addr = NULL; |
| const char *alias = NULL; |
| struct DBusError derr; |
| GSList *uuids = NULL; |
| |
| reply = dbus_pending_call_steal_reply(call); |
| |
| dbus_error_init(&derr); |
| |
| if (dbus_set_error_from_message(&derr, reply)) { |
| ofono_error("Device.GetProperties replied an error: %s, %s", |
| derr.name, derr.message); |
| dbus_error_free(&derr); |
| goto done; |
| } |
| |
| DBG(""); |
| |
| bluetooth_parse_properties(reply, "UUIDs", parse_uuids, &uuids, |
| "Adapter", parse_string, &adapter, |
| "Address", parse_string, &device_addr, |
| "Alias", parse_string, &alias, NULL); |
| |
| if (adapter) |
| adapter_addr = g_hash_table_lookup(adapter_address_hash, |
| adapter); |
| |
| if (!device_addr || !adapter_addr) |
| goto done; |
| |
| bluetooth_probe(uuids, path, device_addr, adapter_addr, alias); |
| |
| done: |
| g_slist_free(uuids); |
| dbus_message_unref(reply); |
| } |
| |
| static void parse_devices(DBusMessageIter *array, gpointer user_data) |
| { |
| DBusMessageIter value; |
| GSList **device_list = user_data; |
| |
| DBG(""); |
| |
| if (dbus_message_iter_get_arg_type(array) != DBUS_TYPE_ARRAY) |
| return; |
| |
| dbus_message_iter_recurse(array, &value); |
| |
| while (dbus_message_iter_get_arg_type(&value) |
| == DBUS_TYPE_OBJECT_PATH) { |
| const char *path; |
| |
| dbus_message_iter_get_basic(&value, &path); |
| |
| *device_list = g_slist_prepend(*device_list, (gpointer) path); |
| |
| dbus_message_iter_next(&value); |
| } |
| } |
| |
| static gboolean property_changed(DBusConnection *conn, DBusMessage *msg, |
| void *user_data) |
| { |
| const char *property; |
| DBusMessageIter iter; |
| |
| dbus_message_iter_init(msg, &iter); |
| |
| if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_STRING) |
| return FALSE; |
| |
| dbus_message_iter_get_basic(&iter, &property); |
| if (g_str_equal(property, "UUIDs") == TRUE) { |
| GSList *uuids = NULL; |
| const char *path = dbus_message_get_path(msg); |
| DBusMessageIter variant; |
| |
| if (!dbus_message_iter_next(&iter)) |
| return FALSE; |
| |
| if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) |
| return FALSE; |
| |
| dbus_message_iter_recurse(&iter, &variant); |
| |
| parse_uuids(&variant, &uuids); |
| |
| /* We need the full set of properties to be able to create |
| * the modem properly, including Adapter and Alias, so |
| * refetch everything again |
| */ |
| if (uuids) |
| bluetooth_send_with_reply(path, BLUEZ_DEVICE_INTERFACE, |
| "GetProperties", NULL, |
| device_properties_cb, g_strdup(path), |
| g_free, -1, DBUS_TYPE_INVALID); |
| } else if (g_str_equal(property, "Alias") == TRUE) { |
| const char *path = dbus_message_get_path(msg); |
| struct bluetooth_profile *profile; |
| const char *alias = NULL; |
| DBusMessageIter variant; |
| GHashTableIter hash_iter; |
| gpointer key, value; |
| |
| if (!dbus_message_iter_next(&iter)) |
| return FALSE; |
| |
| if (dbus_message_iter_get_arg_type(&iter) != DBUS_TYPE_VARIANT) |
| return FALSE; |
| |
| dbus_message_iter_recurse(&iter, &variant); |
| |
| parse_string(&variant, &alias); |
| |
| g_hash_table_iter_init(&hash_iter, uuid_hash); |
| while (g_hash_table_iter_next(&hash_iter, &key, &value)) { |
| profile = value; |
| if (profile->set_alias) |
| profile->set_alias(path, alias); |
| } |
| } |
| |
| return TRUE; |
| } |
| |
| static void adapter_properties_cb(DBusPendingCall *call, gpointer user_data) |
| { |
| const char *path = user_data; |
| DBusMessage *reply; |
| DBusError derr; |
| GSList *device_list = NULL; |
| GSList *l; |
| const char *addr; |
| |
| reply = dbus_pending_call_steal_reply(call); |
| |
| dbus_error_init(&derr); |
| |
| if (dbus_set_error_from_message(&derr, reply)) { |
| ofono_error("Adapter.GetProperties replied an error: %s, %s", |
| derr.name, derr.message); |
| dbus_error_free(&derr); |
| goto done; |
| } |
| |
| DBG(""); |
| |
| bluetooth_parse_properties(reply, |
| "Devices", parse_devices, &device_list, |
| "Address", parse_string, &addr, |
| NULL); |
| |
| DBG("Adapter Address: %s, Path: %s", addr, path); |
| g_hash_table_insert(adapter_address_hash, |
| g_strdup(path), g_strdup(addr)); |
| |
| for (l = device_list; l; l = l->next) { |
| const char *device = l->data; |
| |
| bluetooth_send_with_reply(device, BLUEZ_DEVICE_INTERFACE, |
| "GetProperties", NULL, |
| device_properties_cb, g_strdup(device), |
| g_free, -1, DBUS_TYPE_INVALID); |
| } |
| |
| done: |
| g_slist_free(device_list); |
| dbus_message_unref(reply); |
| } |
| |
| static void get_adapter_properties(const char *path, const char *handle, |
| gpointer user_data) |
| { |
| bluetooth_send_with_reply(path, BLUEZ_ADAPTER_INTERFACE, |
| "GetProperties", NULL, adapter_properties_cb, |
| g_strdup(path), g_free, -1, DBUS_TYPE_INVALID); |
| } |
| |
| static void remove_record(struct server *server) |
| { |
| DBusMessage *msg; |
| |
| if (server->handle == 0) |
| return; |
| |
| msg = dbus_message_new_method_call(BLUEZ_SERVICE, adapter_any_path, |
| BLUEZ_SERVICE_INTERFACE, |
| "RemoveRecord"); |
| if (msg == NULL) { |
| ofono_error("Unable to allocate D-Bus RemoveRecord message"); |
| return; |
| } |
| |
| dbus_message_append_args(msg, DBUS_TYPE_UINT32, &server->handle, |
| DBUS_TYPE_INVALID); |
| g_dbus_send_message(connection, msg); |
| |
| ofono_info("Unregistered handle for channel %d: 0x%x", |
| server->channel, server->handle); |
| } |
| |
| static void cb_data_destroy(gpointer data) |
| { |
| struct cb_data *cb_data = data; |
| |
| if (cb_data->source != 0) |
| g_source_remove(cb_data->source); |
| |
| g_free(cb_data->path); |
| g_free(cb_data); |
| } |
| |
| static void cancel_authorization(struct cb_data *user_data) |
| { |
| DBusMessage *msg; |
| |
| msg = dbus_message_new_method_call(BLUEZ_SERVICE, user_data->path, |
| BLUEZ_SERVICE_INTERFACE, |
| "CancelAuthorization"); |
| |
| if (msg == NULL) { |
| ofono_error("Unable to allocate D-Bus CancelAuthorization" |
| " message"); |
| return; |
| } |
| |
| g_dbus_send_message(connection, msg); |
| } |
| |
| static gboolean client_event(GIOChannel *chan, GIOCondition cond, gpointer data) |
| { |
| struct cb_data *cb_data = data; |
| |
| cancel_authorization(cb_data); |
| cb_data->source = 0; |
| |
| return FALSE; |
| } |
| |
| static void auth_cb(DBusPendingCall *call, gpointer user_data) |
| { |
| struct cb_data *cb_data = user_data; |
| struct server *server = cb_data->server; |
| DBusMessage *reply = dbus_pending_call_steal_reply(call); |
| DBusError derr; |
| GError *err = NULL; |
| |
| dbus_error_init(&derr); |
| |
| if (dbus_set_error_from_message(&derr, reply)) { |
| ofono_error("RequestAuthorization error: %s, %s", |
| derr.name, derr.message); |
| |
| if (dbus_error_has_name(&derr, DBUS_ERROR_NO_REPLY)) |
| cancel_authorization(cb_data); |
| |
| dbus_error_free(&derr); |
| } else { |
| ofono_info("RequestAuthorization succeeded"); |
| |
| if (!bt_io_accept(cb_data->io, server->connect_cb, |
| server->user_data, NULL, &err)) { |
| ofono_error("%s", err->message); |
| g_error_free(err); |
| } |
| } |
| |
| dbus_message_unref(reply); |
| } |
| |
| static void new_connection(GIOChannel *io, gpointer user_data) |
| { |
| struct server *server = user_data; |
| struct cb_data *cbd; |
| const char *addr; |
| GError *err = NULL; |
| char laddress[18], raddress[18]; |
| guint8 channel; |
| GHashTableIter iter; |
| gpointer key, value; |
| const char *path; |
| |
| bt_io_get(io, BT_IO_RFCOMM, &err, BT_IO_OPT_SOURCE, laddress, |
| BT_IO_OPT_DEST, raddress, |
| BT_IO_OPT_CHANNEL, &channel, |
| BT_IO_OPT_INVALID); |
| if (err) { |
| ofono_error("%s", err->message); |
| g_error_free(err); |
| return; |
| } |
| |
| ofono_info("New connection for %s on channel %u from: %s,", laddress, |
| channel, raddress); |
| |
| path = NULL; |
| g_hash_table_iter_init(&iter, adapter_address_hash); |
| |
| while (g_hash_table_iter_next(&iter, &key, &value)) { |
| if (g_str_equal(laddress, value) == TRUE) { |
| path = key; |
| break; |
| } |
| } |
| |
| if (path == NULL) |
| return; |
| |
| cbd = g_try_new0(struct cb_data, 1); |
| if (cbd == NULL) { |
| ofono_error("Unable to allocate client cb_data structure"); |
| return; |
| } |
| |
| cbd->path = g_strdup(path); |
| cbd->server = server; |
| cbd->io = io; |
| |
| addr = raddress; |
| |
| if (bluetooth_send_with_reply(path, BLUEZ_SERVICE_INTERFACE, |
| "RequestAuthorization", NULL, |
| auth_cb, cbd, cb_data_destroy, |
| TIMEOUT, DBUS_TYPE_STRING, &addr, |
| DBUS_TYPE_UINT32, &server->handle, |
| DBUS_TYPE_INVALID) < 0) { |
| ofono_error("Request Bluetooth authorization failed"); |
| return; |
| } |
| |
| ofono_info("RequestAuthorization(%s, 0x%x)", raddress, server->handle); |
| |
| cbd->source = g_io_add_watch(io, G_IO_HUP | G_IO_ERR | G_IO_NVAL, |
| client_event, cbd); |
| } |
| |
| static void remove_service_handle(gpointer data, gpointer user_data) |
| { |
| struct server *server = data; |
| |
| server->handle = 0; |
| } |
| |
| static void add_record_cb(DBusPendingCall *call, gpointer user_data) |
| { |
| struct server *server = user_data; |
| DBusMessage *reply = dbus_pending_call_steal_reply(call); |
| DBusError derr; |
| |
| dbus_error_init(&derr); |
| |
| if (dbus_set_error_from_message(&derr, reply)) { |
| ofono_error("Replied with an error: %s, %s", |
| derr.name, derr.message); |
| dbus_error_free(&derr); |
| goto done; |
| } |
| |
| dbus_message_get_args(reply, NULL, DBUS_TYPE_UINT32, &server->handle, |
| DBUS_TYPE_INVALID); |
| |
| ofono_info("Registered handle for channel %d: 0x%x", |
| server->channel, server->handle); |
| |
| done: |
| dbus_message_unref(reply); |
| } |
| |
| static void add_record(gpointer data, gpointer user_data) |
| { |
| struct server *server = data; |
| |
| if (server->sdp_record == NULL) |
| return; |
| |
| bluetooth_send_with_reply(adapter_any_path, |
| BLUEZ_SERVICE_INTERFACE, "AddRecord", |
| NULL, add_record_cb, server, NULL, -1, |
| DBUS_TYPE_STRING, &server->sdp_record, |
| DBUS_TYPE_INVALID); |
| } |
| |
| static void find_adapter_cb(DBusPendingCall *call, gpointer user_data) |
| { |
| DBusMessage *reply = dbus_pending_call_steal_reply(call); |
| DBusError derr; |
| const char *path; |
| |
| dbus_error_init(&derr); |
| |
| if (dbus_set_error_from_message(&derr, reply)) { |
| ofono_error("Replied with an error: %s, %s", |
| derr.name, derr.message); |
| dbus_error_free(&derr); |
| goto done; |
| } |
| |
| dbus_message_get_args(reply, NULL, DBUS_TYPE_OBJECT_PATH, &path, |
| DBUS_TYPE_INVALID); |
| |
| adapter_any_path = g_strdup(path); |
| |
| g_slist_foreach(server_list, add_record, NULL); |
| |
| done: |
| dbus_message_unref(reply); |
| } |
| |
| static gboolean adapter_added(DBusConnection *conn, DBusMessage *message, |
| void *user_data) |
| { |
| const char *path; |
| |
| dbus_message_get_args(message, NULL, DBUS_TYPE_OBJECT_PATH, &path, |
| DBUS_TYPE_INVALID); |
| |
| bluetooth_send_with_reply(path, BLUEZ_ADAPTER_INTERFACE, |
| "GetProperties", NULL, adapter_properties_cb, |
| g_strdup(path), g_free, -1, DBUS_TYPE_INVALID); |
| |
| return TRUE; |
| } |
| |
| static void bluetooth_remove(gpointer key, gpointer value, gpointer user_data) |
| { |
| struct bluetooth_profile *profile = value; |
| |
| profile->remove(user_data); |
| } |
| |
| static gboolean adapter_removed(DBusConnection *conn, |
| DBusMessage *message, void *user_data) |
| { |
| const char *path; |
| |
| if (dbus_message_get_args(message, NULL, DBUS_TYPE_OBJECT_PATH, &path, |
| DBUS_TYPE_INVALID) == FALSE) |
| return FALSE; |
| |
| g_hash_table_foreach(uuid_hash, bluetooth_remove, (gpointer) path); |
| g_hash_table_remove(adapter_address_hash, path); |
| |
| return TRUE; |
| } |
| |
| static gboolean device_removed(DBusConnection *conn, |
| DBusMessage *message, void *user_data) |
| { |
| const char *path; |
| |
| if (dbus_message_get_args(message, NULL, DBUS_TYPE_OBJECT_PATH, &path, |
| DBUS_TYPE_INVALID) == FALSE) |
| return FALSE; |
| |
| g_hash_table_foreach(uuid_hash, bluetooth_remove, (gpointer) path); |
| |
| return TRUE; |
| } |
| |
| static void parse_adapters(DBusMessageIter *array, gpointer user_data) |
| { |
| DBusMessageIter value; |
| |
| DBG(""); |
| |
| if (dbus_message_iter_get_arg_type(array) != DBUS_TYPE_ARRAY) |
| return; |
| |
| dbus_message_iter_recurse(array, &value); |
| |
| while (dbus_message_iter_get_arg_type(&value) |
| == DBUS_TYPE_OBJECT_PATH) { |
| const char *path; |
| |
| dbus_message_iter_get_basic(&value, &path); |
| |
| DBG("Calling GetProperties on %s", path); |
| |
| bluetooth_send_with_reply(path, BLUEZ_ADAPTER_INTERFACE, |
| "GetProperties", NULL, adapter_properties_cb, |
| g_strdup(path), g_free, -1, DBUS_TYPE_INVALID); |
| |
| dbus_message_iter_next(&value); |
| } |
| } |
| |
| static void manager_properties_cb(DBusPendingCall *call, gpointer user_data) |
| { |
| DBusMessage *reply; |
| DBusError derr; |
| |
| reply = dbus_pending_call_steal_reply(call); |
| |
| dbus_error_init(&derr); |
| |
| if (dbus_set_error_from_message(&derr, reply)) { |
| ofono_error("Manager.GetProperties() replied an error: %s, %s", |
| derr.name, derr.message); |
| dbus_error_free(&derr); |
| goto done; |
| } |
| |
| DBG(""); |
| |
| bluetooth_parse_properties(reply, "Adapters", parse_adapters, NULL, |
| NULL); |
| |
| done: |
| dbus_message_unref(reply); |
| } |
| |
| static void bluetooth_connect(DBusConnection *conn, void *user_data) |
| { |
| bluetooth_send_with_reply("/", BLUEZ_MANAGER_INTERFACE, "GetProperties", |
| NULL, manager_properties_cb, NULL, NULL, -1, |
| DBUS_TYPE_INVALID); |
| |
| bluetooth_send_with_reply("/", BLUEZ_MANAGER_INTERFACE, "FindAdapter", |
| NULL, find_adapter_cb, NULL, NULL, -1, |
| DBUS_TYPE_STRING, &adapter_any_name, |
| DBUS_TYPE_INVALID); |
| } |
| |
| static void bluetooth_disconnect(DBusConnection *conn, void *user_data) |
| { |
| if (uuid_hash == NULL) |
| return; |
| |
| g_hash_table_foreach(uuid_hash, bluetooth_remove, NULL); |
| |
| g_slist_foreach(server_list, remove_service_handle, NULL); |
| } |
| |
| static guint bluetooth_watch; |
| static guint adapter_added_watch; |
| static guint adapter_removed_watch; |
| static guint device_removed_watch; |
| static guint property_watch; |
| |
| static void bluetooth_ref(void) |
| { |
| if (bluetooth_refcount > 0) |
| goto increment; |
| |
| connection = ofono_dbus_get_connection(); |
| |
| bluetooth_watch = g_dbus_add_service_watch(connection, BLUEZ_SERVICE, |
| bluetooth_connect, |
| bluetooth_disconnect, NULL, NULL); |
| |
| adapter_added_watch = g_dbus_add_signal_watch(connection, BLUEZ_SERVICE, |
| NULL, BLUEZ_MANAGER_INTERFACE, |
| "AdapterAdded", |
| adapter_added, NULL, NULL); |
| |
| adapter_removed_watch = g_dbus_add_signal_watch(connection, |
| BLUEZ_SERVICE, NULL, |
| BLUEZ_MANAGER_INTERFACE, |
| "AdapterRemoved", |
| adapter_removed, NULL, NULL); |
| |
| device_removed_watch = g_dbus_add_signal_watch(connection, |
| BLUEZ_SERVICE, NULL, |
| BLUEZ_ADAPTER_INTERFACE, |
| "DeviceRemoved", |
| device_removed, NULL, NULL); |
| |
| property_watch = g_dbus_add_signal_watch(connection, |
| BLUEZ_SERVICE, NULL, |
| BLUEZ_DEVICE_INTERFACE, |
| "PropertyChanged", |
| property_changed, NULL, NULL); |
| |
| if (bluetooth_watch == 0 || adapter_added_watch == 0 || |
| adapter_removed_watch == 0 || property_watch == 0) { |
| goto remove; |
| } |
| |
| uuid_hash = g_hash_table_new_full(g_str_hash, g_str_equal, |
| g_free, NULL); |
| |
| adapter_address_hash = g_hash_table_new_full(g_str_hash, g_str_equal, |
| g_free, g_free); |
| |
| increment: |
| g_atomic_int_inc(&bluetooth_refcount); |
| |
| return; |
| |
| remove: |
| g_dbus_remove_watch(connection, bluetooth_watch); |
| g_dbus_remove_watch(connection, adapter_added_watch); |
| g_dbus_remove_watch(connection, adapter_removed_watch); |
| g_dbus_remove_watch(connection, property_watch); |
| } |
| |
| static void bluetooth_unref(void) |
| { |
| if (g_atomic_int_dec_and_test(&bluetooth_refcount) == FALSE) |
| return; |
| |
| g_free(adapter_any_path); |
| adapter_any_path = NULL; |
| |
| g_dbus_remove_watch(connection, bluetooth_watch); |
| g_dbus_remove_watch(connection, adapter_added_watch); |
| g_dbus_remove_watch(connection, adapter_removed_watch); |
| g_dbus_remove_watch(connection, property_watch); |
| |
| g_hash_table_destroy(uuid_hash); |
| g_hash_table_destroy(adapter_address_hash); |
| } |
| |
| void bluetooth_get_properties() |
| { |
| g_hash_table_foreach(adapter_address_hash, |
| (GHFunc) get_adapter_properties, NULL); |
| } |
| |
| int bluetooth_register_uuid(const char *uuid, struct bluetooth_profile *profile) |
| { |
| bluetooth_ref(); |
| |
| g_hash_table_insert(uuid_hash, g_strdup(uuid), profile); |
| |
| g_hash_table_foreach(adapter_address_hash, |
| (GHFunc) get_adapter_properties, NULL); |
| |
| return 0; |
| } |
| |
| void bluetooth_unregister_uuid(const char *uuid) |
| { |
| g_hash_table_remove(uuid_hash, uuid); |
| |
| bluetooth_unref(); |
| } |
| |
| struct server *bluetooth_register_server(guint8 channel, const char *sdp_record, |
| ConnectFunc cb, gpointer user_data) |
| { |
| struct server *server; |
| GError *err = NULL; |
| |
| server = g_try_new0(struct server, 1); |
| if (!server) |
| return NULL; |
| |
| server->channel = channel; |
| |
| server->io = bt_io_listen(BT_IO_RFCOMM, NULL, new_connection, |
| server, NULL, &err, |
| BT_IO_OPT_CHANNEL, server->channel, |
| BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, |
| BT_IO_OPT_INVALID); |
| if (server->io == NULL) { |
| g_error_free(err); |
| g_free(server); |
| return NULL; |
| } |
| |
| bluetooth_ref(); |
| |
| if (sdp_record != NULL) |
| server->sdp_record = g_strdup(sdp_record); |
| |
| server->connect_cb = cb; |
| server->user_data = user_data; |
| |
| server_list = g_slist_prepend(server_list, server); |
| |
| if (adapter_any_path != NULL) |
| add_record(server, NULL); |
| |
| return server; |
| } |
| |
| void bluetooth_unregister_server(struct server *server) |
| { |
| server_list = g_slist_remove(server_list, server); |
| |
| remove_record(server); |
| |
| if (server->io != NULL) { |
| g_io_channel_shutdown(server->io, TRUE, NULL); |
| g_io_channel_unref(server->io); |
| server->io = NULL; |
| } |
| |
| g_free(server->sdp_record); |
| g_free(server); |
| |
| bluetooth_unref(); |
| } |
| |
| OFONO_PLUGIN_DEFINE(bluez4, "Bluetooth Utils Plugins", VERSION, |
| OFONO_PLUGIN_PRIORITY_DEFAULT, NULL, NULL) |