diff options
Diffstat (limited to 'profiles')
38 files changed, 10569 insertions, 49 deletions
diff --git a/profiles/alert/server.c b/profiles/alert/server.c new file mode 100755 index 00000000..2f6e3cde --- /dev/null +++ b/profiles/alert/server.c @@ -0,0 +1,1044 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011 Nokia Corporation + * Copyright (C) 2011 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 <stdbool.h> +#include <errno.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include <glib.h> + +#include "lib/bluetooth.h" +#include "lib/hci.h" +#include "lib/hci_lib.h" +#include "lib/sdp.h" +#include "lib/uuid.h" + +#include "gdbus/gdbus.h" + +#include "src/plugin.h" +#include "src/dbus-common.h" +#include "attrib/att.h" +#include "src/adapter.h" +#include "src/device.h" +#include "attrib/att-database.h" +#include "src/log.h" +#include "attrib/gatt-service.h" +#include "attrib/gattrib.h" +#include "src/attrib-server.h" +#include "attrib/gatt.h" +#include "src/profile.h" +#include "src/error.h" +#include "src/textfile.h" +#include "src/attio.h" + +#define PHONE_ALERT_STATUS_SVC_UUID 0x180E +#define ALERT_NOTIF_SVC_UUID 0x1811 + +#define ALERT_STATUS_CHR_UUID 0x2A3F +#define RINGER_CP_CHR_UUID 0x2A40 +#define RINGER_SETTING_CHR_UUID 0x2A41 + +#define ALERT_NOTIF_CP_CHR_UUID 0x2A44 +#define UNREAD_ALERT_CHR_UUID 0x2A45 +#define NEW_ALERT_CHR_UUID 0x2A46 +#define SUPP_NEW_ALERT_CAT_CHR_UUID 0x2A47 +#define SUPP_UNREAD_ALERT_CAT_CHR_UUID 0x2A48 + +#define ALERT_OBJECT_PATH "/org/bluez" +#define ALERT_INTERFACE "org.bluez.Alert1" +#define ALERT_AGENT_INTERFACE "org.bluez.AlertAgent1" + +/* Maximum length for "Text String Information" */ +#define NEW_ALERT_MAX_INFO_SIZE 18 +/* Maximum length for New Alert Characteristic Value */ +#define NEW_ALERT_CHR_MAX_VALUE_SIZE (NEW_ALERT_MAX_INFO_SIZE + 2) + +enum { + ENABLE_NEW_INCOMING, + ENABLE_UNREAD_CAT, + DISABLE_NEW_INCOMING, + DISABLE_UNREAD_CAT, + NOTIFY_NEW_INCOMING, + NOTIFY_UNREAD_CAT, +}; + +enum { + RINGER_SILENT_MODE = 1, + RINGER_MUTE_ONCE, + RINGER_CANCEL_SILENT_MODE, +}; + +/* Ringer Setting characteristic values */ +enum { + RINGER_SILENT, + RINGER_NORMAL, +}; + +enum notify_type { + NOTIFY_RINGER_SETTING = 0, + NOTIFY_ALERT_STATUS, + NOTIFY_NEW_ALERT, + NOTIFY_UNREAD_ALERT, + NOTIFY_SIZE, +}; + +struct alert_data { + const char *category; + char *srv; + char *path; + guint watcher; +}; + +struct alert_adapter { + struct btd_adapter *adapter; + uint16_t supp_new_alert_cat_handle; + uint16_t supp_unread_alert_cat_handle; + uint16_t hnd_ccc[NOTIFY_SIZE]; + uint16_t hnd_value[NOTIFY_SIZE]; +}; + +struct notify_data { + struct alert_adapter *al_adapter; + enum notify_type type; + uint8_t *value; + size_t len; +}; + +struct notify_callback { + struct notify_data *notify_data; + struct btd_device *device; + guint id; +}; + +static GSList *registered_alerts = NULL; +static GSList *alert_adapters = NULL; +static uint8_t ringer_setting = RINGER_NORMAL; +static uint8_t alert_status = 0; + +static const char * const anp_categories[] = { + "simple", + "email", + "news", + "call", + "missed-call", + "sms-mms", + "voice-mail", + "schedule", + "high-priority", + "instant-message", +}; + +static const char * const pasp_categories[] = { + "ringer", + "vibrate", + "display", +}; + +static int adapter_cmp(gconstpointer a, gconstpointer b) +{ + const struct alert_adapter *al_adapter = a; + const struct btd_adapter *adapter = b; + + return al_adapter->adapter == adapter ? 0 : -1; +} + +static struct alert_adapter *find_alert_adapter(struct btd_adapter *adapter) +{ + GSList *l = g_slist_find_custom(alert_adapters, adapter, adapter_cmp); + + return l ? l->data : NULL; +} + +static void alert_data_destroy(gpointer user_data) +{ + struct alert_data *alert = user_data; + + if (alert->watcher) + g_dbus_remove_watch(btd_get_dbus_connection(), alert->watcher); + + g_free(alert->srv); + g_free(alert->path); + g_free(alert); +} + +static void alert_release(gpointer user_data) +{ + struct alert_data *alert = user_data; + DBusMessage *msg; + + msg = dbus_message_new_method_call(alert->srv, alert->path, + ALERT_AGENT_INTERFACE, + "Release"); + if (msg) + g_dbus_send_message(btd_get_dbus_connection(), msg); + + alert_data_destroy(alert); +} + +static void alert_destroy(gpointer user_data) +{ + DBG(""); + + g_slist_free_full(registered_alerts, alert_release); + registered_alerts = NULL; +} + +static const char *valid_category(const char *category) +{ + unsigned i; + + for (i = 0; i < G_N_ELEMENTS(anp_categories); i++) { + if (g_str_equal(anp_categories[i], category)) + return anp_categories[i]; + } + + for (i = 0; i < G_N_ELEMENTS(pasp_categories); i++) { + if (g_str_equal(pasp_categories[i], category)) + return pasp_categories[i]; + } + + return NULL; +} + +static struct alert_data *get_alert_data_by_category(const char *category) +{ + GSList *l; + struct alert_data *alert; + + for (l = registered_alerts; l; l = g_slist_next(l)) { + alert = l->data; + if (g_str_equal(alert->category, category)) + return alert; + } + + return NULL; +} + +static gboolean registered_category(const char *category) +{ + struct alert_data *alert; + + alert = get_alert_data_by_category(category); + if (alert) + return TRUE; + + return FALSE; +} + +static gboolean pasp_category(const char *category) +{ + unsigned i; + + for (i = 0; i < G_N_ELEMENTS(pasp_categories); i++) + if (g_str_equal(category, pasp_categories[i])) + return TRUE; + + return FALSE; +} + +static gboolean valid_description(const char *category, + const char *description) +{ + if (!pasp_category(category)) { + if (strlen(description) >= NEW_ALERT_MAX_INFO_SIZE) + return FALSE; + + return TRUE; + } + + if (g_str_equal(description, "active") || + g_str_equal(description, "not active")) + return TRUE; + + if (g_str_equal(category, "ringer")) + if (g_str_equal(description, "enabled") || + g_str_equal(description, "disabled")) + return TRUE; + + return FALSE; +} + +static gboolean valid_count(const char *category, uint16_t count) +{ + if (!pasp_category(category) && count > 0 && count <= 255) + return TRUE; + + if (pasp_category(category) && count == 1) + return TRUE; + + return FALSE; +} + +static void update_supported_categories(gpointer data, gpointer user_data) +{ + struct alert_adapter *al_adapter = data; + struct btd_adapter *adapter = al_adapter->adapter; + uint8_t value[2]; + unsigned int i; + + memset(value, 0, sizeof(value)); + + for (i = 0; i < G_N_ELEMENTS(anp_categories); i++) { + if (registered_category(anp_categories[i])) + hci_set_bit(i, value); + } + + attrib_db_update(adapter, al_adapter->supp_new_alert_cat_handle, NULL, + value, sizeof(value), NULL); + + /* FIXME: For now report all registered categories as supporting unread + * status, until it is known which ones should be supported */ + attrib_db_update(adapter, al_adapter->supp_unread_alert_cat_handle, + NULL, value, sizeof(value), NULL); +} + +static void watcher_disconnect(DBusConnection *conn, void *user_data) +{ + struct alert_data *alert = user_data; + + DBG("Category %s was disconnected", alert->category); + + registered_alerts = g_slist_remove(registered_alerts, alert); + alert_data_destroy(alert); + + g_slist_foreach(alert_adapters, update_supported_categories, NULL); +} + +static gboolean is_notifiable_device(struct btd_device *device, uint16_t ccc) +{ + char *filename; + GKeyFile *key_file; + char handle[6]; + char *str; + uint16_t val; + gboolean result; + + sprintf(handle, "%hu", ccc); + + filename = btd_device_get_storage_path(device, "ccc"); + if (!filename) { + warn("Unable to get ccc storage path for device"); + return FALSE; + } + + key_file = g_key_file_new(); + g_key_file_load_from_file(key_file, filename, 0, NULL); + + str = g_key_file_get_string(key_file, handle, "Value", NULL); + if (!str) { + result = FALSE; + goto end; + } + + val = strtol(str, NULL, 16); + if (!(val & 0x0001)) { + result = FALSE; + goto end; + } + + result = TRUE; +end: + g_free(str); + g_free(filename); + g_key_file_free(key_file); + + return result; +} + +static void destroy_notify_callback(guint8 status, const guint8 *pdu, guint16 len, + gpointer user_data) +{ + struct notify_callback *cb = user_data; + + DBG("status=%#x", status); + + btd_device_remove_attio_callback(cb->device, cb->id); + btd_device_unref(cb->device); + g_free(cb->notify_data->value); + g_free(cb->notify_data); + g_free(cb); +} + +static void attio_connected_cb(GAttrib *attrib, gpointer user_data) +{ + struct notify_callback *cb = user_data; + struct notify_data *nd = cb->notify_data; + enum notify_type type = nd->type; + struct alert_adapter *al_adapter = nd->al_adapter; + size_t len; + uint8_t *pdu = g_attrib_get_buffer(attrib, &len); + + + switch (type) { + case NOTIFY_RINGER_SETTING: + len = enc_notification(al_adapter->hnd_value[type], + &ringer_setting, sizeof(ringer_setting), + pdu, len); + break; + case NOTIFY_ALERT_STATUS: + len = enc_notification(al_adapter->hnd_value[type], + &alert_status, sizeof(alert_status), + pdu, len); + break; + case NOTIFY_NEW_ALERT: + case NOTIFY_UNREAD_ALERT: + len = enc_notification(al_adapter->hnd_value[type], + nd->value, nd->len, pdu, len); + break; + case NOTIFY_SIZE: + default: + DBG("Unknown type, could not send notification"); + goto end; + } + + DBG("Send notification for handle: 0x%04x, ccc: 0x%04x", + al_adapter->hnd_value[type], + al_adapter->hnd_ccc[type]); + + g_attrib_send(attrib, 0, pdu, len, destroy_notify_callback, cb, NULL); + + return; + +end: + btd_device_remove_attio_callback(cb->device, cb->id); + btd_device_unref(cb->device); + g_free(cb->notify_data->value); + g_free(cb->notify_data); + g_free(cb); +} + +static void filter_devices_notify(struct btd_device *device, void *user_data) +{ + struct notify_data *notify_data = user_data; + struct alert_adapter *al_adapter = notify_data->al_adapter; + enum notify_type type = notify_data->type; + struct notify_callback *cb; + + if (!is_notifiable_device(device, al_adapter->hnd_ccc[type])) + return; + + cb = g_new0(struct notify_callback, 1); + cb->notify_data = notify_data; + cb->device = btd_device_ref(device); + cb->id = btd_device_add_attio_callback(device, + attio_connected_cb, NULL, cb); +} + +static void notify_devices(struct alert_adapter *al_adapter, + enum notify_type type, uint8_t *value, size_t len) +{ + struct notify_data *notify_data; + + notify_data = g_new0(struct notify_data, 1); + notify_data->al_adapter = al_adapter; + notify_data->type = type; + notify_data->value = g_memdup(value, len); + notify_data->len = len; + + btd_adapter_for_each_device(al_adapter->adapter, filter_devices_notify, + notify_data); +} + +static void pasp_notification(enum notify_type type) +{ + GSList *it; + struct alert_adapter *al_adapter; + + for (it = alert_adapters; it; it = g_slist_next(it)) { + al_adapter = it->data; + + notify_devices(al_adapter, type, NULL, 0); + } +} + +static DBusMessage *register_alert(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + const char *sender = dbus_message_get_sender(msg); + char *path; + const char *category; + const char *c; + struct alert_data *alert; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &c, + DBUS_TYPE_OBJECT_PATH, &path, DBUS_TYPE_INVALID)) + return btd_error_invalid_args(msg); + + category = valid_category(c); + if (!category) { + DBG("Invalid category: %s", c); + return btd_error_invalid_args(msg); + } + + if (registered_category(category)) { + DBG("Category %s already registered", category); + return dbus_message_new_method_return(msg); + } + + alert = g_new0(struct alert_data, 1); + alert->srv = g_strdup(sender); + alert->path = g_strdup(path); + alert->category = category; + alert->watcher = g_dbus_add_disconnect_watch(conn, alert->srv, + watcher_disconnect, alert, NULL); + + if (alert->watcher == 0) { + alert_data_destroy(alert); + DBG("Could not register disconnect watcher"); + return btd_error_failed(msg, + "Could not register disconnect watcher"); + } + + registered_alerts = g_slist_append(registered_alerts, alert); + + g_slist_foreach(alert_adapters, update_supported_categories, NULL); + + DBG("RegisterAlert(\"%s\", \"%s\")", alert->category, alert->path); + + return dbus_message_new_method_return(msg); +} + +static void update_new_alert(gpointer data, gpointer user_data) +{ + struct alert_adapter *al_adapter = data; + struct btd_adapter *adapter = al_adapter->adapter; + uint8_t *value = user_data; + + attrib_db_update(adapter, al_adapter->hnd_value[NOTIFY_NEW_ALERT], NULL, + &value[1], value[0], NULL); + + notify_devices(al_adapter, NOTIFY_NEW_ALERT, &value[1], value[0]); +} + +static void update_phone_alerts(const char *category, const char *description) +{ + unsigned int i; + + if (g_str_equal(category, "ringer")) { + if (g_str_equal(description, "enabled")) { + ringer_setting = RINGER_NORMAL; + pasp_notification(NOTIFY_RINGER_SETTING); + return; + } else if (g_str_equal(description, "disabled")) { + ringer_setting = RINGER_SILENT; + pasp_notification(NOTIFY_RINGER_SETTING); + return; + } + } + + for (i = 0; i < G_N_ELEMENTS(pasp_categories); i++) { + if (g_str_equal(pasp_categories[i], category)) { + if (g_str_equal(description, "active")) { + alert_status |= (1 << i); + pasp_notification(NOTIFY_ALERT_STATUS); + } else if (g_str_equal(description, "not active")) { + alert_status &= ~(1 << i); + pasp_notification(NOTIFY_ALERT_STATUS); + } + break; + } + } +} + +static DBusMessage *new_alert(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + const char *sender = dbus_message_get_sender(msg); + const char *category, *description; + struct alert_data *alert; + uint16_t count; + unsigned int i; + size_t dlen; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &category, + DBUS_TYPE_UINT16, &count, DBUS_TYPE_STRING, + &description, DBUS_TYPE_INVALID)) + return btd_error_invalid_args(msg); + + alert = get_alert_data_by_category(category); + if (!alert) { + DBG("Category %s not registered", category); + return btd_error_invalid_args(msg); + } + + if (!g_str_equal(alert->srv, sender)) { + DBG("Sender %s is not registered in category %s", sender, + category); + return btd_error_invalid_args(msg); + } + + if (!valid_description(category, description)) { + DBG("Description %s is invalid for %s category", + description, category); + return btd_error_invalid_args(msg); + } + + if (!valid_count(category, count)) { + DBG("Count %d is invalid for %s category", count, category); + return btd_error_invalid_args(msg); + } + + dlen = strlen(description); + + for (i = 0; i < G_N_ELEMENTS(anp_categories); i++) { + uint8_t value[NEW_ALERT_CHR_MAX_VALUE_SIZE + 1]; + uint8_t *ptr = value; + + if (!g_str_equal(anp_categories[i], category)) + continue; + + memset(value, 0, sizeof(value)); + + *ptr++ = 2; /* Attribute value size */ + *ptr++ = i; /* Category ID (mandatory) */ + *ptr++ = count; /* Number of New Alert (mandatory) */ + /* Text String Information (optional) */ + strncpy((char *) ptr, description, + NEW_ALERT_MAX_INFO_SIZE - 1); + + if (dlen > 0) + *value += dlen + 1; + + g_slist_foreach(alert_adapters, update_new_alert, value); + } + + if (pasp_category(category)) + update_phone_alerts(category, description); + + DBG("NewAlert(\"%s\", %d, \"%s\")", category, count, description); + + return dbus_message_new_method_return(msg); +} + +static int agent_ringer_mute_once(void) +{ + struct alert_data *alert; + DBusMessage *msg; + + alert = get_alert_data_by_category("ringer"); + if (!alert) { + DBG("Category ringer is not registered"); + return -EINVAL; + } + + msg = dbus_message_new_method_call(alert->srv, alert->path, + ALERT_AGENT_INTERFACE, "MuteOnce"); + if (!msg) + return -ENOMEM; + + dbus_message_set_no_reply(msg, TRUE); + g_dbus_send_message(btd_get_dbus_connection(), msg); + + return 0; +} + +static int agent_ringer_set_ringer(const char *mode) +{ + struct alert_data *alert; + DBusMessage *msg; + + alert = get_alert_data_by_category("ringer"); + if (!alert) { + DBG("Category ringer is not registered"); + return -EINVAL; + } + + msg = dbus_message_new_method_call(alert->srv, alert->path, + ALERT_AGENT_INTERFACE, "SetRinger"); + if (!msg) + return -ENOMEM; + + dbus_message_append_args(msg, DBUS_TYPE_STRING, &mode, + DBUS_TYPE_INVALID); + + dbus_message_set_no_reply(msg, TRUE); + g_dbus_send_message(btd_get_dbus_connection(), msg); + + return 0; +} + +static void update_unread_alert(gpointer data, gpointer user_data) +{ + struct alert_adapter *al_adapter = data; + struct btd_adapter *adapter = al_adapter->adapter; + uint8_t *value = user_data; + + attrib_db_update(adapter, + al_adapter->hnd_value[NOTIFY_UNREAD_ALERT], NULL, value, + 2, NULL); + + notify_devices(al_adapter, NOTIFY_UNREAD_ALERT, value, 2); +} + +static DBusMessage *unread_alert(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + const char *sender = dbus_message_get_sender(msg); + struct alert_data *alert; + const char *category; + unsigned int i; + uint16_t count; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &category, + DBUS_TYPE_UINT16, &count, + DBUS_TYPE_INVALID)) + return btd_error_invalid_args(msg); + + alert = get_alert_data_by_category(category); + if (!alert) { + DBG("Category %s not registered", category); + return btd_error_invalid_args(msg); + } + + if (!valid_count(category, count)) { + DBG("Count %d is invalid for %s category", count, category); + return btd_error_invalid_args(msg); + } + + if (!g_str_equal(alert->srv, sender)) { + DBG("Sender %s is not registered in category %s", sender, + category); + return btd_error_invalid_args(msg); + } + + for (i = 0; i < G_N_ELEMENTS(anp_categories); i++) { + if (g_str_equal(anp_categories[i], category)) { + uint8_t value[2]; + + value[0] = i; /* Category ID */ + value[1] = count; /* Unread count */ + + g_slist_foreach(alert_adapters, update_unread_alert, + value); + } + } + + DBG("category %s, count %d", category, count); + + return dbus_message_new_method_return(msg); +} + +static uint8_t ringer_cp_write(struct attribute *a, + struct btd_device *device, + gpointer user_data) +{ + DBG("a = %p", a); + + if (a->len > 1) { + DBG("Invalid command size (%zu)", a->len); + return 0; + } + + switch (a->data[0]) { + case RINGER_SILENT_MODE: + DBG("Silent Mode"); + agent_ringer_set_ringer("disabled"); + break; + case RINGER_MUTE_ONCE: + DBG("Mute Once"); + agent_ringer_mute_once(); + break; + case RINGER_CANCEL_SILENT_MODE: + DBG("Cancel Silent Mode"); + agent_ringer_set_ringer("enabled"); + break; + default: + DBG("Invalid command (0x%02x)", a->data[0]); + } + + return 0; +} + +static uint8_t alert_status_read(struct attribute *a, + struct btd_device *device, + gpointer user_data) +{ + struct btd_adapter *adapter = user_data; + + DBG("a = %p", a); + + if (a->data == NULL || a->data[0] != alert_status) + attrib_db_update(adapter, a->handle, NULL, &alert_status, + sizeof(alert_status), NULL); + + return 0; +} + +static uint8_t ringer_setting_read(struct attribute *a, + struct btd_device *device, + gpointer user_data) +{ + struct btd_adapter *adapter = user_data; + + DBG("a = %p", a); + + if (a->data == NULL || a->data[0] != ringer_setting) + attrib_db_update(adapter, a->handle, NULL, &ringer_setting, + sizeof(ringer_setting), NULL); + + return 0; +} + +static void register_phone_alert_service(struct alert_adapter *al_adapter) +{ + bt_uuid_t uuid; + + bt_uuid16_create(&uuid, PHONE_ALERT_STATUS_SVC_UUID); + + /* Phone Alert Status Service */ + gatt_service_add(al_adapter->adapter, GATT_PRIM_SVC_UUID, &uuid, + /* Alert Status characteristic */ + GATT_OPT_CHR_UUID16, ALERT_STATUS_CHR_UUID, + GATT_OPT_CHR_PROPS, GATT_CHR_PROP_READ | + GATT_CHR_PROP_NOTIFY, + GATT_OPT_CHR_VALUE_CB, ATTRIB_READ, + alert_status_read, al_adapter->adapter, + GATT_OPT_CCC_GET_HANDLE, + &al_adapter->hnd_ccc[NOTIFY_ALERT_STATUS], + GATT_OPT_CHR_VALUE_GET_HANDLE, + &al_adapter->hnd_value[NOTIFY_ALERT_STATUS], + /* Ringer Control Point characteristic */ + GATT_OPT_CHR_UUID16, RINGER_CP_CHR_UUID, + GATT_OPT_CHR_PROPS, GATT_CHR_PROP_WRITE_WITHOUT_RESP, + GATT_OPT_CHR_VALUE_CB, ATTRIB_WRITE, + ringer_cp_write, NULL, + /* Ringer Setting characteristic */ + GATT_OPT_CHR_UUID16, RINGER_SETTING_CHR_UUID, + GATT_OPT_CHR_PROPS, GATT_CHR_PROP_READ | + GATT_CHR_PROP_NOTIFY, + GATT_OPT_CHR_VALUE_CB, ATTRIB_READ, + ringer_setting_read, al_adapter->adapter, + GATT_OPT_CCC_GET_HANDLE, + &al_adapter->hnd_ccc[NOTIFY_RINGER_SETTING], + GATT_OPT_CHR_VALUE_GET_HANDLE, + &al_adapter->hnd_value[NOTIFY_RINGER_SETTING], + GATT_OPT_INVALID); +} + +static uint8_t supp_new_alert_cat_read(struct attribute *a, + struct btd_device *device, + gpointer user_data) +{ + struct btd_adapter *adapter = user_data; + uint8_t value[] = { 0x00, 0x00 }; + + DBG("a = %p", a); + + if (a->data == NULL) + attrib_db_update(adapter, a->handle, NULL, value, sizeof(value), + NULL); + + return 0; +} + +static uint8_t supp_unread_alert_cat_read(struct attribute *a, + struct btd_device *device, + gpointer user_data) +{ + struct btd_adapter *adapter = user_data; + uint8_t value[] = { 0x00, 0x00 }; + + DBG("a = %p", a); + + if (a->data == NULL) + attrib_db_update(adapter, a->handle, NULL, value, sizeof(value), + NULL); + + return 0; +} + +static uint8_t alert_notif_cp_write(struct attribute *a, + struct btd_device *device, + gpointer user_data) +{ + DBG("a = %p", a); + + if (a->len < 2) + return 0; + + switch (a->data[0]) { + case ENABLE_NEW_INCOMING: + DBG("ENABLE_NEW_INCOMING: 0x%02x", a->data[1]); + break; + case ENABLE_UNREAD_CAT: + DBG("ENABLE_UNREAD_CAT: 0x%02x", a->data[1]); + break; + case DISABLE_NEW_INCOMING: + DBG("DISABLE_NEW_INCOMING: 0x%02x", a->data[1]); + break; + case DISABLE_UNREAD_CAT: + DBG("DISABLE_UNREAD_CAT: 0x%02x", a->data[1]); + break; + case NOTIFY_NEW_INCOMING: + DBG("NOTIFY_NEW_INCOMING: 0x%02x", a->data[1]); + break; + case NOTIFY_UNREAD_CAT: + DBG("NOTIFY_UNREAD_CAT: 0x%02x", a->data[1]); + break; + default: + DBG("0x%02x 0x%02x", a->data[0], a->data[1]); + } + + return 0; +} + +static void register_alert_notif_service(struct alert_adapter *al_adapter) +{ + bt_uuid_t uuid; + + bt_uuid16_create(&uuid, ALERT_NOTIF_SVC_UUID); + + /* Alert Notification Service */ + gatt_service_add(al_adapter->adapter, GATT_PRIM_SVC_UUID, &uuid, + /* Supported New Alert Category */ + GATT_OPT_CHR_UUID16, SUPP_NEW_ALERT_CAT_CHR_UUID, + GATT_OPT_CHR_PROPS, GATT_CHR_PROP_READ, + GATT_OPT_CHR_VALUE_CB, ATTRIB_READ, + supp_new_alert_cat_read, al_adapter->adapter, + GATT_OPT_CHR_VALUE_GET_HANDLE, + &al_adapter->supp_new_alert_cat_handle, + /* New Alert */ + GATT_OPT_CHR_UUID16, NEW_ALERT_CHR_UUID, + GATT_OPT_CHR_PROPS, GATT_CHR_PROP_NOTIFY, + GATT_OPT_CCC_GET_HANDLE, + &al_adapter->hnd_ccc[NOTIFY_NEW_ALERT], + GATT_OPT_CHR_VALUE_GET_HANDLE, + &al_adapter->hnd_value[NOTIFY_NEW_ALERT], + /* Supported Unread Alert Category */ + GATT_OPT_CHR_UUID16, SUPP_UNREAD_ALERT_CAT_CHR_UUID, + GATT_OPT_CHR_PROPS, GATT_CHR_PROP_READ, + GATT_OPT_CHR_VALUE_CB, ATTRIB_READ, + supp_unread_alert_cat_read, al_adapter->adapter, + GATT_OPT_CHR_VALUE_GET_HANDLE, + &al_adapter->supp_unread_alert_cat_handle, + /* Unread Alert Status */ + GATT_OPT_CHR_UUID16, UNREAD_ALERT_CHR_UUID, + GATT_OPT_CHR_PROPS, GATT_CHR_PROP_NOTIFY, + GATT_OPT_CCC_GET_HANDLE, + &al_adapter->hnd_ccc[NOTIFY_UNREAD_ALERT], + GATT_OPT_CHR_VALUE_GET_HANDLE, + &al_adapter->hnd_value[NOTIFY_UNREAD_ALERT], + /* Alert Notification Control Point */ + GATT_OPT_CHR_UUID16, ALERT_NOTIF_CP_CHR_UUID, + GATT_OPT_CHR_PROPS, GATT_CHR_PROP_WRITE, + GATT_OPT_CHR_VALUE_CB, ATTRIB_WRITE, + alert_notif_cp_write, NULL, + GATT_OPT_INVALID); +} + +static int alert_server_probe(struct btd_profile *p, + struct btd_adapter *adapter) +{ + struct alert_adapter *al_adapter; + + al_adapter = g_new0(struct alert_adapter, 1); + al_adapter->adapter = btd_adapter_ref(adapter); + + alert_adapters = g_slist_append(alert_adapters, al_adapter); + + register_phone_alert_service(al_adapter); + register_alert_notif_service(al_adapter); + + return 0; +} + +static void alert_server_remove(struct btd_profile *p, + struct btd_adapter *adapter) +{ + struct alert_adapter *al_adapter; + + al_adapter = find_alert_adapter(adapter); + if (!al_adapter) + return; + + alert_adapters = g_slist_remove(alert_adapters, al_adapter); + btd_adapter_unref(al_adapter->adapter); + + g_free(al_adapter); +} + +static struct btd_profile alert_profile = { + .name = "gatt-alert-server", + .adapter_probe = alert_server_probe, + .adapter_remove = alert_server_remove, +}; + +static const GDBusMethodTable alert_methods[] = { + { GDBUS_METHOD("RegisterAlert", + GDBUS_ARGS({ "category", "s" }, + { "agent", "o" }), NULL, + register_alert) }, + { GDBUS_METHOD("NewAlert", + GDBUS_ARGS({ "category", "s" }, + { "count", "q" }, + { "description", "s" }), NULL, + new_alert) }, + { GDBUS_METHOD("UnreadAlert", + GDBUS_ARGS({ "category", "s" }, { "count", "q" }), NULL, + unread_alert) }, + { } +}; + +static int alert_server_init(void) +{ + if (!g_dbus_register_interface(btd_get_dbus_connection(), + ALERT_OBJECT_PATH, ALERT_INTERFACE, + alert_methods, NULL, NULL, NULL, + alert_destroy)) { + error("D-Bus failed to register %s interface", + ALERT_INTERFACE); + return -EIO; + } + + return btd_profile_register(&alert_profile); +} + +static void alert_server_exit(void) +{ + btd_profile_unregister(&alert_profile); + + g_dbus_unregister_interface(btd_get_dbus_connection(), + ALERT_OBJECT_PATH, ALERT_INTERFACE); +} + +static int alert_init(void) +{ + return alert_server_init(); +} + +static void alert_exit(void) +{ + alert_server_exit(); +} + +BLUETOOTH_PLUGIN_DEFINE(alert, VERSION, + BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, + alert_init, alert_exit) diff --git a/profiles/audio/a2dp.c b/profiles/audio/a2dp.c index db0736d6..5877219b 100755 --- a/profiles/audio/a2dp.c +++ b/profiles/audio/a2dp.c @@ -72,6 +72,9 @@ struct a2dp_sep { struct avdtp *session; struct avdtp_stream *stream; guint suspend_timer; +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + gboolean remote_suspended; +#endif gboolean delay_reporting; gboolean locked; gboolean suspending; @@ -207,11 +210,21 @@ static void finalize_setup_errno(struct a2dp_setup *s, int err, { GSourceFunc finalize; va_list args; +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + struct avdtp_error *avdtp_err; +#else struct avdtp_error avdtp_err; +#endif if (err < 0) { +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + avdtp_err = g_new(struct avdtp_error, 1); + avdtp_error_init(avdtp_err, AVDTP_ERRNO, -err); + s->err = avdtp_err; +#else avdtp_error_init(&avdtp_err, AVDTP_ERRNO, -err); s->err = &avdtp_err; +#endif } va_start(args, cb1); @@ -420,6 +433,13 @@ static void stream_state_changed(struct avdtp_stream *stream, return; } +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + if (new_state == AVDTP_STATE_STREAMING && sep->suspend_timer) { + g_source_remove(sep->suspend_timer); + sep->suspend_timer = 0; + } +#endif + if (new_state != AVDTP_STATE_IDLE) return; @@ -844,12 +864,25 @@ static gboolean start_ind(struct avdtp *session, struct avdtp_local_sep *sep, else DBG("Source %p: Start_Ind", sep); +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + if (!a2dp_sep->locked) { + a2dp_sep->session = avdtp_ref(session); + if(a2dp_sep->remote_suspended == FALSE) + a2dp_sep->suspend_timer = g_timeout_add_seconds(SUSPEND_TIMEOUT, + (GSourceFunc) suspend_timeout, + a2dp_sep); + else + a2dp_sep->remote_suspended = FALSE; + } +#else + if (!a2dp_sep->locked) { a2dp_sep->session = avdtp_ref(session); a2dp_sep->suspend_timer = g_timeout_add_seconds(SUSPEND_TIMEOUT, (GSourceFunc) suspend_timeout, a2dp_sep); } +#endif if (!a2dp_sep->starting) return TRUE; @@ -903,6 +936,10 @@ static gboolean suspend_ind(struct avdtp *session, struct avdtp_local_sep *sep, else DBG("Source %p: Suspend_Ind", sep); +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + a2dp_sep->remote_suspended = TRUE; +#endif + if (a2dp_sep->suspend_timer) { g_source_remove(a2dp_sep->suspend_timer); a2dp_sep->suspend_timer = 0; @@ -1207,7 +1244,11 @@ static struct avdtp_sep_ind endpoint_ind = { .delayreport = endpoint_delayreport_ind, }; +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +static sdp_record_t *a2dp_record(uint8_t type, gboolean sink_enabled) +#else static sdp_record_t *a2dp_record(uint8_t type) +#endif { sdp_list_t *svclass_id, *pfseq, *apseq, *root; uuid_t root_uuid, l2cap_uuid, avdtp_uuid, a2dp_uuid; @@ -1216,7 +1257,22 @@ static sdp_record_t *a2dp_record(uint8_t type) sdp_record_t *record; sdp_data_t *psm, *version, *features; uint16_t lp = AVDTP_UUID; +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + uint16_t a2dp_ver, avdtp_ver, feat; + if (sink_enabled) { + DBG("A2DP record for Sink role"); + a2dp_ver = 0x0102; + avdtp_ver = 0x0103; + feat = 0x0002; + } else { + DBG("A2DP record for Source role"); + a2dp_ver = 0x0102; + avdtp_ver = 0x0103; + feat = 0x0001; + } +#else uint16_t a2dp_ver = 0x0103, avdtp_ver = 0x0103, feat = 0x000f; +#endif record = sdp_record_alloc(); if (!record) @@ -1400,6 +1456,13 @@ struct avdtp *a2dp_avdtp_get(struct btd_device *device) return NULL; } +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + if (chan->auth_id) { + DBG("auth is already going..."); + return NULL; + } +#endif + if (chan->session) return avdtp_ref(chan->session); @@ -1524,6 +1587,18 @@ static void confirm_cb(GIOChannel *io, gpointer data) if (!device) goto drop; +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +{ + gboolean restricted = FALSE; + + restricted = device_is_profile_restricted(device, A2DP_SINK_UUID); + if (restricted) { + DBG("A2DP is restricted"); + goto drop; + } +} +#endif + chan = queue_find(server->channels, match_by_device, device); if (chan) { struct a2dp_setup *setup; @@ -1564,13 +1639,34 @@ static bool a2dp_server_listen(struct a2dp_server *server) if (server->io) return true; - server->io = bt_io_listen(NULL, confirm_cb, server, NULL, &err, +#if defined(TIZEN_FEATURE_BLUEZ_MODIFY) + if (btd_adapter_get_a2dp_role(server->adapter) == BLUETOOTH_A2DP_SINK_ROLE) { + server->io = bt_io_listen(NULL, confirm_cb, server, NULL, &err, BT_IO_OPT_SOURCE_BDADDR, btd_adapter_get_address(server->adapter), BT_IO_OPT_PSM, AVDTP_PSM, BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, + BT_IO_OPT_IMTU, 895, BT_IO_OPT_MASTER, true, BT_IO_OPT_INVALID); + } else { + server->io = bt_io_listen(NULL, confirm_cb, server, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, + btd_adapter_get_address(server->adapter), + BT_IO_OPT_PSM, AVDTP_PSM, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, + BT_IO_OPT_MASTER, true, + BT_IO_OPT_INVALID); + } +#else + server->io = bt_io_listen(NULL, confirm_cb, server, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, + btd_adapter_get_address(server->adapter), + BT_IO_OPT_PSM, AVDTP_PSM, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, + BT_IO_OPT_MASTER, true, + BT_IO_OPT_INVALID); +#endif if (server->io) return true; @@ -1697,7 +1793,14 @@ struct a2dp_sep *a2dp_add_sep(struct btd_adapter *adapter, uint8_t type, if (*record_id != 0) goto add; +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + if (btd_adapter_get_a2dp_role(adapter) == BLUETOOTH_A2DP_SINK_ROLE) + record = a2dp_record(type, true); + else + record = a2dp_record(type, false); +#else record = a2dp_record(type); +#endif if (!record) { error("Unable to allocate new service record"); a2dp_unregister_sep(sep); @@ -1796,8 +1899,17 @@ done: static struct a2dp_sep *a2dp_find_sep(struct avdtp *session, GSList *list, const char *sender) { +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + struct a2dp_sep *selected_sep = NULL; +#endif + for (; list; list = list->next) { struct a2dp_sep *sep = list->data; +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + struct avdtp_remote_sep *rsep; + struct avdtp_media_codec_capability *cap; + struct avdtp_service_capability *service; +#endif /* Use sender's endpoint if available */ if (sender) { @@ -1811,14 +1923,35 @@ static struct a2dp_sep *a2dp_find_sep(struct avdtp *session, GSList *list, continue; } +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + rsep = avdtp_find_remote_sep(session, sep->lsep); + if (rsep == NULL) + continue; + + service = avdtp_get_codec(rsep); + cap = (struct avdtp_media_codec_capability *) service->data; + + if (cap->media_codec_type != A2DP_CODEC_VENDOR) { + selected_sep = sep; + continue; + } +#else if (avdtp_find_remote_sep(session, sep->lsep) == NULL) continue; +#endif return sep; } +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + if (selected_sep) + return selected_sep; + else + return NULL; +#else return NULL; +#endif } static struct a2dp_sep *a2dp_select_sep(struct avdtp *session, uint8_t type, diff --git a/profiles/audio/avctp.c b/profiles/audio/avctp.c index 2a43d32f..c223b019 100755 --- a/profiles/audio/avctp.c +++ b/profiles/audio/avctp.c @@ -321,16 +321,31 @@ static void send_key(int fd, uint16_t key, int pressed) static gboolean auto_release(gpointer user_data) { struct avctp *session = user_data; - - session->key.timer = 0; +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + uint16_t op = session->key.op; +#endif DBG("AV/C: key press timeout"); +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + if (op != KEY_FASTFORWARD && op != KEY_REWIND) { + session->key.timer = 0; + send_key(session->uinput, op, 0); + } else { + return TRUE; + } +#else + session->key.timer = 0; send_key(session->uinput, session->key.op, 0); +#endif return FALSE; } +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +extern void avrcp_stop_position_timer(void); +#endif + static void handle_press(struct avctp *session, uint16_t op) { if (session->key.timer > 0) { @@ -339,8 +354,9 @@ static void handle_press(struct avctp *session, uint16_t op) /* Only auto release if keys are different */ if (session->key.op == op) goto done; - +#ifndef TIZEN_FEATURE_BLUEZ_MODIFY send_key(session->uinput, session->key.op, 0); +#endif } session->key.op = op; @@ -361,6 +377,9 @@ static void handle_release(struct avctp *session, uint16_t op) send_key(session->uinput, op, 0); } +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +extern void avrcp_stop_position_timer(void); +#endif static size_t handle_panel_passthrough(struct avctp *session, uint8_t transaction, uint8_t *code, @@ -415,9 +434,13 @@ static size_t handle_panel_passthrough(struct avctp *session, break; } - if (pressed) + if (pressed) { handle_press(session, key_map[i].uinput); - else +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + if (key_map[i].avc == AVC_REWIND) + avrcp_stop_position_timer(); +#endif + } else handle_release(session, key_map[i].uinput); break; @@ -791,7 +814,11 @@ static gboolean process_queue(void *user_data) return FALSE; chan->p = p; +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + p->timeout = g_timeout_add_seconds(5, req_timeout, chan); +#else p->timeout = g_timeout_add_seconds(2, req_timeout, chan); +#endif return FALSE; @@ -1368,9 +1395,15 @@ static void avctp_control_confirm(struct avctp *session, GIOChannel *chan, src = btd_adapter_get_address(device_get_adapter(dev)); dst = device_get_address(dev); +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + session->auth_id = btd_request_authorization(src, dst, + AVRCP_TARGET_UUID, + auth_cb, session); +#else session->auth_id = btd_request_authorization(src, dst, AVRCP_REMOTE_UUID, auth_cb, session); +#endif if (session->auth_id == 0) goto drop; @@ -1434,6 +1467,16 @@ static void avctp_confirm_cb(GIOChannel *chan, gpointer data) if (!device) return; +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + char name[10]; + device_get_name(device, name, sizeof(name)); + DBG("name : %s", name); + if (g_str_equal(name, "PLT_M50")) { + DBG("Don't accept avrcp connection with this headset"); + return; + } +#endif + session = avctp_get_internal(device); if (session == NULL) return; @@ -1690,13 +1733,20 @@ static gboolean repeat_timeout(gpointer user_data) struct avctp *session = user_data; avctp_passthrough_release(session, session->key.op); +#ifndef TIZEN_FEATURE_BLUEZ_MODIFY avctp_passthrough_press(session, session->key.op); return TRUE; +#else + return FALSE; +#endif } static void release_pressed(struct avctp *session) { +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + if (session->key.op != AVC_FAST_FORWARD && session->key.op != AVC_REWIND) +#endif avctp_passthrough_release(session, session->key.op); if (session->key.timer > 0) @@ -1748,6 +1798,23 @@ int avctp_send_passthrough(struct avctp *session, uint8_t op) return avctp_passthrough_press(session, op); } +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +int avctp_send_release_passthrough(struct avctp *session, uint8_t op) +{ + DBG("+"); + + if (op != AVC_FAST_FORWARD && op != AVC_REWIND) + return FALSE; + + /* Auto release if key pressed */ + if (session->key.timer > 0) + g_source_remove(session->key.timer); + session->key.timer = 0; + + DBG("-"); + return avctp_passthrough_release(session, op); +} +#endif int avctp_send_vendordep(struct avctp *session, uint8_t transaction, uint8_t code, uint8_t subunit, diff --git a/profiles/audio/avctp.h b/profiles/audio/avctp.h index 68a27356..90316add 100755 --- a/profiles/audio/avctp.h +++ b/profiles/audio/avctp.h @@ -173,6 +173,9 @@ unsigned int avctp_register_browsing_pdu_handler(struct avctp *session, gboolean avctp_unregister_browsing_pdu_handler(unsigned int id); int avctp_send_passthrough(struct avctp *session, uint8_t op); +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +int avctp_send_release_passthrough(struct avctp *session, uint8_t op); +#endif int avctp_send_vendordep(struct avctp *session, uint8_t transaction, uint8_t code, uint8_t subunit, uint8_t *operands, size_t operand_count); diff --git a/profiles/audio/avdtp.c b/profiles/audio/avdtp.c index 4ec9cca2..bc5a735b 100755 --- a/profiles/audio/avdtp.c +++ b/profiles/audio/avdtp.c @@ -41,6 +41,11 @@ #include "lib/sdp_lib.h" #include "lib/uuid.h" +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +#include <sys/ioctl.h> +#include <bluetooth/hci.h> +#endif /* TIZEN_FEATURE_BLUEZ_MODIFY */ + #include "btio/btio.h" #include "src/log.h" #include "src/shared/util.h" @@ -48,9 +53,14 @@ #include "src/adapter.h" #include "src/device.h" +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +#include "src/service.h" +#endif + #include "avdtp.h" #include "sink.h" #include "source.h" +#include "../../profile.h" #define AVDTP_PSM 25 @@ -85,7 +95,11 @@ static unsigned int seids; #define AVDTP_MSG_TYPE_ACCEPT 0x02 #define AVDTP_MSG_TYPE_REJECT 0x03 +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +#define REQ_TIMEOUT 10 +#else #define REQ_TIMEOUT 6 +#endif #define ABORT_TIMEOUT 2 #define DISCONNECT_TIMEOUT 1 #define START_TIMEOUT 1 @@ -830,13 +844,23 @@ static void handle_transport_connect(struct avdtp *session, GIOChannel *io, goto proceed; DBG("sk %d, omtu %d, send buffer size %d", sk, omtu, buf_size); +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + min_buf_size = omtu * 10; +#else min_buf_size = omtu * 2; +#endif if (buf_size < min_buf_size) { DBG("send buffer size to be increassed to %d", min_buf_size); set_send_buffer_size(sk, min_buf_size); } - +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + else { + DBG("send buffer size to be decreassed to %d", + min_buf_size); + set_send_buffer_size(sk, min_buf_size); + } +#endif proceed: if (!stream->open_acp && sep->cfm && sep->cfm->open) sep->cfm->open(session, sep, stream, NULL, sep->user_data); @@ -940,11 +964,182 @@ static void handle_unanswered_req(struct avdtp *session, pending_req_free(req); } +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +static gboolean send_broadcom_a2dp_qos(const bdaddr_t *dst, gboolean qos_high) +{ + int dd; + int err = 0; + struct hci_conn_info_req *cr; + broadcom_qos_cp cp; + + dd = hci_open_dev(0); + + if (dd < 0) + return FALSE; + + cr = g_malloc0(sizeof(*cr) + sizeof(struct hci_conn_info)); + + cr->type = ACL_LINK; + bacpy(&cr->bdaddr, dst); + + err = ioctl(dd, HCIGETCONNINFO, cr); + if (err < 0) { + error("Fail to get HCIGETCOINFO"); + g_free(cr); + hci_close_dev(dd); + return FALSE; + } + + cp.handle = cr->conn_info->handle; + DBG("Handle %d", cp.handle); + g_free(cr); + + if (qos_high) + cp.priority = BRCM_QOS_PRIORITY_HIGH; + else + cp.priority = BRCM_QOS_PRIORITY_NORMAL; + + if (hci_send_cmd(dd, OGF_VENDOR_CMD, BROADCOM_QOS_CMD, + BROADCOM_QOS_CP_SIZE, &cp) < 0) { + hci_close_dev(dd); + return FALSE; + } + DBG("Send Broadcom Qos Patch %s", qos_high ? "High" : "Low"); + + hci_close_dev(dd); + + return TRUE; +} + +#ifdef TIZEN_FEATURE_BLUEZ_SPRD_QOS +static gboolean send_sprd_a2dp_qos(bdaddr_t *dst, gboolean qos_high) +{ + int dd; + int err = 0; + struct hci_conn_info_req *cr; + qos_setup_cp cp; + + dd = hci_open_dev(0); + + cr = g_malloc0(sizeof(*cr) + sizeof(struct hci_conn_info)); + + cr->type = ACL_LINK; + bacpy(&cr->bdaddr, dst); + + err = ioctl(dd, HCIGETCONNINFO, cr); + if (err < 0) { + error("Fail to get HCIGETCOINFO"); + g_free(cr); + hci_close_dev(dd); + return FALSE; + } + + cp.handle = cr->conn_info->handle; + cp.flags = 0x00; + DBG("Handle %d", cp.handle); + g_free(cr); + + if (qos_high) { + cp.qos.service_type = 0x02; + cp.qos.token_rate = 0X000000C8; + cp.qos.peak_bandwidth = 0X000000C8; + cp.qos.latency = 0x00000001; + cp.qos.delay_variation = 0xFFFFFFFF; + } else { + cp.qos.service_type = 0x01; + cp.qos.token_rate = 0X00000000; + cp.qos.peak_bandwidth = 0X00000000; + cp.qos.latency = 0x00000001; + cp.qos.delay_variation = 0xFFFFFFFF; + } + + if (hci_send_cmd(dd, OGF_LINK_POLICY, OCF_QOS_SETUP, + QOS_SETUP_CP_SIZE, &cp) < 0) { + hci_close_dev(dd); + return FALSE; + } + DBG("Send Spreadtrum Qos Patch %s", qos_high ? "High" : "Low"); + + hci_close_dev(dd); + + return TRUE; +} +#endif /* TIZEN_FEATURE_BLUEZ_SPRD_QOS */ + +static gboolean fix_role_to_master(const bdaddr_t *dst, gboolean fix_to_master) +{ + int dd; + int err = 0; + struct hci_conn_info_req *cr; + switch_role_cp sr_cp; + write_link_policy_cp lp_cp; + + dd = hci_open_dev(0); + if (dd < 0) { + error("hci_open_dev is failed"); + return FALSE; + } + + cr = g_malloc0(sizeof(*cr) + sizeof(struct hci_conn_info)); + + cr->type = ACL_LINK; + bacpy(&cr->bdaddr, dst); + err = ioctl(dd, HCIGETCONNINFO, cr); + if (err < 0) { + error("Fail to get HCIGETCOINFO : %d", err); + g_free(cr); + hci_close_dev(dd); + return FALSE; + } + + if (!(cr->conn_info->link_mode & HCI_LM_MASTER) && fix_to_master) { + DBG("Need to role switch"); + + bacpy(&sr_cp.bdaddr, dst); + sr_cp.role = 0x00; /* 0x00 : Master, 0x01 : Slave */ + if (hci_send_cmd(dd, OGF_LINK_POLICY, OCF_SWITCH_ROLE, + SWITCH_ROLE_CP_SIZE, &sr_cp) < 0) { + error("switch role is failed"); + g_free(cr); + hci_close_dev(dd); + return FALSE; + } + } + + lp_cp.handle = cr->conn_info->handle; + DBG("Handle %d", lp_cp.handle); + g_free(cr); + + lp_cp.policy = fix_to_master ? 0x00 : HCI_LP_SNIFF | HCI_LP_RSWITCH; + DBG("Request link policy : 0x%X", lp_cp.policy); + + if (hci_send_cmd(dd, OGF_LINK_POLICY, OCF_WRITE_LINK_POLICY, + WRITE_LINK_POLICY_CP_SIZE, &lp_cp) < 0) { + error("write link policy is failed : %d", lp_cp.policy); + hci_close_dev(dd); + return FALSE; + } + + hci_close_dev(dd); + + return TRUE; +} +#endif /* TIZEN_FEATURE_BLUEZ_MODIFY */ + static void avdtp_sep_set_state(struct avdtp *session, struct avdtp_local_sep *sep, avdtp_state_t state) { struct avdtp_stream *stream = sep->stream; +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + const bdaddr_t *dst; +#if defined(TIZEN_FEATURE_BLUEZ_SPRD_QOS) + dst = device_get_address(session->device); +#else + if (TIZEN_FEATURE_BLUEZ_BRCM_QOS || TIZEN_FEATURE_BLUEZ_ROLE_CHANGE) + dst = device_get_address(session->device); +#endif +#endif avdtp_state_t old_state; struct avdtp_error err, *err_ptr = NULL; GSList *l; @@ -975,12 +1170,38 @@ static void avdtp_sep_set_state(struct avdtp *session, break; case AVDTP_STATE_OPEN: stream->starting = FALSE; +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + if (TIZEN_FEATURE_BLUEZ_BRCM_QOS) { + send_broadcom_a2dp_qos(dst, FALSE); + } else { +#if defined(TIZEN_FEATURE_BLUEZ_SPRD_QOS) + if (old_state == AVDTP_STATE_STREAMING) + send_sprd_a2dp_qos(dst, FALSE); +#endif + } + if (TIZEN_FEATURE_BLUEZ_ROLE_CHANGE) + fix_role_to_master(dst, FALSE); +#endif /* TIZEN_FEATURE_BLUEZ_MODIFY */ + break; case AVDTP_STATE_STREAMING: if (stream->start_timer) { g_source_remove(stream->start_timer); stream->start_timer = 0; } + +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + if (TIZEN_FEATURE_BLUEZ_BRCM_QOS) { + send_broadcom_a2dp_qos(dst, TRUE); + } else { +#if defined(TIZEN_FEATURE_BLUEZ_SPRD_QOS) + if (old_state == AVDTP_STATE_OPEN) + send_sprd_a2dp_qos(dst, TRUE); +#endif + } + if (TIZEN_FEATURE_BLUEZ_ROLE_CHANGE) + fix_role_to_master(dst, TRUE); +#endif /* TIZEN_FEATURE_BLUEZ_MODIFY */ stream->open_acp = FALSE; break; case AVDTP_STATE_CLOSING: @@ -1020,7 +1241,11 @@ static void avdtp_sep_set_state(struct avdtp *session, } } +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +void finalize_discovery(struct avdtp *session, int err) +#else static void finalize_discovery(struct avdtp *session, int err) +#endif { struct discover_callback *discover = session->discover; struct avdtp_error avdtp_err; @@ -1104,12 +1329,20 @@ static void avdtp_free(void *data) static void connection_lost(struct avdtp *session, int err) { char address[18]; +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + struct btd_service *service; +#endif session = avdtp_ref(session); ba2str(device_get_address(session->device), address); DBG("Disconnected from %s", address); +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + service = btd_device_get_service(session->device, A2DP_SINK_UUID); + if (service) + btd_service_connecting_complete(service, -err); +#endif g_slist_foreach(session->streams, (GFunc) release_stream, session); session->streams = NULL; @@ -1117,14 +1350,48 @@ static void connection_lost(struct avdtp *session, int err) avdtp_set_state(session, AVDTP_SESSION_STATE_DISCONNECTED); - avdtp_unref(session); +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + DBG("%p: ref=%d", session, session->ref); + if (err != EIO && session->ref > 0) /* link loss*/ + return; +#endif + + avdtp_free(session); } +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +static gboolean disconnect_acl_timeout(gpointer user_data) +{ + struct btd_device *device = user_data; + + DBG(""); + + btd_device_disconnect(device); + + return FALSE; +} +#endif + static gboolean disconnect_timeout(gpointer user_data) { struct avdtp *session = user_data; struct btd_service *service; gboolean stream_setup; +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + struct btd_device *device = NULL; + struct btd_adapter *adapter = NULL; + const bdaddr_t *bdaddr = NULL; + + DBG(""); +#endif + +/* Fix : REVERSE_INULL */ +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + if (session->device == NULL) { + error("session device NOT found"); + return FALSE; + } +#endif session->dc_timer = 0; @@ -1143,19 +1410,83 @@ static gboolean disconnect_timeout(gpointer user_data) return FALSE; } +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + if (session->device) { + adapter = device_get_adapter(session->device); + bdaddr = device_get_address(session->device); + if (adapter && bdaddr) + device = btd_adapter_find_device(adapter, bdaddr, BDADDR_BREDR); + if (!device) + error("device is NOT found"); + } +#endif + connection_lost(session, ETIMEDOUT); +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + if (device) + g_timeout_add(100, + disconnect_acl_timeout, + device); +#endif + return FALSE; } +#if defined TIZEN_FEATURE_BLUEZ_MODIFY +static void set_disconnect_timer_for_sink(struct avdtp *session, gboolean disconn) +{ + char name[6]; + + if (session->dc_timer) + remove_disconnect_timer(session); + + device_get_name(session->device, name, sizeof(name)); + DBG("name : [%s]", name); + if (g_str_equal(name, "VW BT") || g_str_equal(name, "VW MI") || + g_str_equal(name, "Seat ")) { + session->dc_timer = g_timeout_add_seconds(3, disconnect_timeout, + session); + } else if (g_str_equal(name, "CAR M")) { + session->dc_timer = g_timeout_add(200, disconnect_timeout, + session); + } else { + if (disconn == TRUE) + session->dc_timer = g_timeout_add(100, + disconnect_timeout, + session); + else + session->dc_timer = g_timeout_add_seconds(DISCONNECT_TIMEOUT, + disconnect_timeout, + session); + } +} +#endif + static void set_disconnect_timer(struct avdtp *session) { +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + char name[6]; +#endif if (session->dc_timer) remove_disconnect_timer(session); - session->dc_timer = g_timeout_add_seconds(DISCONNECT_TIMEOUT, - disconnect_timeout, - session); +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + device_get_name(session->device, name, sizeof(name)); + DBG("name : [%s]", name); + if (g_str_equal(name, "VW BT") || g_str_equal(name, "VW MI") || + g_str_equal(name, "Seat ")) { + session->dc_timer = g_timeout_add_seconds(3, disconnect_timeout, + session); + } else if (g_str_equal(name, "CAR M")) { + session->dc_timer = g_timeout_add(200, disconnect_timeout, + session); + } else { + session->dc_timer = g_timeout_add_seconds(DISCONNECT_TIMEOUT, + disconnect_timeout, + session); + } +#endif } void avdtp_unref(struct avdtp *session) @@ -1164,6 +1495,10 @@ void avdtp_unref(struct avdtp *session) return; session->ref--; +#if defined TIZEN_FEATURE_BLUEZ_MODIFY + struct btd_adapter *adapter; + adapter = avdtp_get_adapter(session); +#endif DBG("%p: ref=%d", session, session->ref); @@ -1172,7 +1507,14 @@ void avdtp_unref(struct avdtp *session) switch (session->state) { case AVDTP_SESSION_STATE_CONNECTED: +#if defined TIZEN_FEATURE_BLUEZ_MODIFY + if (btd_adapter_get_a2dp_role(adapter) == BLUETOOTH_A2DP_SINK_ROLE) + set_disconnect_timer_for_sink(session, TRUE); + else + set_disconnect_timer(session); +#else set_disconnect_timer(session); +#endif break; case AVDTP_SESSION_STATE_CONNECTING: connection_lost(session, ECONNABORTED); @@ -2163,6 +2505,21 @@ static gboolean session_cb(GIOChannel *chan, GIOCondition cond, } if (session->in.message_type == AVDTP_MSG_TYPE_COMMAND) { +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + struct btd_service *service; + + service = btd_device_get_service(session->device, A2DP_SINK_UUID); + if (service != NULL) { + DBG("A2dp state %d", btd_service_get_state( + btd_device_get_service(session->device, A2DP_SINK_UUID))); + + if (btd_service_get_state(btd_device_get_service(session->device, + A2DP_SINK_UUID)) == BTD_SERVICE_STATE_DISCONNECTING) { + DBG("avdtp:%p , disconnect timer is going on", session); + return FALSE; + } + } +#endif if (!avdtp_parse_cmd(session, session->in.transaction, session->in.signal_id, session->in.buf, @@ -2272,6 +2629,10 @@ static void avdtp_connect_cb(GIOChannel *chan, GError *err, gpointer user_data) struct avdtp *session = user_data; char address[18]; int err_no = EIO; +#if defined TIZEN_FEATURE_BLUEZ_MODIFY + struct btd_adapter *adapter; + adapter = avdtp_get_adapter(session); +#endif if (err) { err_no = err->code; @@ -2320,8 +2681,16 @@ static void avdtp_connect_cb(GIOChannel *chan, GError *err, gpointer user_data) (GIOFunc) session_cb, session, NULL); - if (session->stream_setup) + if (session->stream_setup) { +#if defined TIZEN_FEATURE_BLUEZ_MODIFY + if (btd_adapter_get_a2dp_role(adapter) == BLUETOOTH_A2DP_SINK_ROLE) + set_disconnect_timer_for_sink(session, FALSE); + else + set_disconnect_timer(session); +#else set_disconnect_timer(session); +#endif + } } else if (session->pending_open) handle_transport_connect(session, chan, session->imtu, session->omtu); @@ -2385,10 +2754,26 @@ static GIOChannel *l2cap_connect(struct avdtp *session) GError *err = NULL; GIOChannel *io; const bdaddr_t *src; +#if defined TIZEN_FEATURE_BLUEZ_MODIFY + struct btd_adapter *adapter; + adapter = avdtp_get_adapter(session); +#endif src = btd_adapter_get_address(device_get_adapter(session->device)); - io = bt_io_connect(avdtp_connect_cb, session, +#if defined TIZEN_FEATURE_BLUEZ_MODIFY + if (btd_adapter_get_a2dp_role(adapter) == BLUETOOTH_A2DP_SINK_ROLE) { + io = bt_io_connect(avdtp_connect_cb, session, + NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, src, + BT_IO_OPT_DEST_BDADDR, + device_get_address(session->device), + BT_IO_OPT_PSM, AVDTP_PSM, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, + BT_IO_OPT_IMTU, 895, + BT_IO_OPT_INVALID); + } else { + io = bt_io_connect(avdtp_connect_cb, session, NULL, &err, BT_IO_OPT_SOURCE_BDADDR, src, BT_IO_OPT_DEST_BDADDR, @@ -2396,6 +2781,18 @@ static GIOChannel *l2cap_connect(struct avdtp *session) BT_IO_OPT_PSM, AVDTP_PSM, BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, BT_IO_OPT_INVALID); + } +#else + io = bt_io_connect(avdtp_connect_cb, session, + NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, src, + BT_IO_OPT_DEST_BDADDR, + device_get_address(session->device), + BT_IO_OPT_PSM, AVDTP_PSM, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM, + BT_IO_OPT_INVALID); +#endif + if (!io) { error("%s", err->message); g_error_free(err); @@ -2782,6 +3179,12 @@ static gboolean avdtp_abort_resp(struct avdtp *session, struct seid_rej *resp, int size) { struct avdtp_local_sep *sep = stream->lsep; +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + if (!sep) { + error("Error in getting sep"); + return FALSE; + } +#endif avdtp_sep_set_state(session, sep, AVDTP_STATE_ABORTING); @@ -3403,11 +3806,44 @@ int avdtp_start(struct avdtp *session, struct avdtp_stream *stream) /* If timer already active wait it */ if (stream->start_timer) return 0; +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + else { + char address[18]; + uint32_t timeout_sec = START_TIMEOUT; + + ba2str(device_get_address(session->device), address); + /* For Bose headset (Bose AE2w) 2 seconds timeout is required to avoid AVDTP ABORT_CMD */ + if (!strncasecmp(address, "00:0C:8A", 8)) + timeout_sec = 2; + /* For Gear Circle, HS3000 headset, this headset doesn't initiate start command and + * when we add timer for 1 second so idle may trigger callback after 1.2 sec or + * 1.5 sec. So, don't timer for this headset.*/ + if (!strncasecmp(address, "10:92:66", 8) || + !strncasecmp(address, "A8:9F:BA", 8) || + !strncasecmp(address, "00:26:B4", 8)) { + start_timeout(stream); + return 0; + } + /* Here we can't use Mac address as there are changing so check for name */ + char name[10]; + device_get_name(session->device, name, sizeof(name)); + DBG("name : %s", name); + if (g_str_equal(name, "HS3000")) { + start_timeout(stream); + return 0; + } + stream->start_timer = g_timeout_add_seconds(timeout_sec, + start_timeout, + stream); + return 0; + } +#else stream->start_timer = g_timeout_add_seconds(START_TIMEOUT, start_timeout, stream); return 0; +#endif } if (stream->close_int == TRUE) { diff --git a/profiles/audio/avdtp.h b/profiles/audio/avdtp.h index 621a6e3c..54591aa2 100755 --- a/profiles/audio/avdtp.h +++ b/profiles/audio/avdtp.h @@ -230,6 +230,10 @@ int avdtp_discover(struct avdtp *session, avdtp_discover_cb_t cb, gboolean avdtp_has_stream(struct avdtp *session, struct avdtp_stream *stream); +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +void finalize_discovery(struct avdtp *session, int err); +#endif + unsigned int avdtp_stream_add_cb(struct avdtp *session, struct avdtp_stream *stream, avdtp_stream_state_cb cb, void *data); diff --git a/profiles/audio/avrcp.c b/profiles/audio/avrcp.c index 51a89b12..4d245106 100755 --- a/profiles/audio/avrcp.c +++ b/profiles/audio/avrcp.c @@ -137,9 +137,13 @@ #define AVRCP_CHARSET_UTF8 106 #define AVRCP_BROWSING_TIMEOUT 1 +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +#define AVRCP_CT_VERSION 0x0103 +#define AVRCP_TG_VERSION 0x0103 +#else #define AVRCP_CT_VERSION 0x0106 #define AVRCP_TG_VERSION 0x0105 - +#endif #define AVRCP_SCOPE_MEDIA_PLAYER_LIST 0x00 #define AVRCP_SCOPE_MEDIA_PLAYER_VFS 0x01 #define AVRCP_SCOPE_SEARCH 0x02 @@ -275,6 +279,9 @@ struct avrcp { uint8_t transaction; uint8_t transaction_events[AVRCP_EVENT_LAST + 1]; struct pending_pdu *pending_pdu; +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + uint32_t playback_status_id; +#endif }; struct passthrough_handler { @@ -292,6 +299,15 @@ struct control_pdu_handler { static GSList *servers = NULL; static unsigned int avctp_id = 0; +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +#ifdef TIZEN_FEATURE_BLUEZ_AVRCP_TARGET +static uint16_t adapter_avrcp_tg_ver = 0; +#endif +#ifdef TIZEN_FEATURE_BLUEZ_AVRCP_CONTROL +static uint16_t adapter_avrcp_ct_ver = 0; +#endif +#endif + /* Default feature bit mask for media player as per avctp.c:key_map */ static const uint8_t features[16] = { 0xF8, 0xBF, 0xFF, 0xBF, 0x1F, @@ -306,6 +322,13 @@ static uint32_t company_ids[] = { static void avrcp_register_notification(struct avrcp *session, uint8_t event); +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +static GList *player_list_settings(struct avrcp_player *player); +void avrcp_stop_position_timer(void); +unsigned int pos_timer_id = 0; +#endif + +#ifdef TIZEN_FEATURE_BLUEZ_AVRCP_CONTROL static sdp_record_t *avrcp_ct_record(void) { sdp_list_t *svclass_id, *pfseq, *apseq, *apseq1, *root; @@ -315,12 +338,23 @@ static sdp_record_t *avrcp_ct_record(void) sdp_record_t *record; sdp_data_t *psm[2], *version, *features; uint16_t lp = AVCTP_CONTROL_PSM, ap = AVCTP_BROWSING_PSM; +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + uint16_t avctp_ver = 0x0104; + uint16_t feat = 0; +#ifdef ENABLE_AVRCP_CATEGORY1 + feat = AVRCP_FEATURE_CATEGORY_1; +#endif +#ifdef ENABLE_AVRCP_CATEGORY2 + feat = feat | AVRCP_FEATURE_CATEGORY_2; +#endif +#else uint16_t avctp_ver = 0x0103; uint16_t feat = ( AVRCP_FEATURE_CATEGORY_1 | AVRCP_FEATURE_CATEGORY_2 | AVRCP_FEATURE_CATEGORY_3 | AVRCP_FEATURE_CATEGORY_4 | AVRCP_FEATURE_BROWSING); +#endif record = sdp_record_alloc(); if (!record) @@ -371,6 +405,9 @@ static sdp_record_t *avrcp_ct_record(void) /* Bluetooth Profile Descriptor List */ sdp_uuid16_create(&profile[0].uuid, AV_REMOTE_PROFILE_ID); profile[0].version = AVRCP_CT_VERSION; +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + adapter_avrcp_ct_ver = AVRCP_CT_VERSION; +#endif pfseq = sdp_list_append(NULL, &profile[0]); sdp_set_profile_descs(record, pfseq); @@ -396,7 +433,9 @@ static sdp_record_t *avrcp_ct_record(void) return record; } +#endif +#ifdef TIZEN_FEATURE_BLUEZ_AVRCP_TARGET static sdp_record_t *avrcp_tg_record(void) { sdp_list_t *svclass_id, *pfseq, *apseq, *root, *apseq_browsing; @@ -405,9 +444,23 @@ static sdp_record_t *avrcp_tg_record(void) sdp_list_t *aproto_control, *proto_control[2]; sdp_record_t *record; sdp_data_t *psm_control, *version, *features, *psm_browsing; - sdp_list_t *aproto_browsing, *proto_browsing[2] = {0}; +#ifndef TIZEN_FEATURE_BLUEZ_MODIFY + sdp_list_t *aproto_browsing; +#endif + sdp_list_t *proto_browsing[2] = {0}; uint16_t lp = AVCTP_CONTROL_PSM; uint16_t lp_browsing = AVCTP_BROWSING_PSM; +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + uint16_t avctp_ver = 0x0104; + uint16_t feat = 0; +#ifdef ENABLE_AVRCP_CATEGORY1 + feat = AVRCP_FEATURE_CATEGORY_1 | + AVRCP_FEATURE_PLAYER_SETTINGS; +#endif +#ifdef ENABLE_AVRCP_CATEGORY2 + feat = feat | AVRCP_FEATURE_CATEGORY_2; +#endif +#else uint16_t avctp_ver = 0x0103; uint16_t feat = ( AVRCP_FEATURE_CATEGORY_1 | AVRCP_FEATURE_CATEGORY_2 | @@ -415,7 +468,7 @@ static sdp_record_t *avrcp_tg_record(void) AVRCP_FEATURE_CATEGORY_4 | AVRCP_FEATURE_BROWSING | AVRCP_FEATURE_PLAYER_SETTINGS ); - +#endif record = sdp_record_alloc(); if (!record) return NULL; @@ -453,12 +506,17 @@ static sdp_record_t *avrcp_tg_record(void) proto_browsing[1] = sdp_list_append(proto_browsing[1], version); apseq_browsing = sdp_list_append(apseq_browsing, proto_browsing[1]); +#ifndef TIZEN_FEATURE_BLUEZ_MODIFY aproto_browsing = sdp_list_append(NULL, apseq_browsing); sdp_set_add_access_protos(record, aproto_browsing); +#endif /* Bluetooth Profile Descriptor List */ sdp_uuid16_create(&profile[0].uuid, AV_REMOTE_PROFILE_ID); profile[0].version = AVRCP_TG_VERSION; +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + adapter_avrcp_tg_ver = AVRCP_TG_VERSION; +#endif pfseq = sdp_list_append(NULL, &profile[0]); sdp_set_profile_descs(record, pfseq); @@ -471,7 +529,9 @@ static sdp_record_t *avrcp_tg_record(void) sdp_list_free(proto_browsing[0], NULL); sdp_list_free(proto_browsing[1], NULL); sdp_list_free(apseq_browsing, NULL); +#ifndef TIZEN_FEATURE_BLUEZ_MODIFY sdp_list_free(aproto_browsing, NULL); +#endif free(psm_control); free(version); @@ -485,6 +545,7 @@ static sdp_record_t *avrcp_tg_record(void) return record; } +#endif static unsigned int attr_get_max_val(uint8_t attr) { @@ -492,9 +553,17 @@ static unsigned int attr_get_max_val(uint8_t attr) case AVRCP_ATTRIBUTE_EQUALIZER: return AVRCP_EQUALIZER_ON; case AVRCP_ATTRIBUTE_REPEAT_MODE: +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + return AVRCP_REPEAT_MODE_ALL; +#else return AVRCP_REPEAT_MODE_GROUP; +#endif case AVRCP_ATTRIBUTE_SHUFFLE: +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + return AVRCP_SHUFFLE_ALL; +#else return AVRCP_SHUFFLE_GROUP; +#endif case AVRCP_ATTRIBUTE_SCAN: return AVRCP_SCAN_GROUP; } @@ -673,6 +742,10 @@ void avrcp_player_event(struct avrcp_player *player, uint8_t id, GSList *l; int attr; int val; +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + uint32_t *position_val = NULL; + GList *settings; +#endif if (player->sessions == NULL) return; @@ -712,6 +785,24 @@ void avrcp_player_event(struct avrcp_player *player, uint8_t id, break; case AVRCP_EVENT_SETTINGS_CHANGED: size = 2; +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + settings = player_list_settings(player); + pdu->params[1] = g_list_length(settings); + for (; settings; settings = settings->next) { + const char *key = settings->data; + + attr = attr_to_val(key); + if (attr < 0) + continue; + + val = player_get_setting(player, attr); + if (val < 0) + continue; + + pdu->params[size++] = attr; + pdu->params[size++] = val; + } +#else pdu->params[1] = 1; attr = attr_to_val(data); @@ -724,7 +815,19 @@ void avrcp_player_event(struct avrcp_player *player, uint8_t id, pdu->params[size++] = attr; pdu->params[size++] = val; +#endif /* __TIZEN__PATCH__ */ break; +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + case AVRCP_EVENT_PLAYBACK_POS_CHANGED: + size = 5; + position_val = (uint32_t *) data; + *position_val = (*position_val & 0x000000ff) << 24 | + (*position_val & 0x0000ff00) << 8 | + (*position_val & 0x00ff0000) >> 8 | + (*position_val & 0xff000000) >> 24; + memcpy(&pdu->params[1], position_val, sizeof(uint32_t)); + break; +#endif case AVRCP_EVENT_ADDRESSED_PLAYER_CHANGED: size = 5; memcpy(&pdu->params[1], &player->id, sizeof(uint16_t)); @@ -740,6 +843,17 @@ void avrcp_player_event(struct avrcp_player *player, uint8_t id, done: pdu->params_len = htons(size); +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + if (id == AVRCP_EVENT_PLAYBACK_POS_CHANGED && + pos_timer_id > 0) { + /* Remove the timer function which was added for register notification. + * As we are sending changed event eariler then time interval. + */ + DBG("Removing the timer function added by register notification"); + g_source_remove(pos_timer_id); + pos_timer_id = 0; + } +#endif for (l = player->sessions; l; l = l->next) { struct avrcp *session = l->data; @@ -920,8 +1034,10 @@ static const char *attrval_to_str(uint8_t attr, uint8_t value) return "singletrack"; case AVRCP_REPEAT_MODE_ALL: return "alltracks"; +#ifndef TIZEN_FEATURE_BLUEZ_MODIFY case AVRCP_REPEAT_MODE_GROUP: return "group"; +#endif } break; @@ -933,8 +1049,10 @@ static const char *attrval_to_str(uint8_t attr, uint8_t value) return "off"; case AVRCP_SCAN_ALL: return "alltracks"; +#ifndef TIZEN_FEATURE_BLUEZ_MODIFY case AVRCP_SCAN_GROUP: return "group"; +#endif } break; @@ -1426,6 +1544,16 @@ static GList *player_list_settings(struct avrcp_player *player) return player->cb->list_settings(player->user_data); } +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +static uint32_t player_get_playback_position(struct avrcp_player *player) +{ + if (player == NULL) + return UINT32_MAX; + + return player->cb->get_position(player->user_data); +} +#endif + static bool avrcp_handle_play(struct avrcp *session) { struct avrcp_player *player = target_get_player(session); @@ -1507,6 +1635,33 @@ static bool handle_passthrough(struct avctp *conn, uint8_t op, bool pressed, return handler->func(session); } +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +void avrcp_stop_position_timer(void) +{ + if (pos_timer_id > 0) { + DBG("Removing position timer id"); + g_source_remove(pos_timer_id); + pos_timer_id = 0; + } +} +gboolean send_playback_position_event(gpointer user_data) +{ + struct avrcp_player *player = user_data; + uint32_t playback_position; + uint8_t play_status; + + play_status = player_get_status(player); + if (play_status != AVRCP_PLAY_STATUS_PLAYING) + return FALSE; + + playback_position = player_get_playback_position(player); + pos_timer_id = 0; + avrcp_player_event(player, AVRCP_EVENT_PLAYBACK_POS_CHANGED, + &playback_position); + return FALSE; +} +#endif + static uint8_t avrcp_handle_register_notification(struct avrcp *session, struct avrcp_header *pdu, uint8_t transaction) @@ -1515,6 +1670,11 @@ static uint8_t avrcp_handle_register_notification(struct avrcp *session, struct btd_device *dev = session->dev; uint16_t len = ntohs(pdu->params_len); uint64_t uid; +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + uint32_t playback_interval; + uint32_t playback_position; + uint8_t play_status; +#endif GList *settings; /* @@ -1586,6 +1746,40 @@ static uint8_t avrcp_handle_register_notification(struct avrcp *session, len = 2; break; +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + case AVRCP_EVENT_PLAYBACK_POS_CHANGED: + len = 5; + + /* time interval in seconds at which the change in playback position + shall be notified */ + memcpy(&playback_interval, &pdu->params[1], sizeof(uint32_t)); + playback_interval = ((playback_interval>>24)&0xff) | + ((playback_interval<<8)&0xff0000) | + ((playback_interval>>8)&0xff00) | + ((playback_interval<<24)&0xff000000); + + play_status = player_get_status(player); + + if (play_status != AVRCP_PLAY_STATUS_PLAYING) { + DBG("Play Pos Changed Event is skipped(%d)", play_status); + } else { + DBG("Playback interval : %d secs", playback_interval); + pos_timer_id = g_timeout_add_seconds( + playback_interval, + send_playback_position_event, player); + } + + /* retrieve current playback position for interim response */ + playback_position = player_get_playback_position(player); + playback_position = (playback_position & 0x000000ff) << 24 | + (playback_position & 0x0000ff00) << 8 | + (playback_position & 0x00ff0000) >> 8 | + (playback_position & 0xff000000) >> 24; + memcpy(&pdu->params[1], &playback_position, sizeof(uint32_t)); + + break; +#endif + default: /* All other events are not supported yet */ goto err; @@ -2918,6 +3112,26 @@ static int ct_press(struct avrcp_player *player, uint8_t op) return 0; } +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +static int ct_release(struct avrcp_player *player, uint8_t op) +{ + DBG("+"); + int err; + struct avrcp *session; + + session = player->sessions->data; + if (session == NULL) + return -ENOTCONN; + + err = avctp_send_release_passthrough(session->conn, op); + if (err < 0) + return err; + + DBG("-"); + return 0; +} +#endif + static int ct_play(struct media_player *mp, void *user_data) { struct avrcp_player *player = user_data; @@ -2953,6 +3167,43 @@ static int ct_previous(struct media_player *mp, void *user_data) return ct_press(player, AVC_BACKWARD); } +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +static int ct_press_fast_forward(struct media_player *mp, void *user_data) +{ + DBG("+"); + struct avrcp_player *player = user_data; + + DBG("-"); + return ct_press(player, AVC_FAST_FORWARD); +} + +static int ct_release_fast_forward(struct media_player *mp, void *user_data) +{ + DBG("+"); + struct avrcp_player *player = user_data; + + DBG("-"); + return ct_release(player, AVC_FAST_FORWARD); +} + +static int ct_press_rewind(struct media_player *mp, void *user_data) +{ + DBG("+"); + struct avrcp_player *player = user_data; + + DBG("-"); + return ct_press(player, AVC_REWIND); +} + +static int ct_release_rewind(struct media_player *mp, void *user_data) +{ + DBG("+"); + struct avrcp_player *player = user_data; + + DBG("-"); + return ct_release(player, AVC_REWIND); +} +#else static int ct_fast_forward(struct media_player *mp, void *user_data) { struct avrcp_player *player = user_data; @@ -2966,6 +3217,7 @@ static int ct_rewind(struct media_player *mp, void *user_data) return ct_press(player, AVC_REWIND); } +#endif static int ct_list_items(struct media_player *mp, const char *name, uint32_t start, uint32_t end, void *user_data) @@ -3274,8 +3526,15 @@ static const struct media_player_callback ct_cbs = { .stop = ct_stop, .next = ct_next, .previous = ct_previous, +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + .press_fast_forward = ct_press_fast_forward, + .release_fast_forward = ct_release_fast_forward, + .press_rewind = ct_press_rewind, + .release_rewind = ct_release_rewind, +#else .fast_forward = ct_fast_forward, .rewind = ct_rewind, +#endif .list_items = ct_list_items, .change_folder = ct_change_folder, .search = ct_search, @@ -3397,6 +3656,10 @@ static void player_destroy(gpointer data) if (player->destroy) player->destroy(player->user_data); +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + avrcp_stop_position_timer(); +#endif + if (player->changed_id > 0) g_source_remove(player->changed_id); @@ -3724,8 +3987,42 @@ static void avrcp_register_notification(struct avrcp *session, uint8_t event) avrcp_handle_event, session); } -static gboolean avrcp_get_capabilities_resp(struct avctp *conn, uint8_t code, - uint8_t subunit, uint8_t transaction, +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +static char *avrcp_event_to_string(uint8_t event) +{ + + switch (event) { + case AVRCP_EVENT_STATUS_CHANGED: + return "AVRCP EVENT STATUS CHANGED"; + case AVRCP_EVENT_TRACK_CHANGED: + return "AVRCP EVENT TRACK CHANGED"; + case AVRCP_EVENT_SETTINGS_CHANGED: + return "AVRCP EVENT SETTINGS CHANGED"; + case AVRCP_EVENT_ADDRESSED_PLAYER_CHANGED: + return "AVRCP EVENT ADDRESSED PLAYER CHANGED"; + case AVRCP_EVENT_UIDS_CHANGED: + return "AVRCP EVENT UIDS CHANGED"; + case AVRCP_EVENT_AVAILABLE_PLAYERS_CHANGED: + return "AVRCP EVENT AVAILABLE PLAYERS CHANGED"; + case AVRCP_EVENT_VOLUME_CHANGED: + return "AVRCP EVENT VOLUME CHANGED"; + default: + return "Unknown Event"; + } +} + +static gboolean avrcp_get_playback_status(gpointer user_data) +{ + struct avrcp *session = user_data; + + avrcp_get_play_status(session); + + return TRUE; +} +#endif + +static gboolean avrcp_get_capabilities_resp(struct avctp *conn, + uint8_t code, uint8_t subunit, uint8_t *operands, size_t operand_count, void *user_data) { @@ -3751,12 +4048,15 @@ static gboolean avrcp_get_capabilities_resp(struct avctp *conn, uint8_t code, uint8_t event = pdu->params[1 + count]; events |= (1 << event); - +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + DBG("Supported Event %s", avrcp_event_to_string(event)); +#endif switch (event) { case AVRCP_EVENT_STATUS_CHANGED: case AVRCP_EVENT_TRACK_CHANGED: case AVRCP_EVENT_PLAYBACK_POS_CHANGED: case AVRCP_EVENT_SETTINGS_CHANGED: +#ifndef TIZEN_FEATURE_BLUEZ_MODIFY case AVRCP_EVENT_ADDRESSED_PLAYER_CHANGED: case AVRCP_EVENT_UIDS_CHANGED: case AVRCP_EVENT_AVAILABLE_PLAYERS_CHANGED: @@ -3765,6 +4065,7 @@ static gboolean avrcp_get_capabilities_resp(struct avctp *conn, uint8_t code, !session->controller->player) break; case AVRCP_EVENT_VOLUME_CHANGED: +#endif avrcp_register_notification(session, event); break; } @@ -3781,7 +4082,12 @@ static gboolean avrcp_get_capabilities_resp(struct avctp *conn, uint8_t code, if (!(events & (1 << AVRCP_EVENT_STATUS_CHANGED))) avrcp_get_element_attributes(session); - +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + if ((events & (1 << AVRCP_EVENT_STATUS_CHANGED)) == 0) { + session->playback_status_id = g_timeout_add_seconds(1, + avrcp_get_playback_status, session); + } +#endif return FALSE; } @@ -3893,6 +4199,7 @@ static void avrcp_connect_browsing(struct avrcp *session) session); } +#ifdef TIZEN_FEATURE_BLUEZ_AVRCP_TARGET static void target_init(struct avrcp *session) { struct avrcp_server *server = session->server; @@ -3919,13 +4226,23 @@ static void target_init(struct avrcp *session) session->supported_events |= (1 << AVRCP_EVENT_STATUS_CHANGED) | (1 << AVRCP_EVENT_TRACK_CHANGED) | +#ifndef TIZEN_FEATURE_BLUEZ_MODIFY (1 << AVRCP_EVENT_TRACK_REACHED_START) | (1 << AVRCP_EVENT_TRACK_REACHED_END) | +#endif +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + (1 << AVRCP_EVENT_PLAYBACK_POS_CHANGED) | +#endif (1 << AVRCP_EVENT_SETTINGS_CHANGED); if (target->version < 0x0104) return; +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + if (adapter_avrcp_tg_ver < 0x0104) + return; +#endif + session->supported_events |= (1 << AVRCP_EVENT_ADDRESSED_PLAYER_CHANGED) | (1 << AVRCP_EVENT_AVAILABLE_PLAYERS_CHANGED) | @@ -3940,7 +4257,9 @@ static void target_init(struct avrcp *session) avrcp_connect_browsing(session); } +#endif +#ifdef TIZEN_FEATURE_BLUEZ_AVRCP_CONTROL static void controller_init(struct avrcp *session) { struct avrcp_player *player; @@ -3955,6 +4274,11 @@ static void controller_init(struct avrcp *session) DBG("%p version 0x%04x", controller, controller->version); +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + if ((controller->version >= 0x0104) && (adapter_avrcp_ct_ver >= 0x0104)) + session->supported_events |= (1 << AVRCP_EVENT_VOLUME_CHANGED); +#endif + service = btd_device_get_service(session->dev, AVRCP_TARGET_UUID); btd_service_connecting_complete(service, 0); @@ -3973,11 +4297,17 @@ static void controller_init(struct avrcp *session) if (controller->version < 0x0104) return; +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + if (adapter_avrcp_ct_ver < 0x0104) + return; +#endif + if (!(controller->features & AVRCP_FEATURE_BROWSING)) return; avrcp_connect_browsing(session); } +#endif static void session_init_control(struct avrcp *session) { @@ -3991,12 +4321,14 @@ static void session_init_control(struct avrcp *session) handle_vendordep_pdu, session); session->control_handlers = control_handlers; - +#ifdef TIZEN_FEATURE_BLUEZ_AVRCP_CONTROL if (btd_device_get_service(session->dev, AVRCP_TARGET_UUID) != NULL) controller_init(session); - +#endif +#ifdef TIZEN_FEATURE_BLUEZ_AVRCP_TARGET if (btd_device_get_service(session->dev, AVRCP_REMOTE_UUID) != NULL) target_init(session); +#endif } static void controller_destroy(struct avrcp *session) @@ -4030,6 +4362,14 @@ static void session_destroy(struct avrcp *session, int err) server->sessions = g_slist_remove(server->sessions, session); +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + if (session->playback_status_id > 0) { + DBG("Removing the timer for playback status polling"); + g_source_remove(session->playback_status_id); + session->playback_status_id = 0; + } +#endif + session_abort_pending_pdu(session); service = btd_device_get_service(session->dev, AVRCP_TARGET_UUID); @@ -4350,9 +4690,19 @@ static int avrcp_connect(struct btd_service *service) { struct btd_device *dev = btd_service_get_device(service); const char *path = device_get_path(dev); - +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + char name[10]; +#endif DBG("path %s", path); +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + device_get_name(dev, name, sizeof(name)); + DBG("name : %s", name); + if (g_str_equal(name, "PLT_M50")) { + DBG("Don't initiate avrcp connection with this headset"); + return -ENOTSUP; + } +#endif return control_connect(service); } @@ -4380,6 +4730,7 @@ static void avrcp_target_remove(struct btd_service *service) control_unregister(service); } +#ifdef TIZEN_FEATURE_BLUEZ_AVRCP_TARGET static void avrcp_target_server_remove(struct btd_profile *p, struct btd_adapter *adapter) { @@ -4399,7 +4750,9 @@ static void avrcp_target_server_remove(struct btd_profile *p, if (server->ct_record_id == 0) avrcp_server_unregister(server); } +#endif +#ifdef TIZEN_FEATURE_BLUEZ_AVRCP_TARGET static int avrcp_target_server_probe(struct btd_profile *p, struct btd_adapter *adapter) { @@ -4434,6 +4787,7 @@ done: return 0; } +#endif static struct btd_profile avrcp_target_profile = { .name = "audio-avrcp-target", @@ -4444,9 +4798,10 @@ static struct btd_profile avrcp_target_profile = { .connect = avrcp_connect, .disconnect = avrcp_disconnect, - +#ifdef TIZEN_FEATURE_BLUEZ_AVRCP_TARGET .adapter_probe = avrcp_target_server_probe, .adapter_remove = avrcp_target_server_remove, +#endif }; static int avrcp_controller_probe(struct btd_service *service) @@ -4463,6 +4818,7 @@ static void avrcp_controller_remove(struct btd_service *service) control_unregister(service); } +#ifdef TIZEN_FEATURE_BLUEZ_AVRCP_CONTROL static void avrcp_controller_server_remove(struct btd_profile *p, struct btd_adapter *adapter) { @@ -4482,7 +4838,9 @@ static void avrcp_controller_server_remove(struct btd_profile *p, if (server->tg_record_id == 0) avrcp_server_unregister(server); } +#endif +#ifdef TIZEN_FEATURE_BLUEZ_AVRCP_CONTROL static int avrcp_controller_server_probe(struct btd_profile *p, struct btd_adapter *adapter) { @@ -4517,6 +4875,7 @@ done: return 0; } +#endif static struct btd_profile avrcp_controller_profile = { .name = "avrcp-controller", @@ -4527,9 +4886,10 @@ static struct btd_profile avrcp_controller_profile = { .connect = avrcp_connect, .disconnect = avrcp_disconnect, - +#ifdef TIZEN_FEATURE_BLUEZ_AVRCP_CONTROL .adapter_probe = avrcp_controller_server_probe, .adapter_remove = avrcp_controller_server_remove, +#endif }; static int avrcp_init(void) diff --git a/profiles/audio/media.c b/profiles/audio/media.c index 23d15611..e7a16d70 100755 --- a/profiles/audio/media.c +++ b/profiles/audio/media.c @@ -44,6 +44,10 @@ #include "src/dbus-common.h" #include "src/profile.h" +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +#include "src/service.h" +#endif + #include "src/uuid-helper.h" #include "src/log.h" #include "src/error.h" @@ -54,13 +58,29 @@ #include "transport.h" #include "a2dp.h" #include "avrcp.h" +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +#include "sink.h" +#endif #define MEDIA_INTERFACE "org.bluez.Media1" #define MEDIA_ENDPOINT_INTERFACE "org.bluez.MediaEndpoint1" #define MEDIA_PLAYER_INTERFACE "org.mpris.MediaPlayer2.Player" +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +#define A2DP_SINK_ROLE "sink" +#define A2DP_SOURCE_ROLE "source" +#endif + #define REQUEST_TIMEOUT (3 * 1000) /* 3 seconds */ +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +#define SINK_SUSPEND_TIMEOUT 4 /* 4 seconds */ + +unsigned int suspend_timer_id = 0; +static gboolean a2dp_sink_support = false; +static gboolean a2dp_source_support = true; +#endif + struct media_adapter { struct btd_adapter *btd_adapter; GSList *endpoints; /* Endpoints list */ @@ -102,6 +122,9 @@ struct media_player { guint watch; guint properties_watch; guint seek_watch; +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + guint sink_watch; +#endif char *status; uint32_t position; uint32_t duration; @@ -117,6 +140,26 @@ struct media_player { static GSList *adapters = NULL; +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +static gboolean set_avrcp_status = FALSE; +static gboolean send_track_changed_event = FALSE; + +gboolean current_delay_reporting = false; +struct media_endpoint *source_endpoint = NULL; +struct media_endpoint *sink_endpoint = NULL; + +static int media_set_sink_callback(struct btd_device *device, + struct media_player *mp); +static void media_sink_state_changed_cb(struct btd_service *service, + sink_state_t old_state, + sink_state_t new_state, + void *user_data); +void media_stop_suspend_timer(void); +struct media_player *media_adapter_get_player(struct media_adapter *adapter); +static struct media_adapter *find_adapter(struct btd_device *device); +static uint32_t get_position(void *user_data); +#endif + static void endpoint_request_free(struct endpoint_request *request) { if (request->call) @@ -199,7 +242,9 @@ static void media_endpoint_remove(struct media_endpoint *endpoint) if (endpoint->sep) { a2dp_remove_sep(endpoint->sep); +#ifndef TIZEN_FEATURE_BLUEZ_MODIFY return; +#endif } info("Endpoint unregistered: sender=%s path=%s", endpoint->sender, @@ -228,6 +273,9 @@ static void clear_configuration(struct media_endpoint *endpoint, { DBusMessage *msg; const char *path; +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + struct media_player *mp; +#endif msg = dbus_message_new_method_call(endpoint->sender, endpoint->path, MEDIA_ENDPOINT_INTERFACE, @@ -243,6 +291,14 @@ static void clear_configuration(struct media_endpoint *endpoint, g_dbus_send_message(btd_get_dbus_connection(), msg); done: endpoint->transports = g_slist_remove(endpoint->transports, transport); +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + if ((mp = media_adapter_get_player(endpoint->adapter))) + if (mp->sink_watch) { + sink_remove_state_cb(mp->sink_watch); + mp->sink_watch = 0; + } + media_stop_suspend_timer(); +#endif media_transport_destroy(transport); } @@ -405,6 +461,141 @@ static struct media_transport *find_device_transport( return match->data; } +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +struct media_player * media_adapter_get_player(struct media_adapter * adapter) +{ + GSList *l; + DBG(" "); + + for (l = adapter->players; l; l = l->next) { + struct media_player *mp = l->data; + if (mp != NULL) + return mp; + } + return NULL; +} + +void media_stop_suspend_timer(void) +{ + if (suspend_timer_id > 0) { + DBG("Removing sink suspend timer"); + g_source_remove(suspend_timer_id); + suspend_timer_id = 0; + } +} + +gboolean media_reset_mp_status(gpointer user_data) +{ + struct media_player *mp = user_data; + DBG(" "); + + /* PlayBackStatus already reset; so return */ + if (g_strcmp0(mp->status, "playing") != 0) + return FALSE; + + mp->position = get_position(mp); + g_timer_start(mp->timer); + + g_free(mp->status); + mp->status = g_strdup("paused"); + suspend_timer_id = 0; + avrcp_player_event(mp->player, AVRCP_EVENT_STATUS_CHANGED, mp->status); + + return FALSE; +} + +static void media_sink_state_changed_cb(struct btd_service *service, + sink_state_t old_state, + sink_state_t new_state, + void *user_data) +{ + struct media_player *mp = user_data; + DBG(" "); + + /* Check if A2DP streaming is suspended */ + if ((old_state == SINK_STATE_PLAYING) && + (new_state == SINK_STATE_CONNECTED)) { + +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + struct btd_device *device = btd_service_get_device(service); + char name[20] = {0,}; +#endif + + /* Check AVRCP play back status */ + if (g_strcmp0(mp->status, "playing") != 0) + return; + + media_stop_suspend_timer(); + +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + device_get_name(device, name, sizeof(name)); + DBG("Name : %s", name); + + if (g_str_has_prefix(name, "LG HBS") != TRUE) { +#endif + /* PlayBackStatus is still PLAYING; start a timer */ + suspend_timer_id = g_timeout_add_seconds(SINK_SUSPEND_TIMEOUT, + media_reset_mp_status, mp); + DBG("SINK SUSPEND TIMEOUT started"); +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + } +#endif + } + + /* Check if A2DP streaming is started */ + if ((old_state == SINK_STATE_CONNECTED) && + (new_state == SINK_STATE_PLAYING)) { + + struct btd_device *device = btd_service_get_device(service); +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + char name[20] = {0,}; +#else + char name[20]; +#endif + + media_stop_suspend_timer(); + + /* NULL packet streaming during initial connection */ + if (set_avrcp_status == FALSE) { + set_avrcp_status = TRUE; + return; + } + + /* Check for BMW, Audi, VW car kit */ + device_get_name(device, name, sizeof(name)); + DBG("Name : %s", name); + if ((g_str_has_prefix(name, "BMW") == TRUE) || + (g_str_has_prefix(name, "Audi") == TRUE) || + (g_str_has_prefix(name, "VW BT") == TRUE)) { + + /* Check AVRCP play back status */ + if (g_strcmp0(mp->status, "playing") == 0) + return; + + g_free(mp->status); + mp->status = g_strdup("playing"); + avrcp_player_event(mp->player, + AVRCP_EVENT_STATUS_CHANGED, mp->status); + } + } +} + +static int media_set_sink_callback(struct btd_device *device, + struct media_player *mp) +{ + struct btd_service *service; + DBG(" "); + + service = btd_device_get_service(device, A2DP_SINK_UUID); + if (service == NULL) + return -EINVAL; + + mp->sink_watch = sink_add_state_cb(service, media_sink_state_changed_cb, mp); + + return 0; +} +#endif + struct a2dp_config_data { struct a2dp_setup *setup; a2dp_endpoint_config_t cb; @@ -423,6 +614,10 @@ static gboolean set_configuration(struct media_endpoint *endpoint, const char *path; DBusMessageIter iter; struct media_transport *transport; +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + struct media_adapter *adapter; + struct media_player *mp; +#endif transport = find_device_transport(endpoint, device); @@ -443,6 +638,13 @@ static gboolean set_configuration(struct media_endpoint *endpoint, return FALSE; } +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + set_avrcp_status = FALSE; + adapter = find_adapter(device); + if ((mp = media_adapter_get_player(adapter))) + media_set_sink_callback(device, mp); +#endif + endpoint->transports = g_slist_append(endpoint->transports, transport); dbus_message_iter_init_append(msg, &iter); @@ -600,7 +802,11 @@ static gboolean endpoint_init_a2dp_source(struct media_endpoint *endpoint, endpoint->sep = a2dp_add_sep(endpoint->adapter->btd_adapter, AVDTP_SEP_TYPE_SOURCE, endpoint->codec, delay_reporting, &a2dp_endpoint, +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + endpoint, NULL, err); +#else endpoint, a2dp_destroy_endpoint, err); +#endif if (endpoint->sep == NULL) return FALSE; @@ -614,7 +820,11 @@ static gboolean endpoint_init_a2dp_sink(struct media_endpoint *endpoint, endpoint->sep = a2dp_add_sep(endpoint->adapter->btd_adapter, AVDTP_SEP_TYPE_SINK, endpoint->codec, delay_reporting, &a2dp_endpoint, +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + endpoint, NULL, err); +#else endpoint, a2dp_destroy_endpoint, err); +#endif if (endpoint->sep == NULL) return FALSE; @@ -744,13 +954,31 @@ static struct media_endpoint *media_endpoint_create(struct media_adapter *adapte endpoint->adapter = adapter; - if (strcasecmp(uuid, A2DP_SOURCE_UUID) == 0) + if (strcasecmp(uuid, A2DP_SOURCE_UUID) == 0) { +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + source_endpoint = endpoint; + if (btd_adapter_get_a2dp_role(adapter->btd_adapter) == BLUETOOTH_A2DP_SINK_ROLE) + return endpoint; + else + succeeded = endpoint_init_a2dp_source(endpoint, + delay_reporting, err); +#else succeeded = endpoint_init_a2dp_source(endpoint, delay_reporting, err); - else if (strcasecmp(uuid, A2DP_SINK_UUID) == 0) +#endif + } else if (strcasecmp(uuid, A2DP_SINK_UUID) == 0) { +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + sink_endpoint = endpoint; + if (btd_adapter_get_a2dp_role(adapter->btd_adapter) == BLUETOOTH_A2DP_SOURCE_ROLE) + return endpoint; + else + succeeded = endpoint_init_a2dp_sink(endpoint, + delay_reporting, err); +#else succeeded = endpoint_init_a2dp_sink(endpoint, delay_reporting, err); - else if (strcasecmp(uuid, HFP_AG_UUID) == 0 || +#endif + } else if (strcasecmp(uuid, HFP_AG_UUID) == 0 || strcasecmp(uuid, HSP_AG_UUID) == 0) succeeded = TRUE; else if (strcasecmp(uuid, HFP_HS_UUID) == 0 || @@ -787,6 +1015,36 @@ static struct media_endpoint *media_endpoint_create(struct media_adapter *adapte return endpoint; } +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +static int parse_a2dp_uuid(DBusMessageIter *props, const char **uuid) +{ + gboolean has_uuid = FALSE; + + while (dbus_message_iter_get_arg_type(props) == DBUS_TYPE_DICT_ENTRY) { + const char *key; + DBusMessageIter value, entry; + int var; + + dbus_message_iter_recurse(props, &entry); + dbus_message_iter_get_basic(&entry, &key); + + dbus_message_iter_next(&entry); + dbus_message_iter_recurse(&entry, &value); + + var = dbus_message_iter_get_arg_type(&value); + if (strcasecmp(key, "UUID") == 0) { + if (var != DBUS_TYPE_STRING) + return -EINVAL; + dbus_message_iter_get_basic(&value, uuid); + has_uuid = TRUE; + } + dbus_message_iter_next(props); + } + + return has_uuid ? 0 : -EINVAL; +} +#endif + static int parse_properties(DBusMessageIter *props, const char **uuid, gboolean *delay_reporting, uint8_t *codec, uint8_t **capabilities, int *size) @@ -837,6 +1095,42 @@ static int parse_properties(DBusMessageIter *props, const char **uuid, return (has_uuid && has_codec) ? 0 : -EINVAL; } +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +static DBusMessage *a2dp_select_role(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct media_adapter *adapter = data; + DBusMessageIter args, props; + const char *a2dp_role; + gboolean ret; + int err; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &a2dp_role, + DBUS_TYPE_INVALID)) + return btd_error_invalid_args(msg); + + if (!g_strcmp0(a2dp_role, A2DP_SINK_ROLE)) { + btd_adapter_set_a2dp_role(adapter->btd_adapter, BLUETOOTH_A2DP_SINK_ROLE); + a2dp_remove_sep(source_endpoint->sep); + ret = endpoint_init_a2dp_sink(sink_endpoint, current_delay_reporting, NULL); + if (!ret) + DBG("could not init a2dp sink"); + } else if (!g_strcmp0(a2dp_role, A2DP_SOURCE_ROLE)) { + btd_adapter_set_a2dp_role(adapter->btd_adapter, BLUETOOTH_A2DP_SOURCE_ROLE); + a2dp_remove_sep(sink_endpoint->sep); + ret = endpoint_init_a2dp_source(source_endpoint, current_delay_reporting, NULL); + if (!ret) + DBG("could not init a2dp source"); + } else { + DBG("invalid a2dp role"); + return btd_error_invalid_args(msg); + } + + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} +#endif + static DBusMessage *register_endpoint(DBusConnection *conn, DBusMessage *msg, void *data) { @@ -848,7 +1142,15 @@ static DBusMessage *register_endpoint(DBusConnection *conn, DBusMessage *msg, uint8_t *capabilities; int size = 0; int err; - +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + if (btd_adapter_get_a2dp_role(adapter->btd_adapter) == BLUETOOTH_A2DP_SINK_ROLE) { + a2dp_sink_support = true; + a2dp_source_support = false; + } else { + a2dp_sink_support = false; + a2dp_source_support = true; + } +#endif sender = dbus_message_get_sender(msg); dbus_message_iter_init(msg, &args); @@ -866,7 +1168,9 @@ static DBusMessage *register_endpoint(DBusConnection *conn, DBusMessage *msg, if (parse_properties(&props, &uuid, &delay_reporting, &codec, &capabilities, &size) < 0) return btd_error_invalid_args(msg); - +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + current_delay_reporting = delay_reporting; +#endif if (media_endpoint_create(adapter, sender, path, uuid, delay_reporting, codec, capabilities, size, &err) == NULL) { if (err == -EPROTONOSUPPORT) @@ -961,6 +1265,13 @@ static void media_player_free(gpointer data) if (mp->settings) g_hash_table_unref(mp->settings); +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + media_stop_suspend_timer(); + + if (mp->sink_watch) + sink_remove_state_cb(mp->sink_watch); +#endif + g_timer_destroy(mp->timer); g_free(mp->sender); g_free(mp->path); @@ -1126,6 +1437,11 @@ static uint64_t get_uid(void *user_data) if (mp->track == NULL) return UINT64_MAX; +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + if (!g_hash_table_lookup(mp->track, "Title")) + return UINT64_MAX; +#endif + return 0; } @@ -1304,6 +1620,9 @@ static void media_player_exit(DBusConnection *connection, void *user_data) static gboolean set_status(struct media_player *mp, DBusMessageIter *iter) { const char *value; +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + uint32_t playback_position; +#endif if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) return FALSE; @@ -1321,26 +1640,49 @@ static gboolean set_status(struct media_player *mp, DBusMessageIter *iter) mp->status = g_strdup(value); avrcp_player_event(mp->player, AVRCP_EVENT_STATUS_CHANGED, mp->status); +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + if (strcasecmp(mp->status, "reverse-seek") != 0 && + strcasecmp(mp->status, "playing") != 0) { + playback_position = get_position(mp); + avrcp_player_event(mp->player, AVRCP_EVENT_PLAYBACK_POS_CHANGED, + &playback_position); + } +#endif return TRUE; } static gboolean set_position(struct media_player *mp, DBusMessageIter *iter) { +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + uint32_t value; +#else uint64_t value; const char *status; +#endif +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + uint32_t playback_position; +#endif +#ifndef TIZEN_FEATURE_BLUEZ_MODIFY if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_INT64) - return FALSE; - + return FALSE; +#else + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_UINT32) + return FALSE; +#endif dbus_message_iter_get_basic(iter, &value); +#ifndef TIZEN_FEATURE_BLUEZ_MODIFY value /= 1000; - +#endif + DBG("Value %d", value); +#ifndef TIZEN_FEATURE_BLUEZ_MODIFY if (value > get_position(mp)) status = "forward-seek"; else status = "reverse-seek"; +#endif mp->position = value; g_timer_start(mp->timer); @@ -1350,6 +1692,12 @@ static gboolean set_position(struct media_player *mp, DBusMessageIter *iter) if (!mp->position) { avrcp_player_event(mp->player, AVRCP_EVENT_TRACK_REACHED_START, NULL); +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + playback_position = get_position(mp); + avrcp_player_event(mp->player, AVRCP_EVENT_PLAYBACK_POS_CHANGED, + &playback_position); +#endif + return TRUE; } @@ -1360,11 +1708,23 @@ static gboolean set_position(struct media_player *mp, DBusMessageIter *iter) if (mp->position == UINT32_MAX || mp->position >= mp->duration) { avrcp_player_event(mp->player, AVRCP_EVENT_TRACK_REACHED_END, NULL); +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + playback_position = get_position(mp); + avrcp_player_event(mp->player, AVRCP_EVENT_PLAYBACK_POS_CHANGED, + &playback_position); +#endif return TRUE; } +#ifndef TIZEN_FEATURE_BLUEZ_MODIFY /* Send a status change to force resync the position */ avrcp_player_event(mp->player, AVRCP_EVENT_STATUS_CHANGED, status); +#endif +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + playback_position = get_position(mp); + avrcp_player_event(mp->player, AVRCP_EVENT_PLAYBACK_POS_CHANGED, + &playback_position); +#endif return TRUE; } @@ -1372,6 +1732,15 @@ static gboolean set_position(struct media_player *mp, DBusMessageIter *iter) static void set_metadata(struct media_player *mp, const char *key, const char *value) { +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + const char *current_value = NULL; + + current_value = g_hash_table_lookup(mp->track, key); + + if ((g_strcmp0(value, current_value) != 0) && + (send_track_changed_event == FALSE)) + send_track_changed_event = TRUE; +#endif DBG("%s=%s", key, value); g_hash_table_replace(mp->track, g_strdup(key), g_strdup(value)); } @@ -1431,7 +1800,9 @@ static gboolean parse_int64_metadata(struct media_player *mp, const char *key, dbus_message_iter_get_basic(iter, &value); if (strcasecmp(key, "Duration") == 0) { +#ifndef TIZEN_FEATURE_BLUEZ_MODIFY value /= 1000; +#endif mp->duration = value; } @@ -1472,6 +1843,9 @@ static gboolean parse_player_metadata(struct media_player *mp, int ctype; gboolean title = FALSE; uint64_t uid; +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + uint32_t playback_position; +#endif ctype = dbus_message_iter_get_arg_type(iter); if (ctype != DBUS_TYPE_ARRAY) @@ -1479,11 +1853,13 @@ static gboolean parse_player_metadata(struct media_player *mp, dbus_message_iter_recurse(iter, &dict); +#ifndef TIZEN_FEATURE_BLUEZ_MODIFY if (mp->track != NULL) g_hash_table_unref(mp->track); mp->track = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); +#endif while ((ctype = dbus_message_iter_get_arg_type(&dict)) != DBUS_TYPE_INVALID) { @@ -1521,6 +1897,11 @@ static gboolean parse_player_metadata(struct media_player *mp, } else if (strcasecmp(key, "mpris:length") == 0) { if (!parse_int64_metadata(mp, "Duration", &var)) return FALSE; +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + } else if (strcasecmp(key, "xesam:totalTracks") == 0) { + if (!parse_int32_metadata(mp, "NumberOfTracks", &var)) + return FALSE; +#endif } else if (strcasecmp(key, "xesam:trackNumber") == 0) { if (!parse_int32_metadata(mp, "TrackNumber", &var)) return FALSE; @@ -1534,13 +1915,25 @@ static gboolean parse_player_metadata(struct media_player *mp, g_hash_table_insert(mp->track, g_strdup("Title"), g_strdup("")); +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + if (send_track_changed_event) { + uid = get_uid(mp); + avrcp_player_event(mp->player, + AVRCP_EVENT_TRACK_CHANGED, &uid); + send_track_changed_event = FALSE; + + playback_position = get_position(mp); + avrcp_player_event(mp->player, + AVRCP_EVENT_PLAYBACK_POS_CHANGED, &playback_position); + } +#else mp->position = 0; g_timer_start(mp->timer); uid = get_uid(mp); avrcp_player_event(mp->player, AVRCP_EVENT_TRACK_CHANGED, &uid); avrcp_player_event(mp->player, AVRCP_EVENT_TRACK_REACHED_START, NULL); - +#endif return TRUE; } @@ -1791,6 +2184,10 @@ static struct media_player *media_player_create(struct media_adapter *adapter, mp->settings = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + mp->track = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, + g_free); +#endif adapter->players = g_slist_append(adapter->players, mp); @@ -1871,6 +2268,10 @@ static const GDBusMethodTable media_methods[] = { NULL, register_player) }, { GDBUS_METHOD("UnregisterPlayer", GDBUS_ARGS({ "player", "o" }), NULL, unregister_player) }, +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + { GDBUS_METHOD("SelectRole", + GDBUS_ARGS({ "role", "s" }), NULL, a2dp_select_role) }, +#endif { }, }; diff --git a/profiles/audio/player.c b/profiles/audio/player.c index 7944b493..f39b9ae9 100755 --- a/profiles/audio/player.c +++ b/profiles/audio/player.c @@ -534,7 +534,83 @@ static DBusMessage *media_player_previous(DBusConnection *conn, return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); } +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +static DBusMessage *media_player_press_fast_forward(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + DBG("+"); + struct media_player *mp = data; + struct player_callback *cb = mp->cb; + int err; + + if (cb->cbs->press_fast_forward == NULL) + return btd_error_not_supported(msg); + + err = cb->cbs->press_fast_forward(mp, cb->user_data); + if (err < 0) + return btd_error_failed(msg, strerror(-err)); + + DBG("-"); + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + +static DBusMessage *media_player_release_fast_forward(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + DBG("+"); + struct media_player *mp = data; + struct player_callback *cb = mp->cb; + int err; + + if (cb->cbs->release_fast_forward == NULL) + return btd_error_not_supported(msg); + + err = cb->cbs->release_fast_forward(mp, cb->user_data); + if (err < 0) + return btd_error_failed(msg, strerror(-err)); + + DBG("-"); + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + +static DBusMessage *media_player_press_rewind(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + DBG("+"); + struct media_player *mp = data; + struct player_callback *cb = mp->cb; + int err; + + if (cb->cbs->press_rewind == NULL) + return btd_error_not_supported(msg); + + err = cb->cbs->press_rewind(mp, cb->user_data); + if (err < 0) + return btd_error_failed(msg, strerror(-err)); + + DBG("-"); + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} + +static DBusMessage *media_player_release_rewind(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + DBG("+"); + struct media_player *mp = data; + struct player_callback *cb = mp->cb; + int err; + + if (cb->cbs->release_rewind == NULL) + return btd_error_not_supported(msg); + + err = cb->cbs->release_rewind(mp, cb->user_data); + if (err < 0) + return btd_error_failed(msg, strerror(-err)); + DBG("-"); + return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); +} +#else static DBusMessage *media_player_fast_forward(DBusConnection *conn, DBusMessage *msg, void *data) { @@ -568,7 +644,7 @@ static DBusMessage *media_player_rewind(DBusConnection *conn, DBusMessage *msg, return g_dbus_create_reply(msg, DBUS_TYPE_INVALID); } - +#endif static void parse_folder_list(gpointer data, gpointer user_data) { struct media_item *item = data; @@ -725,8 +801,15 @@ static const GDBusMethodTable media_player_methods[] = { { GDBUS_METHOD("Stop", NULL, NULL, media_player_stop) }, { GDBUS_METHOD("Next", NULL, NULL, media_player_next) }, { GDBUS_METHOD("Previous", NULL, NULL, media_player_previous) }, +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + { GDBUS_METHOD("PressFastForward", NULL, NULL, media_player_press_fast_forward) }, + { GDBUS_METHOD("ReleaseFastForward", NULL, NULL, media_player_release_fast_forward) }, + { GDBUS_METHOD("PressRewind", NULL, NULL, media_player_press_rewind) }, + { GDBUS_METHOD("ReleaseRewind", NULL, NULL, media_player_release_rewind) }, +#else { GDBUS_METHOD("FastForward", NULL, NULL, media_player_fast_forward) }, { GDBUS_METHOD("Rewind", NULL, NULL, media_player_rewind) }, +#endif { } }; @@ -1328,18 +1411,37 @@ void media_player_set_metadata(struct media_player *mp, struct media_item *item, const char *key, void *data, size_t len) { +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + char *value; + char *end, *temp; +#else char *value, *curval; +#endif value = g_strndup(data, len); +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + temp = value; + while (g_utf8_validate(temp, -1, (const gchar **)&end) == FALSE) { + temp = g_utf8_find_next_char(end, NULL); + if (temp == NULL) { + *end = '\0'; + break; + } + strcpy(end, temp); + temp = end; + } +#endif + DBG("%s: %s", key, value); +#ifndef TIZEN_FEATURE_BLUEZ_MODIFY curval = g_hash_table_lookup(mp->track, key); if (g_strcmp0(curval, value) == 0) { g_free(value); return; } - +#endif if (mp->process_id == 0) { g_hash_table_remove_all(mp->track); mp->process_id = g_idle_add(process_metadata_changed, mp); diff --git a/profiles/audio/player.h b/profiles/audio/player.h index 54e395a1..21eab49e 100755 --- a/profiles/audio/player.h +++ b/profiles/audio/player.h @@ -52,8 +52,15 @@ struct media_player_callback { int (*stop) (struct media_player *mp, void *user_data); int (*next) (struct media_player *mp, void *user_data); int (*previous) (struct media_player *mp, void *user_data); +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + int (*press_fast_forward) (struct media_player *mp, void *user_data); + int (*release_fast_forward) (struct media_player *mp, void *user_data); + int (*press_rewind) (struct media_player *mp, void *user_data); + int (*release_rewind) (struct media_player *mp, void *user_data); +#else int (*fast_forward) (struct media_player *mp, void *user_data); int (*rewind) (struct media_player *mp, void *user_data); +#endif int (*list_items) (struct media_player *mp, const char *name, uint32_t start, uint32_t end, void *user_data); int (*change_folder) (struct media_player *mp, const char *path, diff --git a/profiles/audio/sink.c b/profiles/audio/sink.c index 7cac2103..332d127d 100755 --- a/profiles/audio/sink.c +++ b/profiles/audio/sink.c @@ -106,9 +106,17 @@ static void sink_set_state(struct sink *sink, sink_state_t new_state) if (new_state != SINK_STATE_DISCONNECTED) return; +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + btd_service_disconnecting_complete(service, 0); +#endif + if (sink->session) { avdtp_unref(sink->session); sink->session = NULL; +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + sink->connect_id = 0; + sink->disconnect_id = 0; +#endif } } @@ -148,7 +156,9 @@ static void stream_state_changed(struct avdtp_stream *stream, switch (new_state) { case AVDTP_STATE_IDLE: +#ifndef TIZEN_FEATURE_BLUEZ_MODIFY btd_service_disconnecting_complete(sink->service, 0); +#endif if (sink->disconnect_id > 0) { a2dp_cancel(sink->disconnect_id); @@ -274,6 +284,7 @@ int sink_connect(struct btd_service *service) { struct sink *sink = btd_service_get_user_data(service); +#ifndef TIZEN_FEATURE_BLUEZ_MODIFY if (!sink->session) sink->session = a2dp_avdtp_get(btd_service_get_device(service)); @@ -281,6 +292,7 @@ int sink_connect(struct btd_service *service) DBG("Unable to get a session"); return -EIO; } +#endif if (sink->connect_id > 0 || sink->disconnect_id > 0) return -EBUSY; @@ -291,6 +303,16 @@ int sink_connect(struct btd_service *service) if (sink->stream_state >= AVDTP_STATE_OPEN) return -EALREADY; +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + if (!sink->session) + sink->session = a2dp_avdtp_get(btd_service_get_device(service)); + + if (!sink->session) { + DBG("Unable to get a session"); + return -EIO; + } +#endif + if (!sink_setup_stream(service, NULL)) { DBG("Failed to create a stream"); return -EIO; @@ -309,8 +331,16 @@ static void sink_free(struct btd_service *service) avdtp_stream_remove_cb(sink->session, sink->stream, sink->cb_id); +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + if (sink->session) { + /* We need to clear the avdtp discovery procedure */ + finalize_discovery(sink->session, ECANCELED); + avdtp_unref(sink->session); + } +#else if (sink->session) avdtp_unref(sink->session); +#endif if (sink->connect_id > 0) { btd_service_connecting_complete(sink->service, -ECANCELED); diff --git a/profiles/cyclingspeed/cyclingspeed.c b/profiles/cyclingspeed/cyclingspeed.c new file mode 100755 index 00000000..e4477256 --- /dev/null +++ b/profiles/cyclingspeed/cyclingspeed.c @@ -0,0 +1,1266 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012 Tieto Poland + * + * 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 <errno.h> +#include <stdbool.h> + +#include <glib.h> + +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/uuid.h" + +#include "gdbus/gdbus.h" + +#include "src/plugin.h" +#include "src/adapter.h" +#include "src/device.h" +#include "src/profile.h" +#include "src/service.h" +#include "src/dbus-common.h" +#include "src/shared/util.h" +#include "src/error.h" +#include "attrib/gattrib.h" +#include "attrib/att.h" +#include "attrib/gatt.h" +#include "src/attio.h" +#include "src/log.h" + +/* min length for ATT indication or notification: opcode (1b) + handle (2b) */ +#define ATT_HDR_LEN 3 + +#define ATT_TIMEOUT 30 + +#define CYCLINGSPEED_INTERFACE "org.bluez.CyclingSpeed1" +#define CYCLINGSPEED_MANAGER_INTERFACE "org.bluez.CyclingSpeedManager1" +#define CYCLINGSPEED_WATCHER_INTERFACE "org.bluez.CyclingSpeedWatcher1" + +#define WHEEL_REV_SUPPORT 0x01 +#define CRANK_REV_SUPPORT 0x02 +#define MULTI_SENSOR_LOC_SUPPORT 0x04 + +#define WHEEL_REV_PRESENT 0x01 +#define CRANK_REV_PRESENT 0x02 + +#define SET_CUMULATIVE_VALUE 0x01 +#define START_SENSOR_CALIBRATION 0x02 +#define UPDATE_SENSOR_LOC 0x03 +#define REQUEST_SUPPORTED_SENSOR_LOC 0x04 +#define RESPONSE_CODE 0x10 + +#define RSP_SUCCESS 0x01 +#define RSP_NOT_SUPPORTED 0x02 +#define RSP_INVALID_PARAM 0x03 +#define RSP_FAILED 0x04 + +struct csc; + +struct controlpoint_req { + struct csc *csc; + uint8_t opcode; + guint timeout; + GDBusPendingReply reply_id; + DBusMessage *msg; + + uint8_t pending_location; +}; + +struct csc_adapter { + struct btd_adapter *adapter; + GSList *devices; /* list of registered devices */ + GSList *watchers; +}; + +struct csc { + struct btd_device *dev; + struct csc_adapter *cadapter; + + GAttrib *attrib; + guint attioid; + /* attio id for measurement characteristics value notifications */ + guint attio_measurement_id; + /* attio id for SC Control Point characteristics value indications */ + guint attio_controlpoint_id; + + struct att_range *svc_range; + + uint16_t measurement_ccc_handle; + uint16_t controlpoint_val_handle; + + uint16_t feature; + gboolean has_location; + uint8_t location; + uint8_t num_locations; + uint8_t *locations; + + struct controlpoint_req *pending_req; +}; + +struct watcher { + struct csc_adapter *cadapter; + guint id; + char *srv; + char *path; +}; + +struct measurement { + struct csc *csc; + + bool has_wheel_rev; + uint32_t wheel_rev; + uint16_t last_wheel_time; + + bool has_crank_rev; + uint16_t crank_rev; + uint16_t last_crank_time; +}; + +struct characteristic { + struct csc *csc; + char uuid[MAX_LEN_UUID_STR + 1]; +}; + +static GSList *csc_adapters = NULL; + +static const char * const location_enum[] = { + "other", "top-of-shoe", "in-shoe", "hip", "front-wheel", "left-crank", + "right-crank", "left-pedal", "right-pedal", "front-hub", + "rear-dropout", "chainstay", "rear-wheel", "rear-hub" +}; + +static const char *location2str(uint8_t value) +{ + if (value < G_N_ELEMENTS(location_enum)) + return location_enum[value]; + + info("Body Sensor Location [%d] is RFU", value); + + return location_enum[0]; +} + +static int str2location(const char *location) +{ + size_t i; + + for (i = 0; i < G_N_ELEMENTS(location_enum); i++) + if (!strcmp(location_enum[i], location)) + return i; + + return -1; +} + +static int cmp_adapter(gconstpointer a, gconstpointer b) +{ + const struct csc_adapter *cadapter = a; + const struct btd_adapter *adapter = b; + + if (adapter == cadapter->adapter) + return 0; + + return -1; +} + +static int cmp_device(gconstpointer a, gconstpointer b) +{ + const struct csc *csc = a; + const struct btd_device *dev = b; + + if (dev == csc->dev) + return 0; + + return -1; +} + +static int cmp_watcher(gconstpointer a, gconstpointer b) +{ + const struct watcher *watcher = a; + const struct watcher *match = b; + int ret; + + ret = g_strcmp0(watcher->srv, match->srv); + if (ret != 0) + return ret; + + return g_strcmp0(watcher->path, match->path); +} + +static struct csc_adapter *find_csc_adapter(struct btd_adapter *adapter) +{ + GSList *l = g_slist_find_custom(csc_adapters, adapter, cmp_adapter); + + if (!l) + return NULL; + + return l->data; +} + +static void destroy_watcher(gpointer user_data) +{ + struct watcher *watcher = user_data; + + g_free(watcher->path); + g_free(watcher->srv); + g_free(watcher); +} + +static struct watcher *find_watcher(GSList *list, const char *sender, + const char *path) +{ + struct watcher *match; + GSList *l; + + match = g_new0(struct watcher, 1); + match->srv = g_strdup(sender); + match->path = g_strdup(path); + + l = g_slist_find_custom(list, match, cmp_watcher); + destroy_watcher(match); + + if (l != NULL) + return l->data; + + return NULL; +} + +static void remove_watcher(gpointer user_data) +{ + struct watcher *watcher = user_data; + + g_dbus_remove_watch(btd_get_dbus_connection(), watcher->id); +} + +static void destroy_csc_adapter(gpointer user_data) +{ + struct csc_adapter *cadapter = user_data; + + g_slist_free_full(cadapter->watchers, remove_watcher); + + g_free(cadapter); +} + +static void destroy_csc(gpointer user_data) +{ + struct csc *csc = user_data; + + if (csc->attioid > 0) + btd_device_remove_attio_callback(csc->dev, csc->attioid); + + if (csc->attrib != NULL) { + g_attrib_unregister(csc->attrib, csc->attio_measurement_id); + g_attrib_unregister(csc->attrib, csc->attio_controlpoint_id); + g_attrib_unref(csc->attrib); + } + + btd_device_unref(csc->dev); + g_free(csc->svc_range); + g_free(csc->locations); + g_free(csc); +} + +static void char_write_cb(guint8 status, const guint8 *pdu, guint16 len, + gpointer user_data) +{ + char *msg = user_data; + + if (status != 0) + error("%s failed", msg); + + g_free(msg); +} + +static gboolean controlpoint_timeout(gpointer user_data) +{ + struct controlpoint_req *req = user_data; + + if (req->opcode == UPDATE_SENSOR_LOC) { + g_dbus_pending_property_error(req->reply_id, + ERROR_INTERFACE ".Failed", + "Operation failed (timeout)"); + } else if (req->opcode == SET_CUMULATIVE_VALUE) { + DBusMessage *reply; + + reply = btd_error_failed(req->msg, + "Operation failed (timeout)"); + + g_dbus_send_message(btd_get_dbus_connection(), reply); + + dbus_message_unref(req->msg); + } + + req->csc->pending_req = NULL; + g_free(req); + + return FALSE; +} + +static void controlpoint_write_cb(guint8 status, const guint8 *pdu, guint16 len, + gpointer user_data) +{ + struct controlpoint_req *req = user_data; + + if (status == 0) { + req->timeout = g_timeout_add_seconds(ATT_TIMEOUT, + controlpoint_timeout, + req); + return; + } + + error("SC Control Point write failed (opcode=%d)", req->opcode); + + if (req->opcode == UPDATE_SENSOR_LOC) { + g_dbus_pending_property_error(req->reply_id, + ERROR_INTERFACE ".Failed", + "Operation failed (%d)", status); + } else if (req->opcode == SET_CUMULATIVE_VALUE) { + DBusMessage *reply; + + reply = btd_error_failed(req->msg, "Operation failed"); + + g_dbus_send_message(btd_get_dbus_connection(), reply); + + dbus_message_unref(req->msg); + } + + req->csc->pending_req = NULL; + g_free(req); +} + +static void read_supported_locations(struct csc *csc) +{ + struct controlpoint_req *req; + + req = g_new0(struct controlpoint_req, 1); + req->csc = csc; + req->opcode = REQUEST_SUPPORTED_SENSOR_LOC; + + csc->pending_req = req; + + gatt_write_char(csc->attrib, csc->controlpoint_val_handle, + &req->opcode, sizeof(req->opcode), + controlpoint_write_cb, req); +} + +static void read_feature_cb(guint8 status, const guint8 *pdu, guint16 len, + gpointer user_data) +{ + struct csc *csc = user_data; + uint8_t value[2]; + ssize_t vlen; + + if (status) { + error("CSC Feature read failed: %s", att_ecode2str(status)); + return; + } + + vlen = dec_read_resp(pdu, len, value, sizeof(value)); + if (vlen < 0) { + error("Protocol error"); + return; + } + + if (vlen != sizeof(value)) { + error("Invalid value length for CSC Feature"); + return; + } + + csc->feature = get_le16(value); + + if ((csc->feature & MULTI_SENSOR_LOC_SUPPORT) + && (csc->locations == NULL)) + read_supported_locations(csc); +} + +static void read_location_cb(guint8 status, const guint8 *pdu, + guint16 len, gpointer user_data) +{ + struct csc *csc = user_data; + uint8_t value; + ssize_t vlen; + + if (status) { + error("Sensor Location read failed: %s", att_ecode2str(status)); + return; + } + + vlen = dec_read_resp(pdu, len, &value, sizeof(value)); + if (vlen < 0) { + error("Protocol error"); + return; + } + + if (vlen != sizeof(value)) { + error("Invalid value length for Sensor Location"); + return; + } + + csc->has_location = TRUE; + csc->location = value; + + g_dbus_emit_property_changed(btd_get_dbus_connection(), + device_get_path(csc->dev), + CYCLINGSPEED_INTERFACE, "Location"); +} + +static void discover_desc_cb(guint8 status, GSList *descs, gpointer user_data) +{ + struct characteristic *ch = user_data; + struct gatt_desc *desc; + uint8_t attr_val[2]; + char *msg = NULL; + + if (status != 0) { + error("Discover %s descriptors failed: %s", ch->uuid, + att_ecode2str(status)); + goto done; + } + + /* There will be only one descriptor on list and it will be CCC */ + desc = descs->data; + + if (g_strcmp0(ch->uuid, CSC_MEASUREMENT_UUID) == 0) { + ch->csc->measurement_ccc_handle = desc->handle; + + if (g_slist_length(ch->csc->cadapter->watchers) == 0) { + put_le16(0x0000, attr_val); + msg = g_strdup("Disable measurement"); + } else { + put_le16(GATT_CLIENT_CHARAC_CFG_NOTIF_BIT, + attr_val); + msg = g_strdup("Enable measurement"); + } + } else if (g_strcmp0(ch->uuid, SC_CONTROL_POINT_UUID) == 0) { + put_le16(GATT_CLIENT_CHARAC_CFG_IND_BIT, attr_val); + msg = g_strdup("Enable SC Control Point indications"); + } else { + goto done; + } + + gatt_write_char(ch->csc->attrib, desc->handle, attr_val, + sizeof(attr_val), char_write_cb, msg); + +done: + g_free(ch); +} + +static void discover_desc(struct csc *csc, struct gatt_char *c, + struct gatt_char *c_next) +{ + struct characteristic *ch; + uint16_t start, end; + bt_uuid_t uuid; + + start = c->value_handle + 1; + + if (c_next != NULL) { + if (start == c_next->handle) + return; + end = c_next->handle - 1; + } else if (c->value_handle != csc->svc_range->end) { + end = csc->svc_range->end; + } else { + return; + } + + ch = g_new0(struct characteristic, 1); + ch->csc = csc; + memcpy(ch->uuid, c->uuid, sizeof(c->uuid)); + + bt_uuid16_create(&uuid, GATT_CLIENT_CHARAC_CFG_UUID); + + gatt_discover_desc(csc->attrib, start, end, &uuid, discover_desc_cb, + ch); +} + +static void update_watcher(gpointer data, gpointer user_data) +{ + struct watcher *w = data; + struct measurement *m = user_data; + struct csc *csc = m->csc; + const char *path = device_get_path(csc->dev); + DBusMessageIter iter; + DBusMessageIter dict; + DBusMessage *msg; + + msg = dbus_message_new_method_call(w->srv, w->path, + CYCLINGSPEED_WATCHER_INTERFACE, "MeasurementReceived"); + if (msg == NULL) + return; + + dbus_message_iter_init_append(msg, &iter); + + dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH , &path); + + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); + + if (m->has_wheel_rev) { + dict_append_entry(&dict, "WheelRevolutions", + DBUS_TYPE_UINT32, &m->wheel_rev); + dict_append_entry(&dict, "LastWheelEventTime", + DBUS_TYPE_UINT16, &m->last_wheel_time); + } + + if (m->has_crank_rev) { + dict_append_entry(&dict, "CrankRevolutions", + DBUS_TYPE_UINT16, &m->crank_rev); + dict_append_entry(&dict, "LastCrankEventTime", + DBUS_TYPE_UINT16, &m->last_crank_time); + } + + dbus_message_iter_close_container(&iter, &dict); + + dbus_message_set_no_reply(msg, TRUE); + g_dbus_send_message(btd_get_dbus_connection(), msg); +} + +static void process_measurement(struct csc *csc, const uint8_t *pdu, + uint16_t len) +{ + struct measurement m; + uint8_t flags; + + flags = *pdu; + + pdu++; + len--; + + memset(&m, 0, sizeof(m)); + + if ((flags & WHEEL_REV_PRESENT) && (csc->feature & WHEEL_REV_SUPPORT)) { + if (len < 6) { + error("Wheel revolutions data fields missing"); + return; + } + + m.has_wheel_rev = true; + m.wheel_rev = get_le32(pdu); + m.last_wheel_time = get_le16(pdu + 4); + pdu += 6; + len -= 6; + } + + if ((flags & CRANK_REV_PRESENT) && (csc->feature & CRANK_REV_SUPPORT)) { + if (len < 4) { + error("Crank revolutions data fields missing"); + return; + } + + m.has_crank_rev = true; + m.crank_rev = get_le16(pdu); + m.last_crank_time = get_le16(pdu + 2); + pdu += 4; + len -= 4; + } + + /* Notify all registered watchers */ + m.csc = csc; + g_slist_foreach(csc->cadapter->watchers, update_watcher, &m); +} + +static void measurement_notify_handler(const uint8_t *pdu, uint16_t len, + gpointer user_data) +{ + struct csc *csc = user_data; + + /* should be at least opcode (1b) + handle (2b) */ + if (len < 3) { + error("Invalid PDU received"); + return; + } + + process_measurement(csc, pdu + 3, len - 3); +} + +static void controlpoint_property_reply(struct controlpoint_req *req, + uint8_t code) +{ + switch (code) { + case RSP_SUCCESS: + g_dbus_pending_property_success(req->reply_id); + break; + + case RSP_NOT_SUPPORTED: + g_dbus_pending_property_error(req->reply_id, + ERROR_INTERFACE ".NotSupported", + "Feature is not supported"); + break; + + case RSP_INVALID_PARAM: + g_dbus_pending_property_error(req->reply_id, + ERROR_INTERFACE ".InvalidArguments", + "Invalid arguments in method call"); + break; + + case RSP_FAILED: + g_dbus_pending_property_error(req->reply_id, + ERROR_INTERFACE ".Failed", + "Operation failed"); + break; + + default: + g_dbus_pending_property_error(req->reply_id, + ERROR_INTERFACE ".Failed", + "Operation failed (%d)", code); + break; + } +} + +static void controlpoint_method_reply(struct controlpoint_req *req, + uint8_t code) +{ + DBusMessage *reply; + + switch (code) { + case RSP_SUCCESS: + reply = dbus_message_new_method_return(req->msg); + break; + case RSP_NOT_SUPPORTED: + reply = btd_error_not_supported(req->msg); + break; + case RSP_INVALID_PARAM: + reply = btd_error_invalid_args(req->msg); + break; + case RSP_FAILED: + reply = btd_error_failed(req->msg, "Failed"); + break; + default: + reply = btd_error_failed(req->msg, "Unknown error"); + break; + } + + g_dbus_send_message(btd_get_dbus_connection(), reply); + + dbus_message_unref(req->msg); +} + +static void controlpoint_ind_handler(const uint8_t *pdu, uint16_t len, + gpointer user_data) +{ + struct csc *csc = user_data; + struct controlpoint_req *req = csc->pending_req; + uint8_t opcode; + uint8_t req_opcode; + uint8_t rsp_code; + uint8_t *opdu; + uint16_t olen; + size_t plen; + + if (len < ATT_HDR_LEN) { + error("Invalid PDU received"); + return; + } + + /* skip ATT header */ + pdu += ATT_HDR_LEN; + len -= ATT_HDR_LEN; + + if (len < 1) { + error("Op Code missing"); + goto done; + } + + opcode = *pdu; + pdu++; + len--; + + if (opcode != RESPONSE_CODE) { + DBG("Unsupported Op Code received (%d)", opcode); + goto done; + } + + if (len < 2) { + error("Invalid Response Code PDU received"); + goto done; + } + + req_opcode = *pdu; + rsp_code = *(pdu + 1); + pdu += 2; + len -= 2; + + if (req == NULL || req->opcode != req_opcode) { + DBG("Indication received without pending request"); + goto done; + } + + switch (req->opcode) { + case SET_CUMULATIVE_VALUE: + controlpoint_method_reply(req, rsp_code); + break; + + case REQUEST_SUPPORTED_SENSOR_LOC: + if (rsp_code == RSP_SUCCESS) { + csc->num_locations = len; + csc->locations = g_memdup(pdu, len); + } else { + error("Failed to read Supported Sendor Locations"); + } + break; + + case UPDATE_SENSOR_LOC: + csc->location = req->pending_location; + + controlpoint_property_reply(req, rsp_code); + + g_dbus_emit_property_changed(btd_get_dbus_connection(), + device_get_path(csc->dev), + CYCLINGSPEED_INTERFACE, "Location"); + break; + } + + csc->pending_req = NULL; + g_source_remove(req->timeout); + g_free(req); + +done: + opdu = g_attrib_get_buffer(csc->attrib, &plen); + olen = enc_confirmation(opdu, plen); + if (olen > 0) + g_attrib_send(csc->attrib, 0, opdu, olen, NULL, NULL, NULL); +} + +static void discover_char_cb(uint8_t status, GSList *chars, void *user_data) +{ + struct csc *csc = user_data; + uint16_t feature_val_handle = 0; + + if (status) { + error("Discover CSCS characteristics: %s", + att_ecode2str(status)); + return; + } + + for (; chars; chars = chars->next) { + struct gatt_char *c = chars->data; + struct gatt_char *c_next = + (chars->next ? chars->next->data : NULL); + + if (g_strcmp0(c->uuid, CSC_MEASUREMENT_UUID) == 0) { + csc->attio_measurement_id = + g_attrib_register(csc->attrib, + ATT_OP_HANDLE_NOTIFY, c->value_handle, + measurement_notify_handler, csc, NULL); + + discover_desc(csc, c, c_next); + } else if (g_strcmp0(c->uuid, CSC_FEATURE_UUID) == 0) { + feature_val_handle = c->value_handle; + } else if (g_strcmp0(c->uuid, SENSOR_LOCATION_UUID) == 0) { + DBG("Sensor Location supported"); + gatt_read_char(csc->attrib, c->value_handle, + read_location_cb, csc); + } else if (g_strcmp0(c->uuid, SC_CONTROL_POINT_UUID) == 0) { + DBG("SC Control Point supported"); + csc->controlpoint_val_handle = c->value_handle; + + csc->attio_controlpoint_id = g_attrib_register( + csc->attrib, ATT_OP_HANDLE_IND, + c->value_handle, + controlpoint_ind_handler, csc, NULL); + + discover_desc(csc, c, c_next); + } + } + + if (feature_val_handle > 0) + gatt_read_char(csc->attrib, feature_val_handle, + read_feature_cb, csc); +} + +static void enable_measurement(gpointer data, gpointer user_data) +{ + struct csc *csc = data; + uint16_t handle = csc->measurement_ccc_handle; + uint8_t value[2]; + char *msg; + + if (csc->attrib == NULL || !handle) + return; + + put_le16(GATT_CLIENT_CHARAC_CFG_NOTIF_BIT, value); + msg = g_strdup("Enable measurement"); + + gatt_write_char(csc->attrib, handle, value, sizeof(value), + char_write_cb, msg); +} + +static void disable_measurement(gpointer data, gpointer user_data) +{ + struct csc *csc = data; + uint16_t handle = csc->measurement_ccc_handle; + uint8_t value[2]; + char *msg; + + if (csc->attrib == NULL || !handle) + return; + + put_le16(0x0000, value); + msg = g_strdup("Disable measurement"); + + gatt_write_char(csc->attrib, handle, value, sizeof(value), + char_write_cb, msg); +} + +static void attio_connected_cb(GAttrib *attrib, gpointer user_data) +{ + struct csc *csc = user_data; + + DBG(""); + + csc->attrib = g_attrib_ref(attrib); + + gatt_discover_char(csc->attrib, csc->svc_range->start, + csc->svc_range->end, NULL, + discover_char_cb, csc); +} + +static void attio_disconnected_cb(gpointer user_data) +{ + struct csc *csc = user_data; + + DBG(""); + + if (csc->attio_measurement_id > 0) { + g_attrib_unregister(csc->attrib, csc->attio_measurement_id); + csc->attio_measurement_id = 0; + } + + if (csc->attio_controlpoint_id > 0) { + g_attrib_unregister(csc->attrib, csc->attio_controlpoint_id); + csc->attio_controlpoint_id = 0; + } + + g_attrib_unref(csc->attrib); + csc->attrib = NULL; +} + +static void watcher_exit_cb(DBusConnection *conn, void *user_data) +{ + struct watcher *watcher = user_data; + struct csc_adapter *cadapter = watcher->cadapter; + + DBG("cycling watcher [%s] disconnected", watcher->path); + + cadapter->watchers = g_slist_remove(cadapter->watchers, watcher); + g_dbus_remove_watch(conn, watcher->id); + + if (g_slist_length(cadapter->watchers) == 0) + g_slist_foreach(cadapter->devices, disable_measurement, 0); +} + +static DBusMessage *register_watcher(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct csc_adapter *cadapter = data; + struct watcher *watcher; + const char *sender = dbus_message_get_sender(msg); + char *path; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) + return btd_error_invalid_args(msg); + + watcher = find_watcher(cadapter->watchers, sender, path); + if (watcher != NULL) + return btd_error_already_exists(msg); + + watcher = g_new0(struct watcher, 1); + watcher->cadapter = cadapter; + watcher->id = g_dbus_add_disconnect_watch(conn, sender, watcher_exit_cb, + watcher, destroy_watcher); + watcher->srv = g_strdup(sender); + watcher->path = g_strdup(path); + + if (g_slist_length(cadapter->watchers) == 0) + g_slist_foreach(cadapter->devices, enable_measurement, 0); + + cadapter->watchers = g_slist_prepend(cadapter->watchers, watcher); + + DBG("cycling watcher [%s] registered", path); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *unregister_watcher(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct csc_adapter *cadapter = data; + struct watcher *watcher; + const char *sender = dbus_message_get_sender(msg); + char *path; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) + return btd_error_invalid_args(msg); + + watcher = find_watcher(cadapter->watchers, sender, path); + if (watcher == NULL) + return btd_error_does_not_exist(msg); + + cadapter->watchers = g_slist_remove(cadapter->watchers, watcher); + g_dbus_remove_watch(conn, watcher->id); + + if (g_slist_length(cadapter->watchers) == 0) + g_slist_foreach(cadapter->devices, disable_measurement, 0); + + DBG("cycling watcher [%s] unregistered", path); + + return dbus_message_new_method_return(msg); +} + +static const GDBusMethodTable cyclingspeed_manager_methods[] = { + { GDBUS_METHOD("RegisterWatcher", + GDBUS_ARGS({ "agent", "o" }), NULL, + register_watcher) }, + { GDBUS_METHOD("UnregisterWatcher", + GDBUS_ARGS({ "agent", "o" }), NULL, + unregister_watcher) }, + { } +}; + +static int csc_adapter_probe(struct btd_profile *p, struct btd_adapter *adapter) +{ + struct csc_adapter *cadapter; + + cadapter = g_new0(struct csc_adapter, 1); + cadapter->adapter = adapter; + + if (!g_dbus_register_interface(btd_get_dbus_connection(), + adapter_get_path(adapter), + CYCLINGSPEED_MANAGER_INTERFACE, + cyclingspeed_manager_methods, + NULL, NULL, cadapter, + destroy_csc_adapter)) { + error("D-Bus failed to register %s interface", + CYCLINGSPEED_MANAGER_INTERFACE); + destroy_csc_adapter(cadapter); + return -EIO; + } + + csc_adapters = g_slist_prepend(csc_adapters, cadapter); + + return 0; +} + +static void csc_adapter_remove(struct btd_profile *p, + struct btd_adapter *adapter) +{ + struct csc_adapter *cadapter; + + cadapter = find_csc_adapter(adapter); + if (cadapter == NULL) + return; + + csc_adapters = g_slist_remove(csc_adapters, cadapter); + + g_dbus_unregister_interface(btd_get_dbus_connection(), + adapter_get_path(cadapter->adapter), + CYCLINGSPEED_MANAGER_INTERFACE); +} + +static gboolean property_get_location(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct csc *csc = data; + const char *loc; + + if (!csc->has_location) + return FALSE; + + loc = location2str(csc->location); + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &loc); + + return TRUE; +} + +static void property_set_location(const GDBusPropertyTable *property, + DBusMessageIter *iter, + GDBusPendingPropertySet id, void *data) +{ + struct csc *csc = data; + char *loc; + int loc_val; + uint8_t att_val[2]; + struct controlpoint_req *req; + + if (csc->pending_req != NULL) { + g_dbus_pending_property_error(id, + ERROR_INTERFACE ".InProgress", + "Operation already in progress"); + return; + } + + if (!(csc->feature & MULTI_SENSOR_LOC_SUPPORT)) { + g_dbus_pending_property_error(id, + ERROR_INTERFACE ".NotSupported", + "Feature is not supported"); + return; + } + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) { + g_dbus_pending_property_error(id, + ERROR_INTERFACE ".InvalidArguments", + "Invalid arguments in method call"); + return; + } + + dbus_message_iter_get_basic(iter, &loc); + + loc_val = str2location(loc); + + if (loc_val < 0) { + g_dbus_pending_property_error(id, + ERROR_INTERFACE ".InvalidArguments", + "Invalid arguments in method call"); + return; + } + + req = g_new(struct controlpoint_req, 1); + req->csc = csc; + req->reply_id = id; + req->opcode = UPDATE_SENSOR_LOC; + req->pending_location = loc_val; + + csc->pending_req = req; + + att_val[0] = UPDATE_SENSOR_LOC; + att_val[1] = loc_val; + + gatt_write_char(csc->attrib, csc->controlpoint_val_handle, att_val, + sizeof(att_val), controlpoint_write_cb, req); +} + +static gboolean property_exists_location(const GDBusPropertyTable *property, + void *data) +{ + struct csc *csc = data; + + return csc->has_location; +} + +static gboolean property_get_locations(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct csc *csc = data; + DBusMessageIter entry; + int i; + + if (!(csc->feature & MULTI_SENSOR_LOC_SUPPORT)) + return FALSE; + + dbus_message_iter_open_container(iter, DBUS_TYPE_ARRAY, + DBUS_TYPE_STRING_AS_STRING, &entry); + for (i = 0; i < csc->num_locations; i++) { + char *loc = g_strdup(location2str(csc->locations[i])); + dbus_message_iter_append_basic(&entry, DBUS_TYPE_STRING, &loc); + g_free(loc); + } + + dbus_message_iter_close_container(iter, &entry); + + return TRUE; +} + +static gboolean property_exists_locations(const GDBusPropertyTable *property, + void *data) +{ + struct csc *csc = data; + + return !!(csc->feature & MULTI_SENSOR_LOC_SUPPORT); +} + +static gboolean property_get_wheel_rev_sup(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct csc *csc = data; + dbus_bool_t val; + + val = !!(csc->feature & WHEEL_REV_SUPPORT); + dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &val); + + return TRUE; +} + +static gboolean property_get_multi_loc_sup(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct csc *csc = data; + dbus_bool_t val; + + val = !!(csc->feature & MULTI_SENSOR_LOC_SUPPORT); + dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &val); + + return TRUE; +} + +static const GDBusPropertyTable cyclingspeed_device_properties[] = { + { "Location", "s", property_get_location, property_set_location, + property_exists_location }, + { "SupportedLocations", "as", property_get_locations, NULL, + property_exists_locations }, + { "WheelRevolutionDataSupported", "b", property_get_wheel_rev_sup }, + { "MultipleLocationsSupported", "b", property_get_multi_loc_sup }, + { } +}; + +static DBusMessage *set_cumulative_wheel_rev(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct csc *csc = data; + dbus_uint32_t value; + struct controlpoint_req *req; + uint8_t att_val[5]; /* uint8 opcode + uint32 value */ + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT32, &value, + DBUS_TYPE_INVALID)) + return btd_error_invalid_args(msg); + + if (csc->pending_req != NULL) + return btd_error_in_progress(msg); + + req = g_new(struct controlpoint_req, 1); + req->csc = csc; + req->opcode = SET_CUMULATIVE_VALUE; + req->msg = dbus_message_ref(msg); + + csc->pending_req = req; + + att_val[0] = SET_CUMULATIVE_VALUE; + put_le32(value, att_val + 1); + + gatt_write_char(csc->attrib, csc->controlpoint_val_handle, att_val, + sizeof(att_val), controlpoint_write_cb, req); + + return NULL; +} + +static const GDBusMethodTable cyclingspeed_device_methods[] = { + { GDBUS_ASYNC_METHOD("SetCumulativeWheelRevolutions", + GDBUS_ARGS({ "value", "u" }), NULL, + set_cumulative_wheel_rev) }, + { } +}; + +static int csc_device_probe(struct btd_service *service) +{ + struct btd_device *device = btd_service_get_device(service); + struct btd_adapter *adapter; + struct csc_adapter *cadapter; + struct csc *csc; + struct gatt_primary *prim; + + prim = btd_device_get_primary(device, CYCLING_SC_UUID); + if (prim == NULL) + return -EINVAL; + + adapter = device_get_adapter(device); + + cadapter = find_csc_adapter(adapter); + if (cadapter == NULL) + return -1; + + csc = g_new0(struct csc, 1); + csc->dev = btd_device_ref(device); + csc->cadapter = cadapter; + + if (!g_dbus_register_interface(btd_get_dbus_connection(), + device_get_path(device), + CYCLINGSPEED_INTERFACE, + cyclingspeed_device_methods, + NULL, + cyclingspeed_device_properties, + csc, destroy_csc)) { + error("D-Bus failed to register %s interface", + CYCLINGSPEED_INTERFACE); + destroy_csc(csc); + return -EIO; + } + + csc->svc_range = g_new0(struct att_range, 1); + csc->svc_range->start = prim->range.start; + csc->svc_range->end = prim->range.end; + + cadapter->devices = g_slist_prepend(cadapter->devices, csc); + + csc->attioid = btd_device_add_attio_callback(device, attio_connected_cb, + attio_disconnected_cb, csc); + + return 0; +} + +static void csc_device_remove(struct btd_service *service) +{ + struct btd_device *device = btd_service_get_device(service); + struct btd_adapter *adapter; + struct csc_adapter *cadapter; + struct csc *csc; + GSList *l; + + adapter = device_get_adapter(device); + + cadapter = find_csc_adapter(adapter); + if (cadapter == NULL) + return; + + l = g_slist_find_custom(cadapter->devices, device, cmp_device); + if (l == NULL) + return; + + csc = l->data; + + cadapter->devices = g_slist_remove(cadapter->devices, csc); + + g_dbus_unregister_interface(btd_get_dbus_connection(), + device_get_path(device), + CYCLINGSPEED_INTERFACE); +} + +static struct btd_profile cscp_profile = { + .name = "Cycling Speed and Cadence GATT Driver", + .remote_uuid = CYCLING_SC_UUID, + + .adapter_probe = csc_adapter_probe, + .adapter_remove = csc_adapter_remove, + + .device_probe = csc_device_probe, + .device_remove = csc_device_remove, +}; + +static int cyclingspeed_init(void) +{ + return btd_profile_register(&cscp_profile); +} + +static void cyclingspeed_exit(void) +{ + btd_profile_unregister(&cscp_profile); +} + +BLUETOOTH_PLUGIN_DEFINE(cyclingspeed, VERSION, + BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, + cyclingspeed_init, cyclingspeed_exit) diff --git a/profiles/gap/gas.c b/profiles/gap/gas.c index 47c8c257..ac9d4ac2 100755 --- a/profiles/gap/gas.c +++ b/profiles/gap/gas.c @@ -154,6 +154,40 @@ static void handle_appearance(struct gas *gas, uint16_t value_handle) DBG("Failed to send request to read appearance"); } +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +static void read_rpa_res_characteristic_value_cb(bool success, uint8_t att_ecode, + const uint8_t *value, uint16_t length, + void *user_data) +{ + struct gas *gas = user_data; + uint8_t rpa_res_support; + + if (!success) { + DBG("Reading RPA Resolution Char Value failed with ATT error: %u", att_ecode); + return; + } + + /* The RPA Resolution Char Value value is a 8-bit unsigned integer */ + if (length != 1) { + DBG("Malformed RPA resolution char value"); + return; + } + + rpa_res_support = *value; + + DBG("GAP RPA Resolution Char Value: %d", rpa_res_support); + + device_set_rpa_res_char_value(gas->device, rpa_res_support); +} + +static void handle_rpa_res_characteristic_value(struct gas *gas, uint16_t value_handle) +{ + if (!bt_gatt_client_read_value(gas->client, value_handle, + read_rpa_res_characteristic_value_cb, gas, NULL)) + DBG("Failed to send request to read RPA resolution Char Value"); +} +#endif + static bool uuid_cmp(uint16_t u16, const bt_uuid_t *uuid) { bt_uuid_t lhs; @@ -180,6 +214,10 @@ static void handle_characteristic(struct gatt_db_attribute *attr, handle_device_name(gas, value_handle); else if (uuid_cmp(GATT_CHARAC_APPEARANCE, &uuid)) handle_appearance(gas, value_handle); +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + else if (uuid_cmp(GATT_CHARAC_CENTRAL_RPA_RESOLUTION, &uuid)) + handle_rpa_res_characteristic_value(gas, value_handle); +#endif else { char uuid_str[MAX_LEN_UUID_STR]; diff --git a/profiles/heartrate/heartrate.c b/profiles/heartrate/heartrate.c new file mode 100755 index 00000000..9e8c4993 --- /dev/null +++ b/profiles/heartrate/heartrate.c @@ -0,0 +1,870 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012 Tieto Poland + * + * 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 <errno.h> +#include <stdbool.h> +#include <glib.h> + +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/uuid.h" + +#include "gdbus/gdbus.h" + +#include "src/plugin.h" +#include "src/adapter.h" +#include "src/dbus-common.h" +#include "src/device.h" +#include "src/profile.h" +#include "src/shared/util.h" +#include "src/service.h" +#include "src/error.h" +#include "attrib/gattrib.h" +#include "attrib/att.h" +#include "attrib/gatt.h" +#include "src/attio.h" +#include "src/log.h" + +#define HEART_RATE_INTERFACE "org.bluez.HeartRate1" +#define HEART_RATE_MANAGER_INTERFACE "org.bluez.HeartRateManager1" +#define HEART_RATE_WATCHER_INTERFACE "org.bluez.HeartRateWatcher1" + +#define HR_VALUE_FORMAT 0x01 +#define SENSOR_CONTACT_DETECTED 0x02 +#define SENSOR_CONTACT_SUPPORT 0x04 +#define ENERGY_EXP_STATUS 0x08 +#define RR_INTERVAL 0x10 + +struct heartrate_adapter { + struct btd_adapter *adapter; + GSList *devices; + GSList *watchers; +}; + +struct heartrate { + struct btd_device *dev; + struct heartrate_adapter *hradapter; + GAttrib *attrib; + guint attioid; + guint attionotid; + + struct att_range *svc_range; /* primary svc range */ + + uint16_t measurement_ccc_handle; + uint16_t hrcp_val_handle; + + gboolean has_location; + uint8_t location; +}; + +struct watcher { + struct heartrate_adapter *hradapter; + guint id; + char *srv; + char *path; +}; + +struct measurement { + struct heartrate *hr; + uint16_t value; + gboolean has_energy; + uint16_t energy; + gboolean has_contact; + gboolean contact; + uint16_t num_interval; + uint16_t *interval; +}; + +static GSList *heartrate_adapters = NULL; + +static const char * const location_enum[] = { + "other", + "chest", + "wrist", + "finger", + "hand", + "earlobe", + "foot", +}; + +static const char *location2str(uint8_t value) +{ + if (value < G_N_ELEMENTS(location_enum)) + return location_enum[value]; + + error("Body Sensor Location [%d] is RFU", value); + + return NULL; +} + +static int cmp_adapter(gconstpointer a, gconstpointer b) +{ + const struct heartrate_adapter *hradapter = a; + const struct btd_adapter *adapter = b; + + if (adapter == hradapter->adapter) + return 0; + + return -1; +} + +static int cmp_device(gconstpointer a, gconstpointer b) +{ + const struct heartrate *hr = a; + const struct btd_device *dev = b; + + if (dev == hr->dev) + return 0; + + return -1; +} + +static int cmp_watcher(gconstpointer a, gconstpointer b) +{ + const struct watcher *watcher = a; + const struct watcher *match = b; + int ret; + + ret = g_strcmp0(watcher->srv, match->srv); + if (ret != 0) + return ret; + + return g_strcmp0(watcher->path, match->path); +} + +static struct heartrate_adapter * +find_heartrate_adapter(struct btd_adapter *adapter) +{ + GSList *l = g_slist_find_custom(heartrate_adapters, adapter, + cmp_adapter); + if (!l) + return NULL; + + return l->data; +} + +static void destroy_watcher(gpointer user_data) +{ + struct watcher *watcher = user_data; + + g_free(watcher->path); + g_free(watcher->srv); + g_free(watcher); +} + +static struct watcher *find_watcher(GSList *list, const char *sender, + const char *path) +{ + struct watcher *match; + GSList *l; + + match = g_new0(struct watcher, 1); + match->srv = g_strdup(sender); + match->path = g_strdup(path); + + l = g_slist_find_custom(list, match, cmp_watcher); + destroy_watcher(match); + + if (l != NULL) + return l->data; + + return NULL; +} + +static void destroy_heartrate(gpointer user_data) +{ + struct heartrate *hr = user_data; + + if (hr->attioid > 0) + btd_device_remove_attio_callback(hr->dev, hr->attioid); + + if (hr->attrib != NULL) { + g_attrib_unregister(hr->attrib, hr->attionotid); + g_attrib_unref(hr->attrib); + } + + btd_device_unref(hr->dev); + g_free(hr->svc_range); + g_free(hr); +} + +static void remove_watcher(gpointer user_data) +{ + struct watcher *watcher = user_data; + + g_dbus_remove_watch(btd_get_dbus_connection(), watcher->id); +} + +static void destroy_heartrate_adapter(gpointer user_data) +{ + struct heartrate_adapter *hradapter = user_data; + + g_slist_free_full(hradapter->watchers, remove_watcher); + + g_free(hradapter); +} + +static void read_sensor_location_cb(guint8 status, const guint8 *pdu, + guint16 len, gpointer user_data) +{ + struct heartrate *hr = user_data; + uint8_t value; + ssize_t vlen; + + if (status != 0) { + error("Body Sensor Location read failed: %s", + att_ecode2str(status)); + return; + } + + vlen = dec_read_resp(pdu, len, &value, sizeof(value)); + if (vlen < 0) { + error("Protocol error"); + return; + } + + if (vlen != sizeof(value)) { + error("Invalid length for Body Sensor Location"); + return; + } + + hr->has_location = TRUE; + hr->location = value; +} + +static void char_write_cb(guint8 status, const guint8 *pdu, guint16 len, + gpointer user_data) +{ + char *msg = user_data; + + if (status != 0) + error("%s failed", msg); + + g_free(msg); +} + +static void update_watcher(gpointer data, gpointer user_data) +{ + struct watcher *w = data; + struct measurement *m = user_data; + struct heartrate *hr = m->hr; + const char *path = device_get_path(hr->dev); + DBusMessageIter iter; + DBusMessageIter dict; + DBusMessage *msg; + + msg = dbus_message_new_method_call(w->srv, w->path, + HEART_RATE_WATCHER_INTERFACE, "MeasurementReceived"); + if (msg == NULL) + return; + + dbus_message_iter_init_append(msg, &iter); + + dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH , &path); + + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); + + dict_append_entry(&dict, "Value", DBUS_TYPE_UINT16, &m->value); + + if (m->has_energy) + dict_append_entry(&dict, "Energy", DBUS_TYPE_UINT16, + &m->energy); + + if (m->has_contact) + dict_append_entry(&dict, "Contact", DBUS_TYPE_BOOLEAN, + &m->contact); + + if (m->num_interval > 0) + dict_append_array(&dict, "Interval", DBUS_TYPE_UINT16, + &m->interval, m->num_interval); + + dbus_message_iter_close_container(&iter, &dict); + + dbus_message_set_no_reply(msg, TRUE); + g_dbus_send_message(btd_get_dbus_connection(), msg); +} + +static void process_measurement(struct heartrate *hr, const uint8_t *pdu, + uint16_t len) +{ + struct measurement m; + uint8_t flags; + + flags = *pdu; + + pdu++; + len--; + + memset(&m, 0, sizeof(m)); + + if (flags & HR_VALUE_FORMAT) { + if (len < 2) { + error("Heart Rate Measurement field missing"); + return; + } + + m.value = get_le16(pdu); + pdu += 2; + len -= 2; + } else { + if (len < 1) { + error("Heart Rate Measurement field missing"); + return; + } + + m.value = *pdu; + pdu++; + len--; + } + + if (flags & ENERGY_EXP_STATUS) { + if (len < 2) { + error("Energy Expended field missing"); + return; + } + + m.has_energy = TRUE; + m.energy = get_le16(pdu); + pdu += 2; + len -= 2; + } + + if (flags & RR_INTERVAL) { + int i; + + if (len == 0 || (len % 2 != 0)) { + error("RR-Interval field malformed"); + return; + } + + m.num_interval = len / 2; + m.interval = g_new(uint16_t, m.num_interval); + + for (i = 0; i < m.num_interval; pdu += 2, i++) + m.interval[i] = get_le16(pdu); + } + + if (flags & SENSOR_CONTACT_SUPPORT) { + m.has_contact = TRUE; + m.contact = !!(flags & SENSOR_CONTACT_DETECTED); + } + + /* Notify all registered watchers */ + m.hr = hr; + g_slist_foreach(hr->hradapter->watchers, update_watcher, &m); + + g_free(m.interval); +} + +static void notify_handler(const uint8_t *pdu, uint16_t len, gpointer user_data) +{ + struct heartrate *hr = user_data; + + /* should be at least opcode (1b) + handle (2b) */ + if (len < 3) { + error("Invalid PDU received"); + return; + } + + process_measurement(hr, pdu + 3, len - 3); +} + +static void discover_ccc_cb(uint8_t status, GSList *descs, void *user_data) +{ + struct heartrate *hr = user_data; + struct gatt_desc *desc; + uint8_t attr_val[2]; + char *msg; + + if (status != 0) { + error("Discover Heart Rate Measurement descriptors failed: %s", + att_ecode2str(status)); + return; + } + + /* There will be only one descriptor on list and it will be CCC */ + desc = descs->data; + + hr->measurement_ccc_handle = desc->handle; + + if (g_slist_length(hr->hradapter->watchers) == 0) { + put_le16(0x0000, attr_val); + msg = g_strdup("Disable measurement"); + } else { + put_le16(GATT_CLIENT_CHARAC_CFG_NOTIF_BIT, attr_val); + msg = g_strdup("Enable measurement"); + } + + gatt_write_char(hr->attrib, desc->handle, attr_val, sizeof(attr_val), + char_write_cb, msg); +} + +static void discover_measurement_ccc(struct heartrate *hr, + struct gatt_char *c, struct gatt_char *c_next) +{ + uint16_t start, end; + bt_uuid_t uuid; + + start = c->value_handle + 1; + + if (c_next != NULL) { + if (start == c_next->handle) + return; + end = c_next->handle - 1; + } else if (c->value_handle != hr->svc_range->end) { + end = hr->svc_range->end; + } else { + return; + } + + bt_uuid16_create(&uuid, GATT_CLIENT_CHARAC_CFG_UUID); + + gatt_discover_desc(hr->attrib, start, end, &uuid, discover_ccc_cb, hr); +} + +static void discover_char_cb(uint8_t status, GSList *chars, void *user_data) +{ + struct heartrate *hr = user_data; + + if (status) { + error("Discover HRS characteristics failed: %s", + att_ecode2str(status)); + return; + } + + for (; chars; chars = chars->next) { + struct gatt_char *c = chars->data; + + if (g_strcmp0(c->uuid, HEART_RATE_MEASUREMENT_UUID) == 0) { + struct gatt_char *c_next = + (chars->next ? chars->next->data : NULL); + + hr->attionotid = g_attrib_register(hr->attrib, + ATT_OP_HANDLE_NOTIFY, + c->value_handle, + notify_handler, hr, NULL); + + discover_measurement_ccc(hr, c, c_next); + } else if (g_strcmp0(c->uuid, BODY_SENSOR_LOCATION_UUID) == 0) { + DBG("Body Sensor Location supported"); + + gatt_read_char(hr->attrib, c->value_handle, + read_sensor_location_cb, hr); + } else if (g_strcmp0(c->uuid, + HEART_RATE_CONTROL_POINT_UUID) == 0) { + DBG("Heart Rate Control Point supported"); + hr->hrcp_val_handle = c->value_handle; + } + } +} + +static void enable_measurement(gpointer data, gpointer user_data) +{ + struct heartrate *hr = data; + uint16_t handle = hr->measurement_ccc_handle; + uint8_t value[2]; + char *msg; + + if (hr->attrib == NULL || !handle) + return; + + put_le16(GATT_CLIENT_CHARAC_CFG_NOTIF_BIT, value); + msg = g_strdup("Enable measurement"); + + gatt_write_char(hr->attrib, handle, value, sizeof(value), + char_write_cb, msg); +} + +static void disable_measurement(gpointer data, gpointer user_data) +{ + struct heartrate *hr = data; + uint16_t handle = hr->measurement_ccc_handle; + uint8_t value[2]; + char *msg; + + if (hr->attrib == NULL || !handle) + return; + + put_le16(0x0000, value); + msg = g_strdup("Disable measurement"); + + gatt_write_char(hr->attrib, handle, value, sizeof(value), + char_write_cb, msg); +} + +static void attio_connected_cb(GAttrib *attrib, gpointer user_data) +{ + struct heartrate *hr = user_data; + + DBG(""); + + hr->attrib = g_attrib_ref(attrib); + + gatt_discover_char(hr->attrib, hr->svc_range->start, hr->svc_range->end, + NULL, discover_char_cb, hr); +} + +static void attio_disconnected_cb(gpointer user_data) +{ + struct heartrate *hr = user_data; + + DBG(""); + + if (hr->attionotid > 0) { + g_attrib_unregister(hr->attrib, hr->attionotid); + hr->attionotid = 0; + } + + g_attrib_unref(hr->attrib); + hr->attrib = NULL; +} + +static void watcher_exit_cb(DBusConnection *conn, void *user_data) +{ + struct watcher *watcher = user_data; + struct heartrate_adapter *hradapter = watcher->hradapter; + + DBG("heartrate watcher [%s] disconnected", watcher->path); + + hradapter->watchers = g_slist_remove(hradapter->watchers, watcher); + g_dbus_remove_watch(conn, watcher->id); + + if (g_slist_length(hradapter->watchers) == 0) + g_slist_foreach(hradapter->devices, disable_measurement, 0); +} + +static DBusMessage *register_watcher(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct heartrate_adapter *hradapter = data; + struct watcher *watcher; + const char *sender = dbus_message_get_sender(msg); + char *path; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) + return btd_error_invalid_args(msg); + + watcher = find_watcher(hradapter->watchers, sender, path); + if (watcher != NULL) + return btd_error_already_exists(msg); + + watcher = g_new0(struct watcher, 1); + watcher->hradapter = hradapter; + watcher->id = g_dbus_add_disconnect_watch(conn, sender, watcher_exit_cb, + watcher, destroy_watcher); + watcher->srv = g_strdup(sender); + watcher->path = g_strdup(path); + + if (g_slist_length(hradapter->watchers) == 0) + g_slist_foreach(hradapter->devices, enable_measurement, 0); + + hradapter->watchers = g_slist_prepend(hradapter->watchers, watcher); + + DBG("heartrate watcher [%s] registered", path); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *unregister_watcher(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct heartrate_adapter *hradapter = data; + struct watcher *watcher; + const char *sender = dbus_message_get_sender(msg); + char *path; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) + return btd_error_invalid_args(msg); + + watcher = find_watcher(hradapter->watchers, sender, path); + if (watcher == NULL) + return btd_error_does_not_exist(msg); + + hradapter->watchers = g_slist_remove(hradapter->watchers, watcher); + g_dbus_remove_watch(conn, watcher->id); + + if (g_slist_length(hradapter->watchers) == 0) + g_slist_foreach(hradapter->devices, disable_measurement, 0); + + DBG("heartrate watcher [%s] unregistered", path); + + return dbus_message_new_method_return(msg); +} + +static const GDBusMethodTable heartrate_manager_methods[] = { + { GDBUS_METHOD("RegisterWatcher", + GDBUS_ARGS({ "agent", "o" }), NULL, + register_watcher) }, + { GDBUS_METHOD("UnregisterWatcher", + GDBUS_ARGS({ "agent", "o" }), NULL, + unregister_watcher) }, + { } +}; + +static gboolean property_get_location(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct heartrate *hr = data; + char *loc; + + if (!hr->has_location) + return FALSE; + + loc = g_strdup(location2str(hr->location)); + + if (loc == NULL) + return FALSE; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &loc); + + g_free(loc); + + return TRUE; +} + +static gboolean property_exists_location(const GDBusPropertyTable *property, + void *data) +{ + struct heartrate *hr = data; + + if (!hr->has_location || location2str(hr->location) == NULL) + return FALSE; + + return TRUE; +} + +static gboolean property_get_reset_supported(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct heartrate *hr = data; + dbus_bool_t has_reset = !!hr->hrcp_val_handle; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &has_reset); + + return TRUE; +} + +static DBusMessage *hrcp_reset(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct heartrate *hr = data; + uint8_t value; + char *vmsg; + + if (!hr->hrcp_val_handle) + return btd_error_not_supported(msg); + + if (!hr->attrib) + return btd_error_not_available(msg); + + value = 0x01; + vmsg = g_strdup("Reset Control Point"); + gatt_write_char(hr->attrib, hr->hrcp_val_handle, &value, + sizeof(value), char_write_cb, vmsg); + + DBG("Energy Expended Value has been reset"); + + return dbus_message_new_method_return(msg); +} + +static const GDBusMethodTable heartrate_device_methods[] = { + { GDBUS_METHOD("Reset", NULL, NULL, hrcp_reset) }, + { } +}; + +static const GDBusPropertyTable heartrate_device_properties[] = { + { "Location", "s", property_get_location, NULL, + property_exists_location }, + { "ResetSupported", "b", property_get_reset_supported }, + { } +}; + +static int heartrate_adapter_register(struct btd_adapter *adapter) +{ + struct heartrate_adapter *hradapter; + + hradapter = g_new0(struct heartrate_adapter, 1); + hradapter->adapter = adapter; + + if (!g_dbus_register_interface(btd_get_dbus_connection(), + adapter_get_path(adapter), + HEART_RATE_MANAGER_INTERFACE, + heartrate_manager_methods, + NULL, NULL, hradapter, + destroy_heartrate_adapter)) { + error("D-Bus failed to register %s interface", + HEART_RATE_MANAGER_INTERFACE); + destroy_heartrate_adapter(hradapter); + return -EIO; + } + + heartrate_adapters = g_slist_prepend(heartrate_adapters, hradapter); + + return 0; +} + +static void heartrate_adapter_unregister(struct btd_adapter *adapter) +{ + struct heartrate_adapter *hradapter; + + hradapter = find_heartrate_adapter(adapter); + if (hradapter == NULL) + return; + + heartrate_adapters = g_slist_remove(heartrate_adapters, hradapter); + + g_dbus_unregister_interface(btd_get_dbus_connection(), + adapter_get_path(hradapter->adapter), + HEART_RATE_MANAGER_INTERFACE); +} + +static int heartrate_device_register(struct btd_device *device, + struct gatt_primary *prim) +{ + struct btd_adapter *adapter; + struct heartrate_adapter *hradapter; + struct heartrate *hr; + + adapter = device_get_adapter(device); + + hradapter = find_heartrate_adapter(adapter); + + if (hradapter == NULL) + return -1; + + hr = g_new0(struct heartrate, 1); + hr->dev = btd_device_ref(device); + hr->hradapter = hradapter; + + if (!g_dbus_register_interface(btd_get_dbus_connection(), + device_get_path(device), + HEART_RATE_INTERFACE, + heartrate_device_methods, + NULL, + heartrate_device_properties, + hr, destroy_heartrate)) { + error("D-Bus failed to register %s interface", + HEART_RATE_INTERFACE); + destroy_heartrate(hr); + return -EIO; + } + + hr->svc_range = g_new0(struct att_range, 1); + hr->svc_range->start = prim->range.start; + hr->svc_range->end = prim->range.end; + + hradapter->devices = g_slist_prepend(hradapter->devices, hr); + + hr->attioid = btd_device_add_attio_callback(device, attio_connected_cb, + attio_disconnected_cb, hr); + + return 0; +} + +static void heartrate_device_unregister(struct btd_device *device) +{ + struct btd_adapter *adapter; + struct heartrate_adapter *hradapter; + struct heartrate *hr; + GSList *l; + + adapter = device_get_adapter(device); + + hradapter = find_heartrate_adapter(adapter); + if (hradapter == NULL) + return; + + l = g_slist_find_custom(hradapter->devices, device, cmp_device); + if (l == NULL) + return; + + hr = l->data; + + hradapter->devices = g_slist_remove(hradapter->devices, hr); + + g_dbus_unregister_interface(btd_get_dbus_connection(), + device_get_path(device), HEART_RATE_INTERFACE); +} + +static int heartrate_adapter_probe(struct btd_profile *p, + struct btd_adapter *adapter) +{ + return heartrate_adapter_register(adapter); +} + +static void heartrate_adapter_remove(struct btd_profile *p, + struct btd_adapter *adapter) +{ + heartrate_adapter_unregister(adapter); +} + +static int heartrate_device_probe(struct btd_service *service) +{ + struct btd_device *device = btd_service_get_device(service); + struct gatt_primary *prim; + + prim = btd_device_get_primary(device, HEART_RATE_UUID); + if (prim == NULL) + return -EINVAL; + + return heartrate_device_register(device, prim); +} + +static void heartrate_device_remove(struct btd_service *service) +{ + struct btd_device *device = btd_service_get_device(service); + + heartrate_device_unregister(device); +} + +static struct btd_profile hrp_profile = { + .name = "Heart Rate GATT Driver", + .remote_uuid = HEART_RATE_UUID, + + .device_probe = heartrate_device_probe, + .device_remove = heartrate_device_remove, + + .adapter_probe = heartrate_adapter_probe, + .adapter_remove = heartrate_adapter_remove, +}; + +static int heartrate_init(void) +{ + return btd_profile_register(&hrp_profile); +} + +static void heartrate_exit(void) +{ + btd_profile_unregister(&hrp_profile); +} + +BLUETOOTH_PLUGIN_DEFINE(heartrate, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, + heartrate_init, heartrate_exit) diff --git a/profiles/input/device.c b/profiles/input/device.c index a494ea2e..ff553cb3 100755 --- a/profiles/input/device.c +++ b/profiles/input/device.c @@ -87,6 +87,9 @@ struct input_device { uint8_t report_req_pending; guint report_req_timer; uint32_t report_rsp_id; +#ifdef TIZEN_BT_HID_DEVICE_ENABLE + char *role; +#endif }; static int idle_timeout = 0; @@ -333,8 +336,12 @@ static gboolean intr_watch_cb(GIOChannel *chan, GIOCondition cond, gpointer data btd_service_disconnecting_complete(idev->service, 0); /* Enter the auto-reconnect mode if needed */ +#ifdef TIZEN_BT_HID_DEVICE_ENABLE + if (idev->role == NULL) + input_device_enter_reconnect_mode(idev); +#else input_device_enter_reconnect_mode(idev); - +#endif return FALSE; } @@ -1002,10 +1009,21 @@ cleanup: static bool is_connected(struct input_device *idev) { +#ifdef TIZEN_BT_HID_DEVICE_ENABLE + if (idev->role == NULL) { + if (idev->uhid) + return (idev->intr_io != NULL && idev->ctrl_io != NULL); + else + return ioctl_is_connected(idev); + } else { + return (idev->intr_io != NULL && idev->ctrl_io != NULL); + } +#else if (idev->uhid) return (idev->intr_io != NULL && idev->ctrl_io != NULL); else return ioctl_is_connected(idev); +#endif } static int connection_disconnect(struct input_device *idev, uint32_t flags) @@ -1019,6 +1037,10 @@ static int connection_disconnect(struct input_device *idev, uint32_t flags) if (idev->ctrl_io) g_io_channel_shutdown(idev->ctrl_io, TRUE, NULL); +#ifdef TIZEN_BT_HID_DEVICE_ENABLE + if (idev->role != NULL) + btd_service_disconnecting_complete(idev->service, 0); +#endif if (idev->uhid) return 0; else @@ -1031,10 +1053,17 @@ static int input_device_connected(struct input_device *idev) if (idev->intr_io == NULL || idev->ctrl_io == NULL) return -ENOTCONN; - +#ifdef TIZEN_BT_HID_DEVICE_ENABLE + if (idev->role == NULL) { + err = hidp_add_connection(idev); + if (err < 0) + return err; + } +#else err = hidp_add_connection(idev); if (err < 0) return err; +#endif btd_service_connecting_complete(idev->service, 0); @@ -1052,11 +1081,19 @@ static void interrupt_connect_cb(GIOChannel *chan, GError *conn_err, err = -EIO; goto failed; } - +#ifdef TIZEN_BT_HID_DEVICE_ENABLE + if (idev->role == NULL) { + err = input_device_connected(idev); + if (err < 0) + goto failed; + } else { + btd_service_connecting_complete(idev->service, 0); + } +#else err = input_device_connected(idev); if (err < 0) goto failed; - +#endif if (idev->uhid) cond |= G_IO_IN; @@ -1230,7 +1267,9 @@ int input_device_connect(struct btd_service *service) DBG(""); idev = btd_service_get_user_data(service); - +#ifdef TIZEN_BT_HID_DEVICE_ENABLE + DBG("Role=%s", idev->role); +#endif if (idev->ctrl_io) return -EBUSY; @@ -1251,7 +1290,9 @@ int input_device_disconnect(struct btd_service *service) flags = device_is_temporary(idev->device) ? (1 << HIDP_VIRTUAL_CABLE_UNPLUG) : 0; - +#ifdef TIZEN_BT_HID_DEVICE_ENABLE + DBG("Role=%s", idev->role); +#endif err = connection_disconnect(idev, flags); if (err < 0) return err; @@ -1328,6 +1369,27 @@ static struct input_device *input_device_new(struct btd_service *service) return idev; } +#ifdef TIZEN_BT_HID_DEVICE_ENABLE +static struct input_device *input_device_role_new(struct btd_service *service) +{ + struct btd_device *device = btd_service_get_device(service); + const char *path = device_get_path(device); + struct btd_adapter *adapter = device_get_adapter(device); + struct input_device *idev; + + idev = g_new0(struct input_device, 1); + bacpy(&idev->src, btd_adapter_get_address(adapter)); + bacpy(&idev->dst, device_get_address(device)); + idev->service = btd_service_ref(service); + idev->device = btd_device_ref(device); + idev->path = g_strdup(path); + idev->role = g_strdup("device"); + idev->disable_sdp = 0; + idev->uhid = NULL; + return idev; +} +#endif + static gboolean property_get_reconnect_mode( const GDBusPropertyTable *property, DBusMessageIter *iter, void *data) @@ -1345,6 +1407,37 @@ static const GDBusPropertyTable input_properties[] = { { } }; +#ifdef TIZEN_BT_HID_DEVICE_ENABLE +static DBusMessage *hid_device_fd(DBusConnection *conn, + DBusMessage *msg, void *user_data) +{ + struct input_device *idev = user_data; + GError *gerr = NULL; + DBusMessage *reply; + int ctrl_fd = -1; + int intr_fd = -1; + if (idev->ctrl_io == NULL || idev->intr_io == NULL) { + DBG("Return error reply"); + reply = g_dbus_create_error(msg, ERROR_INTERFACE ".InputError", + "%s", "NotConnected"); + g_error_free(gerr); + } else { + ctrl_fd = g_io_channel_unix_get_fd(idev->ctrl_io); + intr_fd = g_io_channel_unix_get_fd(idev->intr_io); + reply = g_dbus_create_reply(msg, DBUS_TYPE_UNIX_FD, + &ctrl_fd, DBUS_TYPE_UNIX_FD, &intr_fd ,DBUS_TYPE_INVALID); + } + + return reply; +} +static const GDBusMethodTable input_device_methods[] = { + { GDBUS_ASYNC_METHOD("GetFD", + NULL, GDBUS_ARGS({ "fd", "h" } , {"fd", "h"}), + hid_device_fd) }, + { } +}; +#endif + int input_device_register(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); @@ -1381,6 +1474,34 @@ int input_device_register(struct btd_service *service) return 0; } +#ifdef TIZEN_BT_HID_DEVICE_ENABLE +int input_device_role_register(struct btd_service *service) +{ + struct btd_device *device = btd_service_get_device(service); + const char *path = device_get_path(device); + struct input_device *idev; + + DBG("%s", path); + + idev = input_device_role_new(service); + if (!idev) + return -EINVAL; + if (g_dbus_register_interface(btd_get_dbus_connection(), + idev->path, INPUT_INTERFACE, + input_device_methods, NULL, + NULL, idev, + NULL) == FALSE) { + error("Unable to register %s interface", INPUT_INTERFACE); + input_device_free(idev); + return -EINVAL; + } + btd_service_set_user_data(service, idev); + + return 0; +} + +#endif + static struct input_device *find_device(const bdaddr_t *src, const bdaddr_t *dst) { @@ -1398,6 +1519,25 @@ static struct input_device *find_device(const bdaddr_t *src, return btd_service_get_user_data(service); } +#ifdef TIZEN_BT_HID_DEVICE_ENABLE +static struct input_device *find_device_role(const bdaddr_t *src, + const bdaddr_t *dst) +{ + struct btd_device *device; + struct btd_service *service; + + device = btd_adapter_find_device(adapter_find(src), dst, BDADDR_BREDR); + if (device == NULL) + return NULL; + + service = btd_device_get_service(device, HID_DEVICE_UUID); + if (service == NULL) + return NULL; + + return btd_service_get_user_data(service); +} +#endif + void input_device_unregister(struct btd_service *service) { struct btd_device *device = btd_service_get_device(service); @@ -1412,6 +1552,19 @@ void input_device_unregister(struct btd_service *service) input_device_free(idev); } +#ifdef TIZEN_BT_HID_DEVICE_ENABLE +void input_device_role_unregister(struct btd_service *service) +{ + struct btd_device *device = btd_service_get_device(service); + const char *path = device_get_path(device); + struct input_device *idev = btd_service_get_user_data(service); + + DBG("%s", path); + + input_device_free(idev); +} +#endif + static int input_device_connadd(struct input_device *idev) { int err; @@ -1443,6 +1596,16 @@ bool input_device_exists(const bdaddr_t *src, const bdaddr_t *dst) return false; } +#ifdef TIZEN_BT_HID_DEVICE_ENABLE +bool input_device_role_exists(const bdaddr_t *src, const bdaddr_t *dst) +{ + if (find_device_role(src, dst)) + return true; + + return false; +} +#endif + int input_device_set_channel(const bdaddr_t *src, const bdaddr_t *dst, int psm, GIOChannel *io) { @@ -1480,6 +1643,58 @@ int input_device_set_channel(const bdaddr_t *src, const bdaddr_t *dst, int psm, return 0; } +#ifdef TIZEN_BT_HID_DEVICE_ENABLE +int input_device_role_set_channel(const bdaddr_t *src, const bdaddr_t *dst, int psm, + GIOChannel *io) +{ + struct input_device *idev = find_device_role(src, dst); + GIOCondition cond = G_IO_HUP | G_IO_ERR | G_IO_NVAL; + + DBG("idev %p psm %d", idev, psm); + + if (!idev) + return -ENOENT; + + switch (psm) { + case L2CAP_PSM_HIDP_CTRL: + if (idev->ctrl_io) + return -EALREADY; + idev->ctrl_io = g_io_channel_ref(io); + idev->ctrl_watch = g_io_add_watch(idev->ctrl_io, cond, + ctrl_watch_cb, idev); + break; + case L2CAP_PSM_HIDP_INTR: + if (idev->intr_io) + return -EALREADY; + idev->intr_io = g_io_channel_ref(io); + idev->intr_watch = g_io_add_watch(idev->intr_io, cond, + intr_watch_cb, idev); + break; + } + if (idev->intr_io && idev->ctrl_io) { + btd_service_connecting_complete(idev->service, 0); + } + return 0; +} + +int input_device_role_close_channels(const bdaddr_t *src, const bdaddr_t *dst) +{ + struct input_device *idev = find_device(src, dst); + + if (!idev) + return -ENOENT; + + if (idev->intr_io) + g_io_channel_shutdown(idev->intr_io, TRUE, NULL); + + if (idev->ctrl_io) + g_io_channel_shutdown(idev->ctrl_io, TRUE, NULL); + + return 0; +} + +#endif + int input_device_close_channels(const bdaddr_t *src, const bdaddr_t *dst) { struct input_device *idev = find_device(src, dst); diff --git a/profiles/input/device.h b/profiles/input/device.h index 51a9aee1..20aba315 100755 --- a/profiles/input/device.h +++ b/profiles/input/device.h @@ -33,6 +33,15 @@ void input_enable_userspace_hid(bool state); int input_device_register(struct btd_service *service); void input_device_unregister(struct btd_service *service); +#ifdef TIZEN_BT_HID_DEVICE_ENABLE +int input_device_role_register(struct btd_service *service); +void input_device_role_unregister(struct btd_service *service); +bool input_device_role_exists(const bdaddr_t *src, const bdaddr_t *dst); +int input_device_role_set_channel(const bdaddr_t *src, const bdaddr_t *dst, int psm, + GIOChannel *io); +int input_device_role_close_channels(const bdaddr_t *src, const bdaddr_t *dst); +#endif + bool input_device_exists(const bdaddr_t *src, const bdaddr_t *dst); int input_device_set_channel(const bdaddr_t *src, const bdaddr_t *dst, int psm, GIOChannel *io); diff --git a/profiles/input/manager.c b/profiles/input/manager.c index 1d31b065..2aaeb153 100755 --- a/profiles/input/manager.c +++ b/profiles/input/manager.c @@ -53,6 +53,19 @@ static void hid_server_remove(struct btd_profile *p, { server_stop(btd_adapter_get_address(adapter)); } +#ifdef TIZEN_BT_HID_DEVICE_ENABLE +static int hid_device_probe(struct btd_profile *p, struct btd_adapter *adapter) +{ + DBG("hid device probe"); + return server_start(btd_adapter_get_address(adapter)); +} + +static void hid_device_remove(struct btd_profile *p, + struct btd_adapter *adapter) +{ + server_stop(btd_adapter_get_address(adapter)); +} +#endif static struct btd_profile input_profile = { .name = "input-hid", @@ -70,6 +83,24 @@ static struct btd_profile input_profile = { .adapter_remove = hid_server_remove, }; +#ifdef TIZEN_BT_HID_DEVICE_ENABLE +static struct btd_profile input_device_profile = { + .name = "hid-device", + .local_uuid = HID_DEVICE_UUID, + .remote_uuid = HID_DEVICE_UUID, + + .auto_connect = false, + .connect = input_device_connect, + .disconnect = input_device_disconnect, + + .device_probe = input_device_role_register, + .device_remove = input_device_role_unregister, + + .adapter_probe = hid_device_probe, + .adapter_remove = hid_device_remove, +}; +#endif + static GKeyFile *load_config_file(const char *file) { GKeyFile *keyfile; @@ -117,7 +148,9 @@ static int input_init(void) } btd_profile_register(&input_profile); - +#ifdef TIZEN_BT_HID_DEVICE_ENABLE + btd_profile_register(&input_device_profile); +#endif if (config) g_key_file_free(config); @@ -127,6 +160,9 @@ static int input_init(void) static void input_exit(void) { btd_profile_unregister(&input_profile); +#ifdef TIZEN_BT_HID_DEVICE_ENABLE + btd_profile_unregister(&input_device_profile); +#endif } BLUETOOTH_PLUGIN_DEFINE(input, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, diff --git a/profiles/input/server.c b/profiles/input/server.c index eb3fcf84..2ee3b9bc 100755 --- a/profiles/input/server.c +++ b/profiles/input/server.c @@ -57,6 +57,9 @@ struct input_server { GIOChannel *ctrl; GIOChannel *intr; struct confirm_data *confirm; +#ifdef TIZEN_BT_HID_DEVICE_ENABLE + char *role; +#endif }; static int server_cmp(gconstpointer s, gconstpointer user_data) @@ -182,7 +185,14 @@ static void connect_event_cb(GIOChannel *chan, GError *err, gpointer data) sixaxis_browse_sdp(&src, &dst, chan, psm); return; } - +#ifdef TIZEN_BT_HID_DEVICE_ENABLE + if (ret == -ENOENT) { + DBG("Connection request for device role"); + ret = input_device_role_set_channel(&src, &dst, psm, chan); + if (ret == 0) + return; + } +#endif error("Refusing input device connect: %s (%d)", strerror(-ret), -ret); /* Send unplug virtual cable to unknown devices */ @@ -208,8 +218,15 @@ static void auth_callback(DBusError *derr, void *user_data) } if (!input_device_exists(&server->src, &confirm->dst) && - !dev_is_sixaxis(&server->src, &confirm->dst)) + !dev_is_sixaxis(&server->src, &confirm->dst)) { +#ifdef TIZEN_BT_HID_DEVICE_ENABLE + if (!input_device_role_exists(&server->src, &confirm->dst)) { + return; + } +#else return; +#endif + } if (!bt_io_accept(confirm->io, connect_event_cb, server, NULL, &err)) { error("bt_io_accept: %s", err->message); @@ -260,8 +277,15 @@ static void confirm_event_cb(GIOChannel *chan, gpointer user_data) } if (!input_device_exists(&src, &dst) && !dev_is_sixaxis(&src, &dst)) { +#ifdef TIZEN_BT_HID_DEVICE_ENABLE + if (!input_device_role_exists(&src, &dst)) { + error("Refusing connection from %s: unknown device", addr); + goto drop; + } +#else error("Refusing connection from %s: unknown device", addr); goto drop; +#endif } server->confirm = g_new0(struct confirm_data, 1); @@ -344,3 +368,59 @@ void server_stop(const bdaddr_t *src) servers = g_slist_remove(servers, server); g_free(server); } + +#ifdef TIZEN_BT_HID_DEVICE_ENABLE +int server_device_start(const bdaddr_t *src) +{ + struct input_server *server; + GError *err = NULL; + + server = g_new0(struct input_server, 1); + bacpy(&server->src, src); + + server->ctrl = bt_io_listen(connect_event_cb, NULL, + server, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, src, + BT_IO_OPT_PSM, L2CAP_PSM_HIDP_CTRL, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, + BT_IO_OPT_INVALID); + if (!server->ctrl) { + error("Failed to listen on control channel"); + } + + server->intr = bt_io_listen(NULL, confirm_event_cb, + server, NULL, &err, + BT_IO_OPT_SOURCE_BDADDR, src, + BT_IO_OPT_PSM, L2CAP_PSM_HIDP_INTR, + BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW, + BT_IO_OPT_INVALID); + if (!server->intr) { + error("Failed to listen on interrupt channel"); + } + server->role = strdup("device"); + servers = g_slist_append(servers, server); + + return 0; +} + +void server_device_stop(const bdaddr_t *src) +{ + struct input_server *server; + GSList *l; + + l = g_slist_find_custom(servers, src, server_cmp); + if (!l) + return; + + server = l->data; + + g_io_channel_shutdown(server->intr, TRUE, NULL); + g_io_channel_unref(server->intr); + + g_io_channel_shutdown(server->ctrl, TRUE, NULL); + g_io_channel_unref(server->ctrl); + g_free(server->role); + servers = g_slist_remove(servers, server); + g_free(server); +} +#endif diff --git a/profiles/network/bnep.c b/profiles/network/bnep.c index 9bf0b187..86d1eb96 100755 --- a/profiles/network/bnep.c +++ b/profiles/network/bnep.c @@ -54,6 +54,19 @@ static int ctl; +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +/* Compatibility with old ioctls */ +#define OLD_BNEPCONADD 1 +#define OLD_BNEPCONDEL 2 +#define OLD_BNEPGETCONLIST 3 +#define OLD_BNEPGETCONINFO 4 + +static unsigned long bnepconnadd; +static unsigned long bnepconndel; +static unsigned long bnepgetconnlist; +static unsigned long bnepgetconninfo; +#endif + struct __service_16 { uint16_t dst; uint16_t src; @@ -88,7 +101,30 @@ int bnep_init(void) return err; } - +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +/* Temporary ioctl compatibility hack */ +{ + struct bnep_connlist_req req; + struct bnep_conninfo ci[1]; + + req.cnum = 1; + req.ci = ci; + + if (!ioctl(ctl, BNEPGETCONNLIST, &req)) { + /* New ioctls */ + bnepconnadd = BNEPCONNADD; + bnepconndel = BNEPCONNDEL; + bnepgetconnlist = BNEPGETCONNLIST; + bnepgetconninfo = BNEPGETCONNINFO; + } else { + /* Old ioctls */ + bnepconnadd = OLD_BNEPCONADD; + bnepconndel = OLD_BNEPCONDEL; + bnepgetconnlist = OLD_BNEPGETCONLIST; + bnepgetconninfo = OLD_BNEPGETCONINFO; + } +} +#endif return 0; } @@ -179,6 +215,11 @@ static int bnep_if_down(const char *devname) sk = socket(AF_INET, SOCK_DGRAM, 0); +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + if (sk < 0) + return -1; +#endif + memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, devname, IF_NAMESIZE - 1); @@ -420,11 +461,12 @@ void bnep_disconnect(struct bnep *session) if (!session) return; +#ifndef TIZEN_FEATURE_BLUEZ_MODIFY if (session->watch > 0) { g_source_remove(session->watch); session->watch = 0; } - +#endif if (session->io) { g_io_channel_unref(session->io); session->io = NULL; @@ -434,6 +476,7 @@ void bnep_disconnect(struct bnep *session) bnep_conndel(&session->dst_addr); } +#ifndef TIZEN_FEATURE_BLUEZ_MODIFY static int bnep_add_to_bridge(const char *devname, const char *bridge) { int ifindex; @@ -448,7 +491,15 @@ static int bnep_add_to_bridge(const char *devname, const char *bridge) sk = socket(AF_INET, SOCK_STREAM, 0); if (sk < 0) return -1; - +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + err = ioctl(sk, SIOCBRADDBR, bridge); + if (err < 0) + { + info("bridge create err: %d", err); + close(sk); + return -errno; + } +#endif memset(&ifr, 0, sizeof(ifr)); strncpy(ifr.ifr_name, bridge, IFNAMSIZ - 1); ifr.ifr_ifindex = ifindex; @@ -465,6 +516,7 @@ static int bnep_add_to_bridge(const char *devname, const char *bridge) return err; } +#endif static int bnep_del_from_bridge(const char *devname, const char *bridge) { @@ -602,6 +654,20 @@ static uint16_t bnep_setup_decode(int sk, struct bnep_setup_conn_req *req, return BNEP_CONN_INVALID_DST; } +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +int bnep_if_down_wrapper(const char *devname) +{ + bnep_if_down(devname); + return 0; +} + +int bnep_conndel_wrapper(const bdaddr_t *dst) +{ + bnep_conndel(dst); + return 0; +} +#endif + static int bnep_server_add_legacy(int sk, uint16_t dst, char *bridge, char *iface, const bdaddr_t *addr, uint8_t *setup_data, int len) @@ -622,12 +688,14 @@ static int bnep_server_add_legacy(int sk, uint16_t dst, char *bridge, goto reply; } +#ifndef TIZEN_FEATURE_BLUEZ_MODIFY err = bnep_add_to_bridge(iface, bridge); if (err < 0) { bnep_conndel(addr); rsp = BNEP_CONN_NOT_ALLOWED; goto reply; } +#endif err = bnep_if_up(iface); if (err < 0) { @@ -697,9 +765,11 @@ int bnep_server_add(int sk, char *bridge, char *iface, const bdaddr_t *addr, goto failed; } +#ifndef TIZEN_FEATURE_BLUEZ_MODIFY err = bnep_add_to_bridge(iface, bridge); if (err < 0) goto failed_conn; +#endif err = bnep_if_up(iface); if (err < 0) @@ -710,7 +780,9 @@ int bnep_server_add(int sk, char *bridge, char *iface, const bdaddr_t *addr, failed_bridge: bnep_del_from_bridge(iface, bridge); +#ifndef TIZEN_FEATURE_BLUEZ_MODIFY failed_conn: +#endif bnep_conndel(addr); return err; diff --git a/profiles/network/bnep.h b/profiles/network/bnep.h index e9f4c1cf..d34ed03a 100755 --- a/profiles/network/bnep.h +++ b/profiles/network/bnep.h @@ -40,3 +40,7 @@ void bnep_disconnect(struct bnep *session); int bnep_server_add(int sk, char *bridge, char *iface, const bdaddr_t *addr, uint8_t *setup_data, int len); void bnep_server_delete(char *bridge, char *iface, const bdaddr_t *addr); +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +int bnep_if_down_wrapper(const char *devname); +int bnep_conndel_wrapper(const bdaddr_t *dst); +#endif diff --git a/profiles/network/connection.c b/profiles/network/connection.c index 5305ace8..53d35fe6 100755 --- a/profiles/network/connection.c +++ b/profiles/network/connection.c @@ -123,12 +123,14 @@ static void bnep_disconn_cb(gpointer data) DBusConnection *conn = btd_get_dbus_connection(); const char *path = device_get_path(nc->peer->device); +#ifndef TIZEN_FEATURE_BLUEZ_MODIFY g_dbus_emit_property_changed(conn, path, NETWORK_PEER_INTERFACE, "Connected"); g_dbus_emit_property_changed(conn, path, NETWORK_PEER_INTERFACE, "Interface"); g_dbus_emit_property_changed(conn, path, NETWORK_PEER_INTERFACE, "UUID"); +#endif device_remove_disconnect_watch(nc->peer->device, nc->dc_id); nc->dc_id = 0; @@ -137,6 +139,14 @@ static void bnep_disconn_cb(gpointer data) info("%s disconnected", nc->dev); nc->state = DISCONNECTED; +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + g_dbus_emit_property_changed(conn, path, + NETWORK_PEER_INTERFACE, "Connected"); + g_dbus_emit_property_changed(conn, path, + NETWORK_PEER_INTERFACE, "Interface"); + g_dbus_emit_property_changed(conn, path, + NETWORK_PEER_INTERFACE, "UUID"); +#endif memset(nc->dev, 0, sizeof(nc->dev)); strncpy(nc->dev, BNEP_INTERFACE, 16); nc->dev[15] = '\0'; @@ -179,9 +189,10 @@ static void cancel_connection(struct network_conn *nc, int err) if (nc->state == CONNECTED) bnep_disconnect(nc->session); +#ifndef TIZEN_FEATURE_BLUEZ_MODIFY bnep_free(nc->session); nc->session = NULL; - +#endif nc->state = DISCONNECTED; } @@ -226,6 +237,9 @@ static void bnep_conn_cb(char *iface, int err, void *data) conn = btd_get_dbus_connection(); path = device_get_path(nc->peer->device); +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + nc->state = CONNECTED; +#endif g_dbus_emit_property_changed(conn, path, NETWORK_PEER_INTERFACE, "Connected"); g_dbus_emit_property_changed(conn, path, @@ -233,7 +247,9 @@ static void bnep_conn_cb(char *iface, int err, void *data) g_dbus_emit_property_changed(conn, path, NETWORK_PEER_INTERFACE, "UUID"); +#ifndef TIZEN_FEATURE_BLUEZ_MODIFY nc->state = CONNECTED; +#endif nc->dc_id = device_add_disconnect_watch(nc->peer->device, disconnect_cb, nc, NULL); diff --git a/profiles/network/server.c b/profiles/network/server.c index e69ffaf5..19206a11 100755 --- a/profiles/network/server.c +++ b/profiles/network/server.c @@ -86,6 +86,11 @@ struct network_server { static GSList *adapters = NULL; static gboolean security = TRUE; +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +static gboolean server_disconnected_cb(GIOChannel *chan, + GIOCondition cond, gpointer user_data); +#endif + static struct network_adapter *find_adapter(GSList *list, struct btd_adapter *adapter) { @@ -151,6 +156,38 @@ static struct network_server *find_server_by_uuid(GSList *list, return NULL; } +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +static struct network_session *find_session(GSList *list, GIOChannel *io) +{ + GSList *l; + + for (l = list; l; l = l->next) { + struct network_session *session = l->data; + + if (session && session->io == io) + return session; + } + + return NULL; +} + +static struct network_session *find_session_by_addr(GSList *list, + bdaddr_t dst_addr) +{ + GSList *l; + + for (l = list; l; l = l->next) { + struct network_session *session = l->data; + + if (!bacmp(&session->dst, &dst_addr)) + return session; + + } + + return NULL; +} +#endif + static sdp_record_t *server_record_new(const char *name, uint16_t id) { sdp_list_t *svclass, *pfseq, *apseq, *root, *aproto; @@ -160,8 +197,13 @@ static sdp_record_t *server_record_new(const char *name, uint16_t id) sdp_data_t *v, *p; uint16_t psm = BNEP_PSM, version = 0x0100; uint16_t security_desc = (security ? 0x0001 : 0x0000); +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + uint16_t net_access_type = 0x000a; + uint32_t max_net_access_rate = 0x001312d0; +#else uint16_t net_access_type = 0xfffe; uint32_t max_net_access_rate = 0; +#endif const char *desc = "Network service"; sdp_record_t *record; @@ -303,6 +345,56 @@ static void setup_destroy(void *user_data) session_free(setup); } +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +static gboolean server_disconnected_cb(GIOChannel *chan, + GIOCondition cond, gpointer user_data) +{ + struct network_server *ns = NULL; + struct network_session *session = NULL; + char address[20] = {0}; + const char* paddr = address; + char *name_str = NULL; + + info("server_disconnected_cb"); + + if (!user_data) + return FALSE; + + ns = (struct network_server *) user_data; + + session = find_session(ns->sessions, chan); + if (session) { + name_str = g_strdup(session->dev); + ba2str(&session->dst, address); + } else { + info("Session is not exist!"); + name_str = g_strdup("bnep"); + } + + g_dbus_emit_signal(btd_get_dbus_connection(), + adapter_get_path(ns->na->adapter), + NETWORK_SERVER_INTERFACE, "PeerDisconnected", + DBUS_TYPE_STRING, &name_str, + DBUS_TYPE_STRING, &paddr, + DBUS_TYPE_INVALID); + + if (session) { + ns->sessions = g_slist_remove(ns->sessions, session); + session_free(session); + } + + if (g_slist_length(ns->sessions) == 0 && + name_str != NULL) { + bnep_if_down_wrapper(name_str); + ns->sessions = NULL; + } + + g_free(name_str); + + return FALSE; +} +#endif + static gboolean bnep_setup(GIOChannel *chan, GIOCondition cond, gpointer user_data) { @@ -381,6 +473,30 @@ static gboolean bnep_setup(GIOChannel *chan, packet, n) < 0) error("BNEP server cannot be added"); +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +if (ns) { + /* Emit connected signal to BT application */ + const gchar *adapter_path = adapter_get_path(na->adapter); + const char *pdev = na->setup->dev; + char address[24] = { 0 }; + char *paddr = address; + + ba2str(&na->setup->dst, paddr); + + ns->sessions = g_slist_append(ns->sessions, na->setup); + + g_dbus_emit_signal(btd_get_dbus_connection(), adapter_path, + NETWORK_SERVER_INTERFACE, "PeerConnected", + DBUS_TYPE_STRING, &pdev, + DBUS_TYPE_STRING, &paddr, + DBUS_TYPE_INVALID); + + na->setup->watch = g_io_add_watch_full(chan, G_PRIORITY_DEFAULT, + G_IO_HUP | G_IO_ERR | G_IO_NVAL, + server_disconnected_cb, ns, NULL); +} +#endif + na->setup = NULL; return FALSE; @@ -515,9 +631,11 @@ static void server_remove_sessions(struct network_server *ns) bnep_server_delete(ns->bridge, session->dev, &session->dst); } +#ifndef TIZEN_FEATURE_BLUEZ_MODIFY g_slist_free_full(ns->sessions, session_free); ns->sessions = NULL; +#endif } static void server_disconnect(DBusConnection *conn, void *user_data) @@ -590,6 +708,11 @@ static DBusMessage *unregister_server(DBusConnection *conn, if (!ns) return btd_error_failed(msg, "Invalid UUID"); +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + if (!ns->record_id) + return btd_error_not_available(msg); +#endif + reply = dbus_message_new_method_return(msg); if (!reply) return NULL; @@ -645,6 +768,94 @@ static void path_unregister(void *data) adapter_free(na); } +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +static DBusMessage *disconnect_device(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + struct network_adapter *na = data; + struct network_server *ns; + struct network_session *session; + const char *addr = NULL; + bdaddr_t dst_addr; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &addr, + DBUS_TYPE_INVALID)) + return btd_error_invalid_args(msg); + + ns = find_server(na->servers, BNEP_SVC_NAP); + + str2ba(addr, &dst_addr); + session = find_session_by_addr(ns->sessions, dst_addr); + + if (session == NULL) + return btd_error_failed(msg, "No active session"); + + if (session->io == NULL) + return btd_error_not_connected(msg); + + bnep_if_down_wrapper(session->dev); + bnep_conndel_wrapper(&dst_addr); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *get_properties(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + struct network_adapter *na = data; + struct network_server *ns; + struct network_session *session; + const char *addr = NULL; + bdaddr_t dst_addr; + DBusMessage *reply; + DBusMessageIter iter; + DBusMessageIter dict; + dbus_bool_t connected; + const char *property; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &addr, + DBUS_TYPE_INVALID)) + return btd_error_invalid_args(msg); + + 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_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); + + ns = find_server(na->servers, BNEP_SVC_NAP); + + str2ba(addr, &dst_addr); + session = find_session_by_addr(ns->sessions, dst_addr); + + connected = (session && session->io) ? TRUE : FALSE; + dict_append_entry(&dict, "Connected", DBUS_TYPE_BOOLEAN, &connected); + + /* Interface */ + property = session ? session->dev : ""; + dict_append_entry(&dict, "Interface", DBUS_TYPE_STRING, &property); + + dbus_message_iter_close_container(&iter, &dict); + + return reply; +} +#endif + +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +static GDBusSignalTable server_signals[] = { + { GDBUS_SIGNAL("PeerConnected", + GDBUS_ARGS({ "device", "s" }, { "address", "s" })) }, + { GDBUS_SIGNAL("PeerDisconnected", + GDBUS_ARGS({ "device", "s" }, { "address", "s" })) }, + { } +}; +#endif + static const GDBusMethodTable server_methods[] = { { GDBUS_METHOD("Register", GDBUS_ARGS({ "uuid", "s" }, { "bridge", "s" }), NULL, @@ -652,6 +863,15 @@ static const GDBusMethodTable server_methods[] = { { GDBUS_METHOD("Unregister", GDBUS_ARGS({ "uuid", "s" }), NULL, unregister_server) }, +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + { GDBUS_METHOD("Disconnect", + GDBUS_ARGS({ "address", "s" }), NULL, + disconnect_device) }, + { GDBUS_METHOD("GetProperties", + GDBUS_ARGS({ "address", "s" }), + GDBUS_ARGS({ "properties", "a{sv}" }), + get_properties) }, +#endif { } }; @@ -709,6 +929,7 @@ int server_register(struct btd_adapter *adapter, uint16_t id) if (g_slist_length(na->servers) > 0) goto done; +#ifndef TIZEN_FEATURE_BLUEZ_MODIFY if (!g_dbus_register_interface(btd_get_dbus_connection(), path, NETWORK_SERVER_INTERFACE, server_methods, NULL, NULL, na, @@ -718,6 +939,20 @@ int server_register(struct btd_adapter *adapter, uint16_t id) server_free(ns); return -1; } +#else + ns->sessions = NULL; + + if (!g_dbus_register_interface(btd_get_dbus_connection(), + path, NETWORK_SERVER_INTERFACE, + server_methods, server_signals, + NULL, + na, path_unregister)) { + error("D-Bus failed to register %s interface", + NETWORK_SERVER_INTERFACE); + server_free(ns); + return -1; + } +#endif DBG("Registered interface %s on path %s", NETWORK_SERVER_INTERFACE, path); diff --git a/profiles/proximity/immalert.c b/profiles/proximity/immalert.c new file mode 100755 index 00000000..adf91404 --- /dev/null +++ b/profiles/proximity/immalert.c @@ -0,0 +1,458 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012 Texas Instruments Corporation + * + * 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 <stdbool.h> + +#include <glib.h> + +#include <dbus/dbus.h> + +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/uuid.h" + +#include "gdbus/gdbus.h" + +#include "src/log.h" +#include "src/adapter.h" +#include "attrib/gattrib.h" +#include "attrib/att.h" +#include "attrib/gatt.h" +#include "attrib/att-database.h" +#include "attrib/gatt-service.h" +#include "src/attrib-server.h" +#include "src/device.h" +#include "src/profile.h" +#include "src/attio.h" +#include "src/dbus-common.h" + +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +#include "src/shared/queue.h" +#include "src/shared/gatt-db.h" +#include "src/shared/att.h" + #include "btio/btio.h" +#include "src/gatt-database.h" +#endif + +#include "reporter.h" +#include "immalert.h" + +struct imm_alert_adapter { + struct btd_adapter *adapter; +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + struct gatt_db_attribute *imservice; +#endif + GSList *connected_devices; +}; + +struct connected_device { + struct btd_device *device; + struct imm_alert_adapter *adapter; + uint8_t alert_level; + guint callback_id; +}; + +static GSList *imm_alert_adapters; + +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +static bool get_dest_info(struct bt_att *att, bdaddr_t *dst, uint8_t *dst_type) +{ + GIOChannel *io = NULL; + GError *gerr = NULL; + + io = g_io_channel_unix_new(bt_att_get_fd(att)); + if (!io) + return false; + + bt_io_get(io, &gerr, BT_IO_OPT_DEST_BDADDR, dst, + BT_IO_OPT_DEST_TYPE, dst_type, + BT_IO_OPT_INVALID); + + if (gerr) { + error("gatt: bt_io_get: %s", gerr->message); + g_error_free(gerr); + g_io_channel_unref(io); + return false; + } + + g_io_channel_unref(io); + return true; +} +#endif + +static int imdevice_cmp(gconstpointer a, gconstpointer b) +{ + const struct connected_device *condev = a; + const struct btd_device *device = b; + + if (condev->device == device) + return 0; + + return -1; +} + +static struct connected_device * +find_connected_device(struct imm_alert_adapter *ia, struct btd_device *device) +{ + GSList *l = g_slist_find_custom(ia->connected_devices, device, + imdevice_cmp); + if (!l) + return NULL; + + return l->data; +} + +static int imadapter_cmp(gconstpointer a, gconstpointer b) +{ + const struct imm_alert_adapter *imadapter = a; + const struct btd_adapter *adapter = b; + + if (imadapter->adapter == adapter) + return 0; + + return -1; +} + +static struct imm_alert_adapter * +find_imm_alert_adapter(struct btd_adapter *adapter) +{ + GSList *l = g_slist_find_custom(imm_alert_adapters, adapter, + imadapter_cmp); + if (!l) + return NULL; + + return l->data; +} + +const char *imm_alert_get_level(struct btd_device *device) +{ + struct imm_alert_adapter *imadapter; + struct connected_device *condev; + + if (!device) + return get_alert_level_string(NO_ALERT); + + imadapter = find_imm_alert_adapter(device_get_adapter(device)); + if (!imadapter) + return get_alert_level_string(NO_ALERT); + + condev = find_connected_device(imadapter, device); + if (!condev) + return get_alert_level_string(NO_ALERT); + + return get_alert_level_string(condev->alert_level); +} + +static void imm_alert_emit_alert_signal(struct connected_device *condev, + uint8_t alert_level) +{ + const char *path, *alert_level_str; + + if (!condev) + return; + + path = device_get_path(condev->device); + alert_level_str = get_alert_level_string(alert_level); + + DBG("alert %s remote %s", alert_level_str, path); + + g_dbus_emit_property_changed(btd_get_dbus_connection(), path, + PROXIMITY_REPORTER_INTERFACE, "ImmediateAlertLevel"); +} + +static void imm_alert_remove_condev(struct connected_device *condev) +{ + struct imm_alert_adapter *ia; + + if (!condev) + return; + + ia = condev->adapter; + + if (condev->callback_id && condev->device) + btd_device_remove_attio_callback(condev->device, + condev->callback_id); + + if (condev->device) + btd_device_unref(condev->device); + + ia->connected_devices = g_slist_remove(ia->connected_devices, condev); + g_free(condev); +} + +/* condev can be NULL */ +static void imm_alert_disc_cb(gpointer user_data) +{ + struct connected_device *condev = user_data; + + if (!condev) + return; + + DBG("immediate alert remove device %p", condev->device); + + imm_alert_emit_alert_signal(condev, NO_ALERT); + imm_alert_remove_condev(condev); +} + +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +static void imm_alert_alert_lvl_write(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + const uint8_t *value, size_t len, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct imm_alert_adapter *ia = user_data; + struct connected_device *condev = NULL; + uint8_t ecode = 0; + bdaddr_t bdaddr; + uint8_t bdaddr_type; + struct btd_device *device = NULL; + + if (!value || len == 0) { + ecode = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN; + goto done; + } + + if (offset != 0) { + ecode = BT_ATT_ERROR_INVALID_OFFSET; + goto done; + } + + if (!get_dest_info(att, &bdaddr, &bdaddr_type)) { + ecode = BT_ATT_ERROR_UNLIKELY; + goto done; + } + + device = btd_adapter_get_device(ia->adapter, &bdaddr, bdaddr_type); + if (!device) { + ecode = BT_ATT_ERROR_UNLIKELY; + goto done; + } + + /* Write value should be anyone of 0x00, 0x01, 0x02 */ + if (value[0] > 0x02) { + ecode = 0x80; + goto done; + } + + /* condev might remain NULL here if nothing is found */ + condev = find_connected_device(ia, device); + + /* Register a disconnect cb if the alert level is non-zero */ + if (value[0] != NO_ALERT && !condev) { + condev = g_new0(struct connected_device, 1); + condev->device = btd_device_ref(device); + condev->adapter = ia; + condev->callback_id = btd_device_add_attio_callback(device, + NULL, imm_alert_disc_cb, condev); + ia->connected_devices = g_slist_append(ia->connected_devices, + condev); + DBG("added connected dev %p", device); + } + + if (condev) { + if (value[0] != NO_ALERT) { + condev->alert_level = value[0]; + imm_alert_emit_alert_signal(condev, value[0]); + } else { + imm_alert_emit_alert_signal(condev, value[0]); + imm_alert_disc_cb(condev); + } + } + + DBG("alert level set to %d by device %p", value[0], device); + gatt_db_attribute_write_result(attrib, id, ecode); + return; + +done: + error("Set immediate alert level for dev %p", device); + /* remove alerts by erroneous devices */ + imm_alert_disc_cb(condev); + gatt_db_attribute_write_result(attrib, id, ecode); +} + +void imm_alert_register(struct btd_adapter *adapter) +{ + bt_uuid_t uuid; + struct imm_alert_adapter *imadapter; + struct gatt_db_attribute *service, *charc; + struct gatt_db *db; + + imadapter = g_new0(struct imm_alert_adapter, 1); + imadapter->adapter = adapter; + + imm_alert_adapters = g_slist_append(imm_alert_adapters, imadapter); + db = (struct gatt_db *) btd_gatt_database_get_db(btd_adapter_get_database(adapter)); + + /* Immediate Alert Service */ + bt_uuid16_create(&uuid, IMMEDIATE_ALERT_SVC_UUID); + service = gatt_db_add_service(db, &uuid, true, 3); + if (!service) + goto err; + + imadapter->imservice = service; + + /* + * Alert Level characteristic. + */ + bt_uuid16_create(&uuid, ALERT_LEVEL_CHR_UUID); + charc = gatt_db_service_add_characteristic(service, &uuid, BT_ATT_PERM_WRITE, + BT_GATT_CHRC_PROP_WRITE, + NULL, + imm_alert_alert_lvl_write, imadapter); + if (!charc) + goto err; + + gatt_db_service_set_active(service, true); + + DBG("Immediate Alert service added"); + return; +err: + DBG("Error adding Immediate Alert service"); + imm_alert_unregister(adapter); +} +#else +static uint8_t imm_alert_alert_lvl_write(struct attribute *a, + struct btd_device *device, gpointer user_data) +{ + uint8_t value; + struct imm_alert_adapter *ia = user_data; + struct connected_device *condev = NULL; + + if (!device) + goto set_error; + + condev = find_connected_device(ia, device); + + if (a->len == 0) { + DBG("Illegal alert level length"); + goto set_error; + } + + value = a->data[0]; + if (value != NO_ALERT && value != MILD_ALERT && value != HIGH_ALERT) { + DBG("Illegal alert value"); + goto set_error; + } + + /* Register a disconnect cb if the alert level is non-zero */ + if (value != NO_ALERT && !condev) { + condev = g_new0(struct connected_device, 1); + condev->device = btd_device_ref(device); + condev->adapter = ia; + condev->callback_id = btd_device_add_attio_callback(device, + NULL, imm_alert_disc_cb, condev); + ia->connected_devices = g_slist_append(ia->connected_devices, + condev); + DBG("added connected dev %p", device); + } + + if (value != NO_ALERT) { + condev->alert_level = value; + imm_alert_emit_alert_signal(condev, value); + } + + /* + * Emit NO_ALERT if the alert level was non-zero before. This is + * guaranteed when there's a condev. + */ + if (value == NO_ALERT && condev) + imm_alert_disc_cb(condev); + + DBG("alert level set to %d by device %p", value, device); + return 0; + +set_error: + error("Set immediate alert level for dev %p", device); + /* remove alerts by erroneous devices */ + imm_alert_disc_cb(condev); + return ATT_ECODE_IO; +} + +void imm_alert_register(struct btd_adapter *adapter) +{ + gboolean svc_added; + bt_uuid_t uuid; + struct imm_alert_adapter *imadapter; + + bt_uuid16_create(&uuid, IMMEDIATE_ALERT_SVC_UUID); + + imadapter = g_new0(struct imm_alert_adapter, 1); + imadapter->adapter = adapter; + + imm_alert_adapters = g_slist_append(imm_alert_adapters, imadapter); + + /* Immediate Alert Service */ + svc_added = gatt_service_add(adapter, + GATT_PRIM_SVC_UUID, &uuid, + /* Alert level characteristic */ + GATT_OPT_CHR_UUID16, ALERT_LEVEL_CHR_UUID, + GATT_OPT_CHR_PROPS, + GATT_CHR_PROP_WRITE_WITHOUT_RESP, + GATT_OPT_CHR_VALUE_CB, ATTRIB_WRITE, + imm_alert_alert_lvl_write, imadapter, + GATT_OPT_INVALID); + + if (!svc_added) { + imm_alert_unregister(adapter); + return; + } + + DBG("Immediate Alert service added"); +} +#endif + +static void remove_condev_list_item(gpointer data, gpointer user_data) +{ + struct connected_device *condev = data; + + imm_alert_remove_condev(condev); +} + +void imm_alert_unregister(struct btd_adapter *adapter) +{ + struct imm_alert_adapter *imadapter; +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + struct gatt_db *db; +#endif + + imadapter = find_imm_alert_adapter(adapter); + if (!imadapter) + return; + + g_slist_foreach(imadapter->connected_devices, remove_condev_list_item, + NULL); +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + /* Remove registered service */ + if (imadapter->imservice) { + db = (struct gatt_db *) btd_gatt_database_get_db(btd_adapter_get_database(adapter)); + gatt_db_remove_service(db, imadapter->imservice); + } +#endif + + imm_alert_adapters = g_slist_remove(imm_alert_adapters, imadapter); + g_free(imadapter); +} diff --git a/profiles/proximity/immalert.h b/profiles/proximity/immalert.h new file mode 100755 index 00000000..1a09fa98 --- /dev/null +++ b/profiles/proximity/immalert.h @@ -0,0 +1,26 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012 Texas Instruments Corporation + * + * + * 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 + * + */ + +void imm_alert_register(struct btd_adapter *adapter); +void imm_alert_unregister(struct btd_adapter *adapter); +const char *imm_alert_get_level(struct btd_device *device); diff --git a/profiles/proximity/linkloss.c b/profiles/proximity/linkloss.c new file mode 100755 index 00000000..60b10647 --- /dev/null +++ b/profiles/proximity/linkloss.c @@ -0,0 +1,547 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012 Texas Instruments Corporation + * + * 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 <stdbool.h> + +#include <glib.h> + +#include <dbus/dbus.h> + +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/uuid.h" + +#include "gdbus/gdbus.h" + +#include "src/log.h" +#include "src/adapter.h" +#include "src/device.h" +#include "attrib/att-database.h" +#include "attrib/gattrib.h" +#include "attrib/att.h" +#include "attrib/gatt.h" +#include "attrib/gatt-service.h" +#include "src/attrib-server.h" +#include "src/service.h" +#include "src/profile.h" +#include "src/attio.h" +#include "src/dbus-common.h" + +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +#include "src/shared/queue.h" +#include "src/shared/gatt-db.h" +#include "src/shared/att.h" +#include "btio/btio.h" +#include "src/gatt-database.h" +#endif + +#include "reporter.h" +#include "linkloss.h" + +struct link_loss_adapter { + struct btd_adapter *adapter; +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + struct gatt_db_attribute *llservice; +#else + uint16_t alert_lvl_value_handle; +#endif + GSList *connected_devices; +}; + +struct connected_device { + struct btd_device *device; + struct link_loss_adapter *adapter; + uint8_t alert_level; + guint callback_id; + guint local_disc_id; +}; + +static GSList *link_loss_adapters; + +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +static bool get_dest_info(struct bt_att *att, bdaddr_t *dst, uint8_t *dst_type) +{ + GIOChannel *io = NULL; + GError *gerr = NULL; + + io = g_io_channel_unix_new(bt_att_get_fd(att)); + if (!io) + return false; + + bt_io_get(io, &gerr, BT_IO_OPT_DEST_BDADDR, dst, + BT_IO_OPT_DEST_TYPE, dst_type, + BT_IO_OPT_INVALID); + + if (gerr) { + error("gatt: bt_io_get: %s", gerr->message); + g_error_free(gerr); + g_io_channel_unref(io); + return false; + } + + g_io_channel_unref(io); + return true; +} +#endif + +static int lldevice_cmp(gconstpointer a, gconstpointer b) +{ + const struct connected_device *llcondev = a; + const struct btd_device *device = b; + + if (llcondev->device == device) + return 0; + + return -1; +} + +static struct connected_device * +find_connected_device(struct link_loss_adapter *la, struct btd_device *device) +{ + GSList *l = g_slist_find_custom(la->connected_devices, device, + lldevice_cmp); + if (!l) + return NULL; + + return l->data; +} + +static int lladapter_cmp(gconstpointer a, gconstpointer b) +{ + const struct link_loss_adapter *lladapter = a; + const struct btd_adapter *adapter = b; + + if (lladapter->adapter == adapter) + return 0; + + return -1; +} + +static struct link_loss_adapter * +find_link_loss_adapter(struct btd_adapter *adapter) +{ + GSList *l = g_slist_find_custom(link_loss_adapters, adapter, + lladapter_cmp); + if (!l) + return NULL; + + return l->data; +} + +const char *link_loss_get_alert_level(struct btd_device *device) +{ + struct link_loss_adapter *lladapter; + struct connected_device *condev; + + if (!device) + return get_alert_level_string(NO_ALERT); + + lladapter = find_link_loss_adapter(device_get_adapter(device)); + if (!lladapter) + return get_alert_level_string(NO_ALERT); + + condev = find_connected_device(lladapter, device); + if (!condev) + return get_alert_level_string(NO_ALERT); + + return get_alert_level_string(condev->alert_level); +} + +static void link_loss_emit_alert_signal(struct connected_device *condev) +{ + const char *alert_level_str, *path; + + if (!condev->device) + return; + + path = device_get_path(condev->device); + alert_level_str = get_alert_level_string(condev->alert_level); + + DBG("alert %s remote %s", alert_level_str, path); + + g_dbus_emit_property_changed(btd_get_dbus_connection(), path, + PROXIMITY_REPORTER_INTERFACE, "LinkLossAlertLevel"); +} +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +static void link_loss_alert_lvl_read(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct link_loss_adapter *la = user_data; + struct connected_device *condev; + uint8_t value; + uint8_t ecode = 0; + bdaddr_t bdaddr; + uint8_t bdaddr_type; + struct btd_device *device; + + value = NO_ALERT; + + if (offset != 0) { + ecode = BT_ATT_ERROR_INVALID_OFFSET; + goto out; + } + + if (!get_dest_info(att, &bdaddr, &bdaddr_type)) { + ecode = BT_ATT_ERROR_UNLIKELY; + goto out; + } + + device = btd_adapter_get_device(la->adapter, &bdaddr, bdaddr_type); + if (!device) { + ecode = BT_ATT_ERROR_UNLIKELY; + goto out; + } + + condev = find_connected_device(la, device); + if (!condev) { + ecode = BT_ATT_ERROR_UNLIKELY; + goto out; + } + + if (condev->alert_level) + value = condev->alert_level; + else + DBG("Alert Level is NULL"); + + DBG("Alert Level %d", value); +out: + gatt_db_attribute_read_result(attrib, id, ecode, &value, sizeof(value)); +} +#else +static uint8_t link_loss_alert_lvl_read(struct attribute *a, + struct btd_device *device, gpointer user_data) +{ + struct link_loss_adapter *la = user_data; + struct connected_device *condev; + uint8_t alert_level = NO_ALERT; + + if (!device) + goto out; + + condev = find_connected_device(la, device); + if (!condev) + goto out; + + alert_level = condev->alert_level; + +out: + DBG("return alert level %d for dev %p", alert_level, device); + + /* update the alert level according to the requesting device */ + attrib_db_update(la->adapter, a->handle, NULL, &alert_level, + sizeof(alert_level), NULL); + + return 0; +} +#endif + +/* condev can be NULL */ +static void link_loss_remove_condev(struct connected_device *condev) +{ + struct link_loss_adapter *la; + + if (!condev) + return; + + la = condev->adapter; + + if (condev->callback_id && condev->device) + btd_device_remove_attio_callback(condev->device, + condev->callback_id); + + if (condev->local_disc_id && condev->device) + device_remove_disconnect_watch(condev->device, + condev->local_disc_id); + + if (condev->device) + btd_device_unref(condev->device); + + la->connected_devices = g_slist_remove(la->connected_devices, condev); + g_free(condev); +} + +static void link_loss_disc_cb(gpointer user_data) +{ + struct connected_device *condev = user_data; + + DBG("alert loss disconnect device %p", condev->device); + + /* if an alert-level is set, emit a signal */ + if (condev->alert_level != NO_ALERT) + link_loss_emit_alert_signal(condev); + + /* we are open for more changes now */ + link_loss_remove_condev(condev); +} + +static void link_loss_local_disc(struct btd_device *device, + gboolean removal, void *user_data) +{ + struct connected_device *condev = user_data; + + /* no need to alert on this device - we requested disconnection */ + link_loss_remove_condev(condev); + + DBG("alert level zeroed for locally disconnecting dev %p", device); +} + +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +static void link_loss_alert_lvl_write(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + const uint8_t *value, size_t len, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct link_loss_adapter *la = user_data; + struct connected_device *condev = NULL; + uint8_t ecode = 0; + bdaddr_t bdaddr; + uint8_t bdaddr_type; + struct btd_device *device = NULL; + + if (!value || len == 0) { + ecode = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN; + goto done; + } + + if (offset != 0) { + ecode = BT_ATT_ERROR_INVALID_OFFSET; + goto done; + } + + if (!get_dest_info(att, &bdaddr, &bdaddr_type)) { + ecode = BT_ATT_ERROR_UNLIKELY; + goto done; + } + + device = btd_adapter_get_device(la->adapter, &bdaddr, bdaddr_type); + if (!device) { + ecode = BT_ATT_ERROR_UNLIKELY; + goto done; + } + + /* Write value should be anyone of 0x00, 0x01, 0x02 */ + if (value[0] > 0x02) { + ecode = 0x80; + goto done; + } + + /* condev might remain NULL here if nothing is found */ + condev = find_connected_device(la, device); + + /* Register a disconnect cb if the alert level is non-zero */ + if (value[0] != NO_ALERT && !condev) { + condev = g_new0(struct connected_device, 1); + condev->device = btd_device_ref(device); + condev->adapter = la; + condev->callback_id = btd_device_add_attio_callback(device, + NULL, link_loss_disc_cb, condev); + condev->local_disc_id = device_add_disconnect_watch(device, + link_loss_local_disc, condev, NULL); + + la->connected_devices = g_slist_append(la->connected_devices, + condev); + } + + if (condev) { + if (value[0] != NO_ALERT) { + condev->alert_level = value[0]; + link_loss_emit_alert_signal(condev); + } else { + link_loss_emit_alert_signal(condev); + link_loss_remove_condev(condev); + condev = NULL; + } + } + + DBG("alert level set to %d by device %p", value[0], device); + gatt_db_attribute_write_result(attrib, id, ecode); + return; +done: + DBG("Set link loss alert level for dev %p", device); + /* reset alert level on erroneous devices */ + link_loss_remove_condev(condev); + gatt_db_attribute_write_result(attrib, id, ecode); +} +#else +static uint8_t link_loss_alert_lvl_write(struct attribute *a, + struct btd_device *device, gpointer user_data) +{ + uint8_t value; + struct link_loss_adapter *la = user_data; + struct connected_device *condev = NULL; + + if (!device) + goto set_error; + + /* condev might remain NULL here if nothing is found */ + condev = find_connected_device(la, device); + + if (a->len == 0) { + DBG("Illegal alert level length"); + goto set_error; + } + + value = a->data[0]; + if (value != NO_ALERT && value != MILD_ALERT && value != HIGH_ALERT) { + DBG("Illegal alert value"); + goto set_error; + } + + /* Register a disconnect cb if the alert level is non-zero */ + if (value != NO_ALERT && !condev) { + condev = g_new0(struct connected_device, 1); + condev->device = btd_device_ref(device); + condev->adapter = la; + condev->callback_id = btd_device_add_attio_callback(device, + NULL, link_loss_disc_cb, condev); + condev->local_disc_id = device_add_disconnect_watch(device, + link_loss_local_disc, condev, NULL); + + la->connected_devices = g_slist_append(la->connected_devices, + condev); + } else if (value == NO_ALERT && condev) { + link_loss_remove_condev(condev); + condev = NULL; + } + + DBG("alert level set to %d by device %p", value, device); + + if (condev) + condev->alert_level = value; + + return 0; + +set_error: + error("Set link loss alert level for dev %p", device); + /* reset alert level on erroneous devices */ + link_loss_remove_condev(condev); + return ATT_ECODE_IO; +} +#endif + +void link_loss_register(struct btd_adapter *adapter) +{ +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + struct gatt_db_attribute *service, *charc; + struct gatt_db *db; +#else + gboolean svc_added; +#endif + bt_uuid_t uuid; + struct link_loss_adapter *lladapter; + + bt_uuid16_create(&uuid, LINK_LOSS_SVC_UUID); + + lladapter = g_new0(struct link_loss_adapter, 1); + lladapter->adapter = adapter; + + link_loss_adapters = g_slist_append(link_loss_adapters, lladapter); +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + db = (struct gatt_db *) btd_gatt_database_get_db(btd_adapter_get_database(adapter)); + + /* + * Link Loss Service + */ + service = gatt_db_add_service(db, &uuid, true, 3); + if (!service) + goto err; + + lladapter->llservice = service; + + /* + * Alert Level characteristic. + */ + bt_uuid16_create(&uuid, ALERT_LEVEL_CHR_UUID); + charc = gatt_db_service_add_characteristic(service, &uuid, BT_ATT_PERM_READ | BT_ATT_PERM_WRITE, + BT_GATT_CHRC_PROP_READ | BT_GATT_CHRC_PROP_WRITE, + link_loss_alert_lvl_read, + link_loss_alert_lvl_write, lladapter); + + if (!charc) + goto err; + + gatt_db_service_set_active(service, true); +#else + /* Link Loss Service */ + svc_added = gatt_service_add(adapter, + GATT_PRIM_SVC_UUID, &uuid, + /* Alert level characteristic */ + GATT_OPT_CHR_UUID16, ALERT_LEVEL_CHR_UUID, + GATT_OPT_CHR_PROPS, + GATT_CHR_PROP_READ | GATT_CHR_PROP_WRITE, + GATT_OPT_CHR_VALUE_CB, ATTRIB_READ, + link_loss_alert_lvl_read, lladapter, + GATT_OPT_CHR_VALUE_CB, ATTRIB_WRITE, + link_loss_alert_lvl_write, lladapter, + GATT_OPT_CHR_VALUE_GET_HANDLE, + &lladapter->alert_lvl_value_handle, + GATT_OPT_INVALID); + + if (!svc_added) + goto err; +#endif + DBG("Link Loss service added"); + return; + +err: + error("Error adding Link Loss service"); + link_loss_unregister(adapter); +} + +static void remove_condev_list_item(gpointer data, gpointer user_data) +{ + struct connected_device *condev = data; + + link_loss_remove_condev(condev); +} + +void link_loss_unregister(struct btd_adapter *adapter) +{ + struct link_loss_adapter *lladapter; +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + struct gatt_db *db; +#endif + + lladapter = find_link_loss_adapter(adapter); + if (!lladapter) + return; + + g_slist_foreach(lladapter->connected_devices, remove_condev_list_item, + NULL); +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + /* Remove registered service */ + if (lladapter->llservice) { + db = (struct gatt_db *) btd_gatt_database_get_db(btd_adapter_get_database(adapter)); + gatt_db_remove_service(db, lladapter->llservice); + } +#endif + + link_loss_adapters = g_slist_remove(link_loss_adapters, lladapter); + g_free(lladapter); +} diff --git a/profiles/proximity/linkloss.h b/profiles/proximity/linkloss.h new file mode 100755 index 00000000..0447def3 --- /dev/null +++ b/profiles/proximity/linkloss.h @@ -0,0 +1,26 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2012 Texas Instruments Corporation + * + * + * 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 + * + */ + +void link_loss_register(struct btd_adapter *adapter); +void link_loss_unregister(struct btd_adapter *adapter); +const char *link_loss_get_alert_level(struct btd_device *device); diff --git a/profiles/proximity/main.c b/profiles/proximity/main.c new file mode 100755 index 00000000..38a51f12 --- /dev/null +++ b/profiles/proximity/main.c @@ -0,0 +1,81 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011 Nokia Corporation + * Copyright (C) 2011 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 <errno.h> +#include <stdint.h> +#include <glib.h> + +#include "gdbus/gdbus.h" + +#include "src/log.h" +#include "src/plugin.h" +#include "manager.h" + +static GKeyFile *config = NULL; + +static GKeyFile *open_config_file(const char *file) +{ + GError *gerr = NULL; + GKeyFile *keyfile; + + keyfile = g_key_file_new(); + + g_key_file_set_list_separator(keyfile, ','); + + if (!g_key_file_load_from_file(keyfile, file, 0, &gerr)) { + if (!g_error_matches(gerr, G_FILE_ERROR, G_FILE_ERROR_NOENT)) + error("Parsing %s failed: %s", file, gerr->message); + g_error_free(gerr); + g_key_file_free(keyfile); + return NULL; + } + + return keyfile; +} + +static int proximity_init(void) +{ + config = open_config_file(CONFIGDIR "/proximity.conf"); + + if (proximity_manager_init(config) < 0) + return -EIO; + + return 0; +} + +static void proximity_exit(void) +{ + if (config) + g_key_file_free(config); + + proximity_manager_exit(); +} + +BLUETOOTH_PLUGIN_DEFINE(proximity, VERSION, + BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, + proximity_init, proximity_exit) diff --git a/profiles/proximity/manager.c b/profiles/proximity/manager.c new file mode 100755 index 00000000..dbb3bda2 --- /dev/null +++ b/profiles/proximity/manager.c @@ -0,0 +1,196 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011 Nokia Corporation + * Copyright (C) 2011 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 <stdbool.h> + +#include <glib.h> + +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/uuid.h" + +#include "gdbus/gdbus.h" + +#include "src/adapter.h" +#include "src/device.h" +#include "src/profile.h" +#include "src/service.h" +#include "attrib/att.h" +#include "attrib/gattrib.h" +#include "attrib/gatt.h" +#include "monitor.h" +#include "reporter.h" +#include "manager.h" + +static struct enabled enabled = { + .linkloss = TRUE, + .pathloss = TRUE, + .findme = TRUE, +}; + +static int monitor_linkloss_probe(struct btd_service *service) +{ + struct btd_device *device = btd_service_get_device(service); + struct gatt_primary *linkloss; + + linkloss = btd_device_get_primary(device, LINK_LOSS_UUID); + if (linkloss == NULL) + return -1; + + return monitor_register_linkloss(device, &enabled, linkloss); +} + +static int monitor_immediate_probe(struct btd_service *service) +{ + struct btd_device *device = btd_service_get_device(service); + struct gatt_primary *immediate; + + immediate = btd_device_get_primary(device, IMMEDIATE_ALERT_UUID); + if (immediate == NULL) + return -1; + + return monitor_register_immediate(device, &enabled, immediate); +} + +static int monitor_txpower_probe(struct btd_service *service) +{ + struct btd_device *device = btd_service_get_device(service); + struct gatt_primary *txpower; + + txpower = btd_device_get_primary(device, TX_POWER_UUID); + if (txpower == NULL) + return -1; + + return monitor_register_txpower(device, &enabled, txpower); +} + +static void monitor_linkloss_remove(struct btd_service *service) +{ + struct btd_device *device = btd_service_get_device(service); + + monitor_unregister_linkloss(device); +} + +static void monitor_immediate_remove(struct btd_service *service) +{ + struct btd_device *device = btd_service_get_device(service); + + monitor_unregister_immediate(device); +} + +static void monitor_txpower_remove(struct btd_service *service) +{ + struct btd_device *device = btd_service_get_device(service); + + monitor_unregister_txpower(device); +} + +static struct btd_profile pxp_monitor_linkloss_profile = { + .name = "proximity-linkloss", + .remote_uuid = LINK_LOSS_UUID, + .device_probe = monitor_linkloss_probe, + .device_remove = monitor_linkloss_remove, +}; + +static struct btd_profile pxp_monitor_immediate_profile = { + .name = "proximity-immediate", + .remote_uuid = IMMEDIATE_ALERT_UUID, + .device_probe = monitor_immediate_probe, + .device_remove = monitor_immediate_remove, +}; + +static struct btd_profile pxp_monitor_txpower_profile = { + .name = "proximity-txpower", + .remote_uuid = TX_POWER_UUID, + .device_probe = monitor_txpower_probe, + .device_remove = monitor_txpower_remove, +}; + +static struct btd_profile pxp_reporter_profile = { + .name = "Proximity Reporter GATT Driver", + .remote_uuid = GATT_UUID, + .device_probe = reporter_device_probe, + .device_remove = reporter_device_remove, + + .adapter_probe = reporter_adapter_probe, + .adapter_remove = reporter_adapter_remove, +}; + +static void load_config_file(GKeyFile *config) +{ + char **list; + int i; + + if (config == NULL) + return; + + 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], "FindMe")) + enabled.findme = FALSE; + else if (g_str_equal(list[i], "LinkLoss")) + enabled.linkloss = FALSE; + else if (g_str_equal(list[i], "PathLoss")) + enabled.pathloss = FALSE; + } + + g_strfreev(list); +} + +int proximity_manager_init(GKeyFile *config) +{ + load_config_file(config); + + if (btd_profile_register(&pxp_monitor_linkloss_profile) < 0) + goto fail; + + if (btd_profile_register(&pxp_monitor_immediate_profile) < 0) + goto fail; + + if (btd_profile_register(&pxp_monitor_txpower_profile) < 0) + goto fail; + + if (btd_profile_register(&pxp_reporter_profile) < 0) + goto fail; + + return 0; + +fail: + proximity_manager_exit(); + + return -1; +} + +void proximity_manager_exit(void) +{ + btd_profile_unregister(&pxp_reporter_profile); + btd_profile_unregister(&pxp_monitor_txpower_profile); + btd_profile_unregister(&pxp_monitor_immediate_profile); + btd_profile_unregister(&pxp_monitor_linkloss_profile); +} diff --git a/profiles/proximity/manager.h b/profiles/proximity/manager.h new file mode 100755 index 00000000..e65c31d8 --- /dev/null +++ b/profiles/proximity/manager.h @@ -0,0 +1,26 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011 Nokia Corporation + * Copyright (C) 2011 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 + * + */ + +int proximity_manager_init(GKeyFile *conf); +void proximity_manager_exit(void); diff --git a/profiles/proximity/monitor.c b/profiles/proximity/monitor.c new file mode 100755 index 00000000..a583eb7d --- /dev/null +++ b/profiles/proximity/monitor.c @@ -0,0 +1,822 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011 Nokia Corporation + * Copyright (C) 2011 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 <errno.h> +#include <fcntl.h> +#include <stdint.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <sys/stat.h> + +#include <glib.h> + +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/uuid.h" + +#include "gdbus/gdbus.h" + +#include "src/dbus-common.h" +#include "src/adapter.h" +#include "src/device.h" +#include "src/error.h" +#include "src/log.h" +#include "attrib/att.h" +#include "attrib/gattrib.h" +#include "attrib/gatt.h" +#include "src/attio.h" +#include "src/textfile.h" + +#include "monitor.h" + +#define PROXIMITY_INTERFACE "org.bluez.ProximityMonitor1" + +#define ALERT_LEVEL_CHR_UUID 0x2A06 +#define POWER_LEVEL_CHR_UUID 0x2A07 + +#define IMMEDIATE_TIMEOUT 5 +#define TX_POWER_SIZE 1 + +enum { + ALERT_NONE = 0, + ALERT_MILD, + ALERT_HIGH, +}; + +struct monitor { + struct btd_device *device; + GAttrib *attrib; + struct att_range *linkloss; + struct att_range *txpower; + struct att_range *immediate; + struct enabled enabled; + char *linklosslevel; /* Link Loss Alert Level */ + char *fallbacklevel; /* Immediate fallback alert level */ + char *immediatelevel; /* Immediate Alert Level */ + char *signallevel; /* Path Loss RSSI level */ + uint16_t linklosshandle; /* Link Loss Characteristic + * Value Handle */ + uint16_t txpowerhandle; /* Tx Characteristic Value Handle */ + uint16_t immediatehandle; /* Immediate Alert Value Handle */ + guint immediateto; /* Reset Immediate Alert to "none" */ + guint attioid; +}; + +static GSList *monitors = NULL; + +static struct monitor *find_monitor(struct btd_device *device) +{ + GSList *l; + + for (l = monitors; l; l = l->next) { + struct monitor *monitor = l->data; + + if (monitor->device == device) + return monitor; + } + + return NULL; +} + +static void write_proximity_config(struct btd_device *device, const char *alert, + const char *level) +{ + char *filename; + GKeyFile *key_file; + char *data; + gsize length = 0; + + filename = btd_device_get_storage_path(device, "proximity"); + if (!filename) { + warn("Unable to get proximity storage path for device"); + return; + } + + key_file = g_key_file_new(); + g_key_file_load_from_file(key_file, filename, 0, NULL); + + if (level) + g_key_file_set_string(key_file, alert, "Level", level); + else + g_key_file_remove_group(key_file, alert, NULL); + + data = g_key_file_to_data(key_file, &length, NULL); + if (length > 0) { + create_file(filename, S_IRUSR | S_IWUSR); + g_file_set_contents(filename, data, length, NULL); + } + + g_free(data); + g_free(filename); + g_key_file_free(key_file); +} + +static char *read_proximity_config(struct btd_device *device, const char *alert) +{ + char *filename; + GKeyFile *key_file; + char *str; + + filename = btd_device_get_storage_path(device, "proximity"); + if (!filename) { + warn("Unable to get proximity storage path for device"); + return NULL; + } + + key_file = g_key_file_new(); + g_key_file_load_from_file(key_file, filename, 0, NULL); + + str = g_key_file_get_string(key_file, alert, "Level", NULL); + + g_free(filename); + g_key_file_free(key_file); + + return str; +} + +static uint8_t str2level(const char *level) +{ + if (g_strcmp0("high", level) == 0) + return ALERT_HIGH; + else if (g_strcmp0("mild", level) == 0) + return ALERT_MILD; + + return ALERT_NONE; +} + +static void linkloss_written(guint8 status, const guint8 *pdu, guint16 plen, + gpointer user_data) +{ + struct monitor *monitor = user_data; + struct btd_device *device = monitor->device; + const char *path = device_get_path(device); + + if (status != 0) { + error("Link Loss Write Request failed: %s", + att_ecode2str(status)); + return; + } + + if (!dec_write_resp(pdu, plen)) { + error("Link Loss Write Request: protocol error"); + return; + } + + DBG("Link Loss Alert Level written"); + + g_dbus_emit_property_changed(btd_get_dbus_connection(), path, + PROXIMITY_INTERFACE, "LinkLossAlertLevel"); +} + +static void char_discovered_cb(uint8_t status, GSList *characteristics, + void *user_data) +{ + struct monitor *monitor = user_data; + struct gatt_char *chr; + uint8_t value = str2level(monitor->linklosslevel); + + if (status) { + error("Discover Link Loss handle: %s", att_ecode2str(status)); + return; + } + + DBG("Setting alert level \"%s\" on Reporter", monitor->linklosslevel); + + /* Assume there is a single Alert Level characteristic */ + chr = characteristics->data; + monitor->linklosshandle = chr->value_handle; + + gatt_write_char(monitor->attrib, monitor->linklosshandle, &value, 1, + linkloss_written, monitor); +} + +static int write_alert_level(struct monitor *monitor) +{ + struct att_range *linkloss = monitor->linkloss; + bt_uuid_t uuid; + + if (monitor->linklosshandle) { + uint8_t value = str2level(monitor->linklosslevel); + + gatt_write_char(monitor->attrib, monitor->linklosshandle, + &value, 1, linkloss_written, monitor); + return 0; + } + + bt_uuid16_create(&uuid, ALERT_LEVEL_CHR_UUID); + + /* FIXME: use cache (requires service changed support) ? */ + gatt_discover_char(monitor->attrib, linkloss->start, linkloss->end, + &uuid, char_discovered_cb, monitor); + + return 0; +} + +static void tx_power_read_cb(guint8 status, const guint8 *pdu, guint16 plen, + gpointer user_data) +{ + uint8_t value[TX_POWER_SIZE]; + ssize_t vlen; + + if (status != 0) { + DBG("Tx Power Level read failed: %s", att_ecode2str(status)); + return; + } + + vlen = dec_read_resp(pdu, plen, value, sizeof(value)); + if (vlen < 0) { + DBG("Protocol error"); + return; + } + + if (vlen != 1) { + DBG("Invalid length for TX Power value: %zd", vlen); + return; + } + + DBG("Tx Power Level: %02x", (int8_t) value[0]); +} + +static void tx_power_handle_cb(uint8_t status, GSList *characteristics, + void *user_data) +{ + struct monitor *monitor = user_data; + struct gatt_char *chr; + + if (status) { + error("Discover Tx Power handle: %s", att_ecode2str(status)); + return; + } + + chr = characteristics->data; + monitor->txpowerhandle = chr->value_handle; + + DBG("Tx Power handle: 0x%04x", monitor->txpowerhandle); + + gatt_read_char(monitor->attrib, monitor->txpowerhandle, + tx_power_read_cb, monitor); +} + +static void read_tx_power(struct monitor *monitor) +{ + struct att_range *txpower = monitor->txpower; + bt_uuid_t uuid; + + if (monitor->txpowerhandle != 0) { + gatt_read_char(monitor->attrib, monitor->txpowerhandle, + tx_power_read_cb, monitor); + return; + } + + bt_uuid16_create(&uuid, POWER_LEVEL_CHR_UUID); + + gatt_discover_char(monitor->attrib, txpower->start, txpower->end, + &uuid, tx_power_handle_cb, monitor); +} + +static gboolean immediate_timeout(gpointer user_data) +{ + struct monitor *monitor = user_data; + const char *path = device_get_path(monitor->device); + + monitor->immediateto = 0; + + if (g_strcmp0(monitor->immediatelevel, "none") == 0) + return FALSE; + + if (monitor->attrib) { + uint8_t value = ALERT_NONE; + gatt_write_cmd(monitor->attrib, monitor->immediatehandle, + &value, 1, NULL, NULL); + } + + g_free(monitor->immediatelevel); + monitor->immediatelevel = g_strdup("none"); + + + g_dbus_emit_property_changed(btd_get_dbus_connection(), path, + PROXIMITY_INTERFACE, "ImmediateAlertLevel"); + + return FALSE; +} + +static void immediate_written(gpointer user_data) +{ + struct monitor *monitor = user_data; + const char *path = device_get_path(monitor->device); + + g_free(monitor->fallbacklevel); + monitor->fallbacklevel = NULL; + + + g_dbus_emit_property_changed(btd_get_dbus_connection(), path, + PROXIMITY_INTERFACE, "ImmediateAlertLevel"); + + monitor->immediateto = g_timeout_add_seconds(IMMEDIATE_TIMEOUT, + immediate_timeout, monitor); +} + +static void write_immediate_alert(struct monitor *monitor) +{ + uint8_t value = str2level(monitor->immediatelevel); + + gatt_write_cmd(monitor->attrib, monitor->immediatehandle, &value, 1, + immediate_written, monitor); +} + +static void immediate_handle_cb(uint8_t status, GSList *characteristics, + void *user_data) +{ + struct monitor *monitor = user_data; + struct gatt_char *chr; + + if (status) { + error("Discover Immediate Alert handle: %s", + att_ecode2str(status)); + return; + } + + chr = characteristics->data; + monitor->immediatehandle = chr->value_handle; + + DBG("Immediate Alert handle: 0x%04x", monitor->immediatehandle); + + if (monitor->fallbacklevel) + write_immediate_alert(monitor); +} + +static void discover_immediate_handle(struct monitor *monitor) +{ + struct att_range *immediate = monitor->immediate; + bt_uuid_t uuid; + + bt_uuid16_create(&uuid, ALERT_LEVEL_CHR_UUID); + + gatt_discover_char(monitor->attrib, immediate->start, immediate->end, + &uuid, immediate_handle_cb, monitor); +} + +static void attio_connected_cb(GAttrib *attrib, gpointer user_data) +{ + struct monitor *monitor = user_data; + + monitor->attrib = g_attrib_ref(attrib); + + if (monitor->enabled.linkloss) + write_alert_level(monitor); + + if (monitor->enabled.pathloss) + read_tx_power(monitor); + + if (monitor->immediatehandle == 0) { + if(monitor->enabled.pathloss || monitor->enabled.findme) + discover_immediate_handle(monitor); + } else if (monitor->fallbacklevel) + write_immediate_alert(monitor); +} + +static void attio_disconnected_cb(gpointer user_data) +{ + struct monitor *monitor = user_data; + const char *path = device_get_path(monitor->device); + + g_attrib_unref(monitor->attrib); + monitor->attrib = NULL; + + if (monitor->immediateto == 0) + return; + + g_source_remove(monitor->immediateto); + monitor->immediateto = 0; + + if (g_strcmp0(monitor->immediatelevel, "none") == 0) + return; + + g_free(monitor->immediatelevel); + monitor->immediatelevel = g_strdup("none"); + + g_dbus_emit_property_changed(btd_get_dbus_connection(), path, + PROXIMITY_INTERFACE, "ImmediateAlertLevel"); +} + +static gboolean level_is_valid(const char *level) +{ + return (g_str_equal("none", level) || + g_str_equal("mild", level) || + g_str_equal("high", level)); +} + +static gboolean property_get_link_loss_level(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct monitor *monitor = data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, + &monitor->linklosslevel); + + return TRUE; +} + +static void property_set_link_loss_level(const GDBusPropertyTable *property, + DBusMessageIter *iter, GDBusPendingPropertySet id, void *data) +{ + struct monitor *monitor = data; + const char *level; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) { + g_dbus_pending_property_error(id, + ERROR_INTERFACE ".InvalidArguments", + "Invalid arguments in method call"); + return; + } + + dbus_message_iter_get_basic(iter, &level); + + if (!level_is_valid(level)) { + g_dbus_pending_property_error(id, + ERROR_INTERFACE ".InvalidArguments", + "Invalid arguments in method call"); + return; + } + + if (g_strcmp0(monitor->linklosslevel, level) == 0) + goto done; + + g_free(monitor->linklosslevel); + monitor->linklosslevel = g_strdup(level); + + write_proximity_config(monitor->device, "LinkLossAlertLevel", level); + + if (monitor->attrib) + write_alert_level(monitor); + +done: + g_dbus_pending_property_success(id); +} + +static gboolean property_exists_link_loss_level( + const GDBusPropertyTable *property, void *data) +{ + struct monitor *monitor = data; + + if (!monitor->enabled.linkloss) + return FALSE; + + return TRUE; +} + +static gboolean property_get_immediate_alert_level( + const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct monitor *monitor = data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, + &monitor->immediatelevel); + + return TRUE; +} + +static void property_set_immediate_alert_level( + const GDBusPropertyTable *property, DBusMessageIter *iter, + GDBusPendingPropertySet id, void *data) +{ + struct monitor *monitor = data; + struct btd_device *device = monitor->device; + const char *level; + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_STRING) { + g_dbus_pending_property_error(id, + ERROR_INTERFACE ".InvalidArguments", + "Invalid arguments in method call"); + return; + } + + dbus_message_iter_get_basic(iter, &level); + + if (!level_is_valid(level)) { + g_dbus_pending_property_error(id, + ERROR_INTERFACE ".InvalidArguments", + "Invalid arguments in method call"); + return; + } + + if (g_strcmp0(monitor->immediatelevel, level) == 0) + goto done; + + if (monitor->immediateto) { + g_source_remove(monitor->immediateto); + monitor->immediateto = 0; + } + + /* Previous Immediate Alert level if connection/write fails */ + g_free(monitor->fallbacklevel); + monitor->fallbacklevel = monitor->immediatelevel; + + monitor->immediatelevel = g_strdup(level); + + /* + * Means that Link/Path Loss are disabled or there is a pending + * writting for Find Me(Immediate Alert characteristic value). + * If enabled, Path Loss always registers a connection callback + * when the Proximity Monitor starts. + */ + if (monitor->attioid == 0) + monitor->attioid = btd_device_add_attio_callback(device, + attio_connected_cb, + attio_disconnected_cb, + monitor); + else if (monitor->attrib) + write_immediate_alert(monitor); + +done: + g_dbus_pending_property_success(id); +} + +static gboolean property_exists_immediate_alert_level( + const GDBusPropertyTable *property, void *data) +{ + struct monitor *monitor = data; + + if (!(monitor->enabled.findme || monitor->enabled.pathloss)) + return FALSE; + + return TRUE; +} + +static gboolean property_get_signal_level( + const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct monitor *monitor = data; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, + &monitor->signallevel); + + return TRUE; +} + +static gboolean property_exists_signal_level(const GDBusPropertyTable *property, + void *data) +{ + struct monitor *monitor = data; + + if (!monitor->enabled.pathloss) + return FALSE; + + return TRUE; +} + +static const GDBusPropertyTable monitor_device_properties[] = { + { "LinkLossAlertLevel", "s", property_get_link_loss_level, + property_set_link_loss_level, + property_exists_link_loss_level }, + { "ImmediateAlertLevel", "s", property_get_immediate_alert_level, + property_set_immediate_alert_level, + property_exists_immediate_alert_level }, + { "SignalLevel", "s", property_get_signal_level, NULL, + property_exists_signal_level }, + { } +}; + +static void monitor_destroy(gpointer user_data) +{ + struct monitor *monitor = user_data; + + monitors = g_slist_remove(monitors, monitor); + + btd_device_unref(monitor->device); + g_free(monitor->linklosslevel); + g_free(monitor->immediatelevel); + g_free(monitor->signallevel); + g_free(monitor); +} + +static struct monitor *register_monitor(struct btd_device *device) +{ + const char *path = device_get_path(device); + struct monitor *monitor; + char *level; + + monitor = find_monitor(device); + if (monitor != NULL) + return monitor; + + level = read_proximity_config(device, "LinkLossAlertLevel"); + + monitor = g_new0(struct monitor, 1); + monitor->device = btd_device_ref(device); + monitor->linklosslevel = (level ? : g_strdup("high")); + monitor->signallevel = g_strdup("unknown"); + monitor->immediatelevel = g_strdup("none"); + + monitors = g_slist_append(monitors, monitor); + + if (g_dbus_register_interface(btd_get_dbus_connection(), path, + PROXIMITY_INTERFACE, + NULL, NULL, monitor_device_properties, + monitor, monitor_destroy) == FALSE) { + error("D-Bus failed to register %s interface", + PROXIMITY_INTERFACE); + monitor_destroy(monitor); + return NULL; + } + + DBG("Registered interface %s on path %s", PROXIMITY_INTERFACE, path); + + return monitor; +} + +static void update_monitor(struct monitor *monitor) +{ + if (monitor->txpower != NULL && monitor->immediate != NULL) + monitor->enabled.pathloss = TRUE; + else + monitor->enabled.pathloss = FALSE; + + DBG("Link Loss: %s, Path Loss: %s, FindMe: %s", + monitor->enabled.linkloss ? "TRUE" : "FALSE", + monitor->enabled.pathloss ? "TRUE" : "FALSE", + monitor->enabled.findme ? "TRUE" : "FALSE"); + + if (!monitor->enabled.linkloss && !monitor->enabled.pathloss) + return; + + if (monitor->attioid != 0) + return; + + monitor->attioid = btd_device_add_attio_callback(monitor->device, + attio_connected_cb, + attio_disconnected_cb, + monitor); +} + +int monitor_register_linkloss(struct btd_device *device, + struct enabled *enabled, + struct gatt_primary *linkloss) +{ + struct monitor *monitor; + + if (!enabled->linkloss) + return 0; + + monitor = register_monitor(device); + if (monitor == NULL) + return -1; + + monitor->linkloss = g_new0(struct att_range, 1); + monitor->linkloss->start = linkloss->range.start; + monitor->linkloss->end = linkloss->range.end; + monitor->enabled.linkloss = TRUE; + + update_monitor(monitor); + + return 0; +} + +int monitor_register_txpower(struct btd_device *device, + struct enabled *enabled, + struct gatt_primary *txpower) +{ + struct monitor *monitor; + + if (!enabled->pathloss) + return 0; + + monitor = register_monitor(device); + if (monitor == NULL) + return -1; + + monitor->txpower = g_new0(struct att_range, 1); + monitor->txpower->start = txpower->range.start; + monitor->txpower->end = txpower->range.end; + + update_monitor(monitor); + + return 0; +} + +int monitor_register_immediate(struct btd_device *device, + struct enabled *enabled, + struct gatt_primary *immediate) +{ + struct monitor *monitor; + + if (!enabled->pathloss && !enabled->findme) + return 0; + + monitor = register_monitor(device); + if (monitor == NULL) + return -1; + + monitor->immediate = g_new0(struct att_range, 1); + monitor->immediate->start = immediate->range.start; + monitor->immediate->end = immediate->range.end; + monitor->enabled.findme = enabled->findme; + + update_monitor(monitor); + + return 0; +} + +static void cleanup_monitor(struct monitor *monitor) +{ + struct btd_device *device = monitor->device; + const char *path = device_get_path(device); + + if (monitor->immediate != NULL || monitor->txpower != NULL) + return; + + if (monitor->immediateto != 0) { + g_source_remove(monitor->immediateto); + monitor->immediateto = 0; + } + + if (monitor->linkloss != NULL) + return; + + if (monitor->attioid != 0) { + btd_device_remove_attio_callback(device, monitor->attioid); + monitor->attioid = 0; + } + + if (monitor->attrib != NULL) { + g_attrib_unref(monitor->attrib); + monitor->attrib = NULL; + } + + g_dbus_unregister_interface(btd_get_dbus_connection(), path, + PROXIMITY_INTERFACE); +} + +void monitor_unregister_linkloss(struct btd_device *device) +{ + struct monitor *monitor; + + monitor = find_monitor(device); + if (monitor == NULL) + return; + + g_free(monitor->linkloss); + monitor->linkloss = NULL; + monitor->enabled.linkloss = FALSE; + + cleanup_monitor(monitor); +} + +void monitor_unregister_txpower(struct btd_device *device) +{ + struct monitor *monitor; + + monitor = find_monitor(device); + if (monitor == NULL) + return; + + g_free(monitor->txpower); + monitor->txpower = NULL; + monitor->enabled.pathloss = FALSE; + + cleanup_monitor(monitor); +} + +void monitor_unregister_immediate(struct btd_device *device) +{ + struct monitor *monitor; + + monitor = find_monitor(device); + if (monitor == NULL) + return; + + g_free(monitor->immediate); + monitor->immediate = NULL; + monitor->enabled.findme = FALSE; + monitor->enabled.pathloss = FALSE; + + cleanup_monitor(monitor); +} diff --git a/profiles/proximity/monitor.h b/profiles/proximity/monitor.h new file mode 100755 index 00000000..d9a40c60 --- /dev/null +++ b/profiles/proximity/monitor.h @@ -0,0 +1,43 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011 Nokia Corporation + * Copyright (C) 2011 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 + * + */ + +struct enabled { + gboolean linkloss; + gboolean pathloss; + gboolean findme; +}; + +int monitor_register_linkloss(struct btd_device *device, + struct enabled *enabled, + struct gatt_primary *linkloss); +int monitor_register_txpower(struct btd_device *device, + struct enabled *enabled, + struct gatt_primary *txpower); +int monitor_register_immediate(struct btd_device *device, + struct enabled *enabled, + struct gatt_primary *immediate); + +void monitor_unregister_linkloss(struct btd_device *device); +void monitor_unregister_txpower(struct btd_device *device); +void monitor_unregister_immediate(struct btd_device *device); diff --git a/profiles/proximity/reporter.c b/profiles/proximity/reporter.c new file mode 100755 index 00000000..30fc7c2f --- /dev/null +++ b/profiles/proximity/reporter.c @@ -0,0 +1,330 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011 Nokia Corporation + * Copyright (C) 2011 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 <stdbool.h> +#include <errno.h> + +#include <glib.h> + +#include <dbus/dbus.h> + +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/uuid.h" + +#include "gdbus/gdbus.h" + +#include "src/log.h" +#include "src/dbus-common.h" +#include "src/adapter.h" +#include "src/device.h" +#include "src/profile.h" +#include "src/service.h" +#include "src/shared/util.h" +#include "attrib/gattrib.h" +#include "attrib/att.h" +#include "attrib/gatt.h" +#include "attrib/att-database.h" +#include "src/attrib-server.h" + +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +#include "src/error.h" +#endif + +#include "reporter.h" +#include "linkloss.h" +#include "immalert.h" + +struct reporter_adapter { + struct btd_adapter *adapter; + GSList *devices; +}; + +static GSList *reporter_adapters; + +static int radapter_cmp(gconstpointer a, gconstpointer b) +{ + const struct reporter_adapter *radapter = a; + const struct btd_adapter *adapter = b; + + if (radapter->adapter == adapter) + return 0; + + return -1; +} + +static struct reporter_adapter * +find_reporter_adapter(struct btd_adapter *adapter) +{ + GSList *l = g_slist_find_custom(reporter_adapters, adapter, + radapter_cmp); + if (!l) + return NULL; + + return l->data; +} + +const char *get_alert_level_string(uint8_t level) +{ + switch (level) { + case NO_ALERT: + return "none"; + case MILD_ALERT: + return "mild"; + case HIGH_ALERT: + return "high"; + } + + return "unknown"; +} + +#ifndef TIZEN_FEATURE_BLUEZ_MODIFY +static void register_tx_power(struct btd_adapter *adapter) +{ + uint16_t start_handle, h; + const int svc_size = 4; + uint8_t atval[256]; + bt_uuid_t uuid; + + bt_uuid16_create(&uuid, TX_POWER_SVC_UUID); + start_handle = attrib_db_find_avail(adapter, &uuid, svc_size); + if (start_handle == 0) { + error("Not enough free handles to register service"); + return; + } + + DBG("start_handle=0x%04x", start_handle); + + h = start_handle; + + /* Primary service definition */ + bt_uuid16_create(&uuid, GATT_PRIM_SVC_UUID); + put_le16(TX_POWER_SVC_UUID, &atval[0]); + attrib_db_add(adapter, h++, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 2); + + /* Power level characteristic */ + bt_uuid16_create(&uuid, GATT_CHARAC_UUID); + atval[0] = GATT_CHR_PROP_READ | GATT_CHR_PROP_NOTIFY; + put_le16(h + 1, &atval[1]); + put_le16(POWER_LEVEL_CHR_UUID, &atval[3]); + attrib_db_add(adapter, h++, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 5); + + /* Power level value */ + bt_uuid16_create(&uuid, POWER_LEVEL_CHR_UUID); + atval[0] = 0x00; + attrib_db_add(adapter, h++, &uuid, ATT_NONE, ATT_NOT_PERMITTED, atval, 1); + + /* Client characteristic configuration */ + bt_uuid16_create(&uuid, GATT_CLIENT_CHARAC_CFG_UUID); + atval[0] = 0x00; + atval[1] = 0x00; + attrib_db_add(adapter, h++, &uuid, ATT_NONE, ATT_NONE, atval, 2); + + g_assert(h - start_handle == svc_size); +} +#endif + +static gboolean property_get_link_loss_level(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct btd_device *device = data; + const char *level; + + level = link_loss_get_alert_level(device); + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &level); + + return TRUE; +} + +static gboolean property_get_immediate_alert_level( + const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct btd_device *device = data; + const char *level; + + level = imm_alert_get_level(device); + + dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &level); + + return TRUE; +} + +static const GDBusPropertyTable reporter_device_properties[] = { + { "LinkLossAlertLevel", "s", property_get_link_loss_level }, + { "ImmediateAlertLevel", "s", property_get_immediate_alert_level }, + { } +}; + +static void unregister_reporter_device(gpointer data, gpointer user_data) +{ + struct btd_device *device = data; + struct reporter_adapter *radapter = user_data; + const char *path = device_get_path(device); + + DBG("unregister on device %s", path); + + g_dbus_unregister_interface(btd_get_dbus_connection(), path, + PROXIMITY_REPORTER_INTERFACE); + + radapter->devices = g_slist_remove(radapter->devices, device); + btd_device_unref(device); +} + +static void register_reporter_device(struct btd_device *device, + struct reporter_adapter *radapter) +{ + const char *path = device_get_path(device); + + DBG("register on device %s", path); + + g_dbus_register_interface(btd_get_dbus_connection(), path, + PROXIMITY_REPORTER_INTERFACE, + NULL, NULL, reporter_device_properties, + device, NULL); + + btd_device_ref(device); + radapter->devices = g_slist_prepend(radapter->devices, device); +} + +int reporter_device_probe(struct btd_service *service) +{ + struct btd_device *device = btd_service_get_device(service); + struct reporter_adapter *radapter; + struct btd_adapter *adapter = device_get_adapter(device); + + radapter = find_reporter_adapter(adapter); + if (!radapter) + return -1; + + register_reporter_device(device, radapter); + + return 0; +} + +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +static DBusMessage *register_proximity(DBusConnection *conn, DBusMessage *msg, + void *user_data) +{ + struct btd_adapter *adapter = user_data; + + if(adapter == NULL) { + DBG("Adapter is NULL"); + return btd_error_invalid_args(msg); + } + + link_loss_register(adapter); + imm_alert_register(adapter); + + /* TODO: TX Power service implementation + * is incomplete in BlueZ. + */ + //register_tx_power(adapter); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *unregister_proximity(DBusConnection *conn, DBusMessage *msg, + void *user_data) +{ + struct btd_adapter *adapter = user_data; + + if(adapter == NULL) { + DBG("Adapter is NULL"); + return btd_error_invalid_args(msg); + } + + link_loss_unregister(adapter); + imm_alert_unregister(adapter); + + return dbus_message_new_method_return(msg); +} + +static const GDBusMethodTable reporter_adapter_methods[] = { + { GDBUS_METHOD("RegisterProximity", NULL, NULL, + register_proximity) }, + { GDBUS_METHOD("UnregisterProximity", NULL, NULL, + unregister_proximity) }, + { } +}; +#endif + +void reporter_device_remove(struct btd_service *service) +{ + struct btd_device *device = btd_service_get_device(service); + struct reporter_adapter *radapter; + struct btd_adapter *adapter = device_get_adapter(device); + + radapter = find_reporter_adapter(adapter); + if (!radapter) + return; + + unregister_reporter_device(device, radapter); +} + +int reporter_adapter_probe(struct btd_profile *p, struct btd_adapter *adapter) +{ + struct reporter_adapter *radapter; + + radapter = g_new0(struct reporter_adapter, 1); + radapter->adapter = adapter; +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY + const char *path = adapter_get_path(adapter); + + g_dbus_register_interface(btd_get_dbus_connection(), path, + PROXIMITY_REPORTER_INTERFACE, + reporter_adapter_methods, + NULL, NULL, adapter, NULL); +#else + link_loss_register(adapter); + register_tx_power(adapter); + imm_alert_register(adapter); +#endif + reporter_adapters = g_slist_prepend(reporter_adapters, radapter); + DBG("Proximity Reporter for adapter %p", adapter); + + return 0; +} + +void reporter_adapter_remove(struct btd_profile *p, + struct btd_adapter *adapter) +{ + struct reporter_adapter *radapter = find_reporter_adapter(adapter); + if (!radapter) + return; + + g_slist_foreach(radapter->devices, unregister_reporter_device, + radapter); + + link_loss_unregister(adapter); + imm_alert_unregister(adapter); + + reporter_adapters = g_slist_remove(reporter_adapters, radapter); + g_free(radapter); +} diff --git a/profiles/proximity/reporter.h b/profiles/proximity/reporter.h new file mode 100755 index 00000000..ed2c4dc5 --- /dev/null +++ b/profiles/proximity/reporter.h @@ -0,0 +1,46 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011 Nokia Corporation + * Copyright (C) 2011 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 + * + */ + +#define PROXIMITY_REPORTER_INTERFACE "org.bluez.ProximityReporter1" + +#define IMMEDIATE_ALERT_SVC_UUID 0x1802 +#define LINK_LOSS_SVC_UUID 0x1803 +#define TX_POWER_SVC_UUID 0x1804 +#define ALERT_LEVEL_CHR_UUID 0x2A06 +#define POWER_LEVEL_CHR_UUID 0x2A07 + +enum { + NO_ALERT = 0x00, + MILD_ALERT = 0x01, + HIGH_ALERT = 0x02, +}; + +void reporter_device_remove(struct btd_service *service); +int reporter_device_probe(struct btd_service *service); + +int reporter_adapter_probe(struct btd_profile *p, struct btd_adapter *adapter); +void reporter_adapter_remove(struct btd_profile *p, + struct btd_adapter *adapter); + +const char *get_alert_level_string(uint8_t level);
\ No newline at end of file diff --git a/profiles/tds/manager.c b/profiles/tds/manager.c new file mode 100755 index 00000000..6d13ee32 --- /dev/null +++ b/profiles/tds/manager.c @@ -0,0 +1,68 @@ +/* + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2016 Samsung Electronics Co. Ltd. + * + * 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 "lib/bluetooth.h" +#include "lib/uuid.h" +#include "src/plugin.h" + +#include "gdbus/gdbus.h" + +#include "src/adapter.h" +#include "src/profile.h" + +#include "tds.h" + +int tds_provider_adapter_probe(struct btd_profile *p, + struct btd_adapter *adapter) +{ + tds_register_provider_interface(adapter); + return 0; +} + +void tds_provider_adapter_remove(struct btd_profile *p, + struct btd_adapter *adapter) +{ + tds_unregister_provider_interface(adapter); +} + +static struct btd_profile tds_provider = { + .name = "TDS Provider GATT Driver", + .remote_uuid = GATT_UUID, + .adapter_probe = tds_provider_adapter_probe, + .adapter_remove = tds_provider_adapter_remove, +}; + +static int tds_provider_init(void) +{ + return btd_profile_register(&tds_provider); +} + +static void tds_provider_exit(void) +{ + btd_profile_unregister(&tds_provider); +} + +BLUETOOTH_PLUGIN_DEFINE(tds, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, + tds_provider_init, tds_provider_exit) diff --git a/profiles/tds/tds.c b/profiles/tds/tds.c new file mode 100755 index 00000000..786bf3dc --- /dev/null +++ b/profiles/tds/tds.c @@ -0,0 +1,785 @@ +/* + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2016 Samsung Electronics Co. Ltd. + * + * 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 <stdbool.h> + +#include <glib.h> + +#include <dbus/dbus.h> +#include <time.h> + +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/uuid.h" +#include "src/plugin.h" + +#include "gdbus/gdbus.h" + +#include "src/error.h" +#include "src/log.h" +#include "src/adapter.h" + +#include "attrib/gattrib.h" +#include "attrib/att.h" +#include "attrib/gatt.h" +#include "attrib/att-database.h" +#include "attrib/gatt-service.h" + +#include "src/shared/gatt-server.h" +#include "src/attrib-server.h" +#include "src/device.h" +#include "src/profile.h" +#include "src/attio.h" +#include "src/dbus-common.h" + +#include "tds.h" + +#ifdef TIZEN_FEATURE_BLUEZ_MODIFY +#include "src/shared/queue.h" +#include "src/shared/gatt-db.h" +#include "src/shared/att.h" + #include "btio/btio.h" +#include "src/gatt-database.h" +#endif + + +#define TDS_USER_CHARACTERITIC_UUID 0x2af6 +#define TDS_USER_CHARACTERITIC_DESCRIPTOR_UUID 0x2a56 + +/* TDS Block Data */ +struct tds_block_data { + uint8_t *val; + unsigned int len; +}; + +/* pointer to User characteristic data */ +static struct tds_block_data *ptr = NULL; + +/* Adapter Instance for the provider */ +struct tds_service_adapter { + struct btd_adapter *adapter; + struct gatt_db_attribute *service; + GSList *connected_devices; +}; + +static GSList *tds_service_adapters; + +struct connected_device { + struct btd_device *device; + struct tds_service_adapter *adapter; + guint callback_id; + uint16_t gatt_chr_handle; + unsigned int timeout_id; + bool tds_control_point_ccc_enabled; +}; + +static int tds_adapter_cmp(gconstpointer a, gconstpointer b) +{ + const struct tds_service_adapter *tdsadapter = a; + const struct btd_adapter *adapter = b; + + if (tdsadapter->adapter == adapter) + return 0; + + return -1; +} + +static struct tds_service_adapter * +find_tds_service_adapter(struct btd_adapter *adapter) +{ + GSList *l = g_slist_find_custom(tds_service_adapters, adapter, + tds_adapter_cmp); + if (!l) + return NULL; + + return l->data; +} + +static int device_cmp(gconstpointer a, gconstpointer b) +{ + const struct connected_device *condev = a; + const struct btd_device *device = b; + + if (condev->device == device) + return 0; + + return -1; +} + +static struct connected_device * +find_connected_device(struct tds_service_adapter *adapter, struct btd_device *device) +{ + GSList *l = g_slist_find_custom(adapter->connected_devices, device, + device_cmp); + if (!l) + return NULL; + + return l->data; +} + +static void indication_cfm_cb(void *user_data) +{ + struct connected_device *condev = user_data; + DBG("Received confirmation of Indication Confirmation"); + g_dbus_emit_signal(btd_get_dbus_connection(), device_get_path(condev->device), + TDS_SERVICE_PROVIDER_INTERFACE, "TdsActivationIndCnfm", + DBUS_TYPE_INVALID); +} + +static DBusMessage *tds_activation_response(DBusConnection *connection, + DBusMessage *msg, void *user_data) +{ + struct connected_device *condev = user_data; + uint8_t *value; + int32_t len = 0; + uint8_t result = 0x04; /* Operation Failed */ + int k; /* Debug */ + uint8_t *pdu = NULL; + + DBG("+"); + if (condev->tds_control_point_ccc_enabled == false) { + DBG("CCCD is disabled, can not send indication to remote device"); + return dbus_message_new_method_return(msg); + } + + if (condev->timeout_id == 0) { + DBG("Timer is not running: either no request pending or response came late!!"); + return btd_error_failed(msg, "TDS Activation Request not pending"); + } + + /* Remove & reset Timer */ + g_source_remove(condev->timeout_id); + condev->timeout_id = 0; + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_BYTE, &result, + DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &value, &len, + DBUS_TYPE_INVALID)) + return btd_error_invalid_args(msg); + + DBG("Result [0x%x] data length [%d]", result, len); + + for(k=0; k < len ; k++) + DBG("Data[%d] = [0x%x]", k, value[k]); + + switch(result) { + case 0x00: + DBG("Success"); + break; + case 0x02: + DBG("Invalid Parameter"); + break; + case 0x03: + DBG("Unsupported Organization ID"); + break; + case 0x04: + DBG("Operation Failed"); + break; + default: + return btd_error_invalid_args(msg); + } + + pdu = g_malloc0(sizeof(uint8_t)* (2+ len)); + pdu[0] = 0x01; /* Opcode - TDS Control Point Activation Request */ + pdu[1] = result; + + if (len > 0) { + memcpy(pdu+2, value, len); + } else { + DBG("TDS Response with no parameters"); + } + + DBG("Send Indication to device [%s], chr handle [%d]", device_get_path(condev->device), condev->gatt_chr_handle); + + if (!bt_gatt_server_send_indication(btd_device_get_gatt_server(condev->device), + condev->gatt_chr_handle, + pdu, (2+len), indication_cfm_cb, condev, NULL)) + DBG("Sending Indication Failed!!"); + else + DBG("Sending Indication Successful, wait for confirmation!!"); + + g_free(pdu); + DBG("-"); + return dbus_message_new_method_return(msg); +} + +static void tds_client_remove_condev(struct connected_device *condev) +{ + struct tds_service_adapter *a; + + if (!condev) + return; + + a = condev->adapter; + + if (condev->callback_id && condev->device) + btd_device_remove_attio_callback(condev->device, + condev->callback_id); + + if (condev->device) + btd_device_unref(condev->device); + + a->connected_devices = g_slist_remove(a->connected_devices, condev); + g_free(condev); +} + +static void tds_client_disc_cb(gpointer user_data) +{ + struct connected_device *condev = user_data; + + if (!condev) + return; + + /* Unregister Interface */ + g_dbus_unregister_interface(btd_get_dbus_connection(), + device_get_path(condev->device), + TDS_SERVICE_PROVIDER_INTERFACE); + + DBG("TDS Client remove device %p", condev->device); + tds_client_remove_condev(condev); +} + +static const GDBusSignalTable tds_signals[] = { + { GDBUS_SIGNAL("TdsActivationRequested", + GDBUS_ARGS({ "org_id", "y"}, + { "TdsDataBlock", "ay"})) }, + { GDBUS_SIGNAL("TdsActivationIndCnfm", NULL) }, +}; + +static const GDBusMethodTable tds_methods[] = { + { GDBUS_ASYNC_METHOD("TdsActivationResponse", + GDBUS_ARGS({ "result", "y" }, { "response_param", "ay" }), NULL, + tds_activation_response) }, + { } +}; + +static bool indication_wait_cb(gpointer user_data) +{ + struct connected_device *condev = (struct connected_device *)user_data; + uint16_t len = 2; + uint8_t pdu[2]; + DBG("Indication Timer Expired!!"); + condev->timeout_id = 0; + + if (!condev->tds_control_point_ccc_enabled) { + DBG("CCCD is not Enabled!! No need to send indication"); + return false; + } else { + DBG("CCCD is Enabled!!..Send Indication with Operation Failed!"); + } + + pdu[0] = 0x01; /* Op Code: Activation Request */ + pdu[1] = 0x04; /* Result: Operation Failed*/ + + DBG("Send Indication to device [%s], chr handle [%d]", device_get_path(condev->device), condev->gatt_chr_handle); + + if (!bt_gatt_server_send_indication(btd_device_get_gatt_server(condev->device), + condev->gatt_chr_handle, + pdu, len, indication_cfm_cb, condev, NULL)) + DBG("Sending Indication Failed!!"); + else + DBG("Sending Indication Successful, wait for confirmation!!"); + + return false; +} + +static void tds_control_point_char_write(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + const uint8_t *value, size_t len, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + DBG("len [%d]", len); + DBG("Opcode [%d]", opcode); + DBG("TRansaction ID [%d]", id); + DBG("Offset [%d]", offset); + + uint8_t ecode = 0; + struct btd_device *device = NULL; + struct tds_service_adapter *tsadapter = user_data; + bdaddr_t bdaddr; + uint8_t bdaddr_type; + struct connected_device *condev = NULL; + int k; + const uint8_t *param = NULL; + + if (!value || len < 2) { + ecode = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN; + goto done; + } + + if (offset != 0) { + ecode = BT_ATT_ERROR_INVALID_OFFSET; + goto done; + } + + if (!bt_att_get_remote_addr(att, &bdaddr, &bdaddr_type)) { + ecode = BT_ATT_ERROR_UNLIKELY; + goto done; + } + + device = btd_adapter_get_device(tsadapter->adapter, &bdaddr, bdaddr_type); + + if (!device) { + ecode = BT_ATT_ERROR_UNLIKELY; + goto done; + } + DBG("Device path [%s]", device_get_path(device)); + + /* Create Connected device and Register SIgnal Interface */ + condev = find_connected_device(tsadapter, device); + + if (!condev) { + DBG("Device is NULL..create device"); + condev = g_new0(struct connected_device, 1); + condev->device = btd_device_ref(device); + condev->adapter = tsadapter; + condev->callback_id = btd_device_add_attio_callback(device, + NULL, tds_client_disc_cb, condev); + + tsadapter->connected_devices = g_slist_append(tsadapter->connected_devices, + condev); + DBG("added connected dev %p", device); + /* Register Signal on Device Interface */ + if (!g_dbus_register_interface(btd_get_dbus_connection(), device_get_path(device), + TDS_SERVICE_PROVIDER_INTERFACE, + tds_methods, tds_signals, + NULL, + condev, NULL)) { + error("Unable to register TDS Activation Signal"); + tds_client_remove_condev(condev); + goto done; + } + } + + if (condev->timeout_id) { + DBG("Already one activation request is under progress from device [%s]", device_get_path(device)); + ecode = BT_ERROR_ALREADY_IN_PROGRESS; + goto done; + } + + condev->gatt_chr_handle = gatt_db_attribute_get_handle(attrib); + DBG("Characteristic Attribute handle [0x%x]", condev->gatt_chr_handle); + + /* Write value should be anyone of 0x00, 0x01, 0x02 */ + switch(value[0]) { + case 0x00: { + DBG("Opcode reserved for future use"); + ecode = BT_ATT_ERROR_REQUEST_NOT_SUPPORTED; + goto done; + } + case 0x01: { + DBG("TDS Control Point Activation Request"); + break; + } + default: { + DBG("Invalid Opcode [0x%x]", value[0]); + ecode = 0x80; + goto done; + } + } + + for(k=0; k < len; k++) + DBG("@@TDS Control Point [%d] 0x%x", k, value[k]); + + /* Success case*/ + if (gatt_db_attribute_write_result(attrib, id, ecode)) { + DBG("TDS Control Point Activation write resp sent successfully!!"); + /* Emit Signal */ + len = len -2; + + if (len > 0) { + param = &value[2]; + } + g_dbus_emit_signal(btd_get_dbus_connection(), device_get_path(device), + TDS_SERVICE_PROVIDER_INTERFACE, "TdsActivationRequested", + DBUS_TYPE_BYTE, &value[1], + DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, ¶m, len, + DBUS_TYPE_INVALID); + + /* Start timer for max 10 seconds to wait for Indication from app */ + if (condev->tds_control_point_ccc_enabled) { + DBG("Control point is enabled for device [%s] start the Indication Timer", device_get_path(device)); + if (condev->timeout_id) + g_source_remove(condev->timeout_id); + condev->timeout_id = g_timeout_add(10000, (GSourceFunc)indication_wait_cb, condev); + } else { + DBG("Control point is Not enabled for device [%s] Dont start the Indication Timer",device_get_path(device)); + } + } else { + DBG("TDS Control Point Activation write resp sending failed!!!"); + } + + return; +done: + gatt_db_attribute_write_result(attrib, id, ecode); +} + +static void tds_user_data_descriptor_read_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + DBG("TDS User Characteritsic descriptor Read requested.."); + + if (!ptr) { + DBG("TDS Block data still not set"); + gatt_db_attribute_read_result(attrib, id, 0, NULL, 0); + } else { + gatt_db_attribute_read_result(attrib, id, 0, ptr->val, ptr->len); + } +} + +static void tds_control_point_ccc_read_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct tds_service_adapter *adapter = user_data; + struct btd_device *device = NULL; + bdaddr_t bdaddr; + uint8_t bdaddr_type; + struct connected_device *condev = NULL; + uint8_t ecode = 0; + uint8_t value[2]; + DBG("TDS Control Point CCC Read requested.."); + + if (!bt_att_get_remote_addr(att, &bdaddr, &bdaddr_type)) { + ecode = BT_ATT_ERROR_UNLIKELY; + goto done; + } + + device = btd_adapter_get_device(adapter->adapter, &bdaddr, bdaddr_type); + + if (!device) { + ecode = BT_ATT_ERROR_UNLIKELY; + goto done; + } + DBG("Device path [%s]", device_get_path(device)); + + /* Create Connected device and Register Signal Interface */ + condev = find_connected_device(adapter, device); + if (!condev) { + DBG("Device is not created yet, default CCCD value is Disabled"); + value[0] = 0x00; + } else { + DBG("CCCD is [%s] for device [%s]", condev->tds_control_point_ccc_enabled ? "Enabled" : "Disabled", device_get_path(device)); + value[0] = condev->tds_control_point_ccc_enabled; + } + + value[1] = 0x00; + +done: + gatt_db_attribute_read_result(attrib, id, ecode, value, 2); +} + + +static void tds_user_char_read_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + uint8_t value[1]; + DBG("TDS user char Read requested.."); + value[0] = 0x01; + gatt_db_attribute_read_result(attrib, id, 0, value, 1); +} + +static void tds_control_point_ccc_write_cb(struct gatt_db_attribute *attrib, + unsigned int id, uint16_t offset, + const uint8_t *value, size_t len, + uint8_t opcode, struct bt_att *att, + void *user_data) +{ + struct tds_service_adapter *adapter = user_data; + struct btd_device *device = NULL; + bdaddr_t bdaddr; + uint8_t bdaddr_type; + struct connected_device *condev = NULL; + uint8_t ecode = 0; + DBG("TDS Control Point CCC Write requested..len [%d] val [0x%x] val [0x%x]", len, value[0], value[1]); + + if (!value || len != 2) { + ecode = BT_ATT_ERROR_INVALID_ATTRIBUTE_VALUE_LEN; + goto done; + } + + if (offset) { + ecode = BT_ATT_ERROR_INVALID_OFFSET; + goto done; + } + + if (!bt_att_get_remote_addr(att, &bdaddr, &bdaddr_type)) { + ecode = BT_ATT_ERROR_UNLIKELY; + goto done; + } + + device = btd_adapter_get_device(adapter->adapter, &bdaddr, bdaddr_type); + + if (!device) { + ecode = BT_ATT_ERROR_UNLIKELY; + goto done; + } + DBG("Device path [%s]", device_get_path(device)); + + /* Create Connected device and Register Signal Interface */ + condev = find_connected_device(adapter, device); + + if (!condev) { + DBG("Device is NULL..create device"); + condev = g_new0(struct connected_device, 1); + condev->device = btd_device_ref(device); + condev->adapter = adapter; + condev->callback_id = btd_device_add_attio_callback(device, + NULL, tds_client_disc_cb, condev); + + adapter->connected_devices = g_slist_append(adapter->connected_devices, + condev); + DBG("added connected dev %p", device); + + /* Register Signal on Device Interface */ + if (!g_dbus_register_interface(btd_get_dbus_connection(), device_get_path(device), + TDS_SERVICE_PROVIDER_INTERFACE, + tds_methods, tds_signals, + NULL, + condev, NULL)) { + error("Unable to register TDS Activation Signal"); + tds_client_remove_condev(condev); + goto done; + } + } + + if (value[0] == 0x00) { + DBG("CCCD is Disabled by Client [%s]", device_get_path(device)); + condev->tds_control_point_ccc_enabled = false; + } else if (value[0] == 0x02) { /* Indication */ + if (condev->tds_control_point_ccc_enabled) { + DBG("TDS Control point CCCD Already Enabled\n"); + goto done; + } + + DBG("CCCD is Enabled by Client [%s]", device_get_path(device)); + condev->tds_control_point_ccc_enabled = true; + } else + ecode = 0x80; + + DBG("TDS Server: Control Point Enabled: [%s]\n", + condev->tds_control_point_ccc_enabled ? "true" : "false"); + +done: + gatt_db_attribute_write_result(attrib, id, ecode); +} + +void tds_service_unregister(struct tds_service_adapter *tsadapter) +{ + DBG("TDS Service UnRegister.."); + struct gatt_db *db; + + /* Remove registered service */ + if (tsadapter->service) { + db = (struct gatt_db *) btd_gatt_database_get_db(btd_adapter_get_database(tsadapter->adapter)); + gatt_db_remove_service(db, tsadapter->service); + } + + if (ptr) { + g_free(ptr->val); + g_free(ptr); + ptr = NULL; + } +} + +void tds_service_register(struct tds_service_adapter *tsadapter) +{ + DBG("TDS Service Register.."); + struct gatt_db_attribute *service, *char_tds_control, *char_user_char, *desc_tds_ccc, *desc_user; + struct gatt_db *db; + + bt_uuid_t uuid; + bt_uuid16_create(&uuid, TRANSPORT_DISCOVERY_SERVICE_UUID); + + db = (struct gatt_db *) btd_gatt_database_get_db(btd_adapter_get_database(tsadapter->adapter)); + + /* + * TDS Primary Service + */ + service = gatt_db_add_service(db, &uuid, true, 7); + if (!service) + goto err; + + tsadapter->service = service; + DBG("TDS Primary Service added"); + + /* + * TDS Control Point characteristic. + */ + bt_uuid16_create(&uuid, TDS_CONTROL_POINT_CHARACTERISTIC_UUID); + char_tds_control = gatt_db_service_add_characteristic(service, &uuid, BT_ATT_PERM_WRITE, + BT_GATT_CHRC_PROP_WRITE | BT_GATT_CHRC_PROP_INDICATE, + NULL, /* Non Readable */ + tds_control_point_char_write, tsadapter); + + if (!char_tds_control) + goto err; + DBG("TDS Control Point char added"); + + + bt_uuid16_create(&uuid, GATT_CLIENT_CHARAC_CFG_UUID); + desc_tds_ccc = gatt_db_service_add_descriptor(char_tds_control, &uuid, + BT_ATT_PERM_READ | BT_ATT_PERM_WRITE, + tds_control_point_ccc_read_cb, + tds_control_point_ccc_write_cb, tsadapter); + + if (!desc_tds_ccc) + goto err; + DBG("TDS Control Point CCCD added"); + /* + * TDS User characteristic. + */ + bt_uuid16_create(&uuid, TDS_USER_CHARACTERITIC_UUID); + char_user_char = gatt_db_service_add_characteristic(service, &uuid, BT_ATT_PERM_NONE, + BT_ATT_PERM_READ, + tds_user_char_read_cb, + NULL, /* Non Writable */ + NULL); + + if (!char_user_char) + goto err; + + DBG("TDS User Characteristic added"); + bt_uuid16_create(&uuid, TDS_USER_CHARACTERITIC_DESCRIPTOR_UUID); + desc_user = gatt_db_service_add_descriptor(char_user_char, &uuid, + BT_ATT_PERM_READ, + tds_user_data_descriptor_read_cb, + NULL, /* Non Writable */ + tsadapter); + if (!desc_user) + goto err; + + DBG("TDS User Char Descriptor added..."); + gatt_db_service_set_active(service, true); + + DBG("TDS Service activated"); + return; + +err: + error("Error adding TDS service"); + tds_service_unregister(tsadapter); +} + +static DBusMessage *register_tds_proider(DBusConnection *conn, DBusMessage *msg, + void *user_data) +{ + DBG("TDS Provider Register"); + struct tds_service_adapter *tsadapter = user_data; + + if (tsadapter->adapter == NULL) { + DBG("Adapter is NULL"); + return btd_error_invalid_args(msg); + } + + tds_service_register(tsadapter); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *set_tds_block_data(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + uint8_t *value; + int32_t len = 0; + + DBG("Set TDS Block data"); + + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE, &value, &len, + DBUS_TYPE_INVALID)) + return btd_error_invalid_args(msg); + + /*TODO Max length to be checked*/ + if (len < 1) + return btd_error_invalid_args(msg); + + if (ptr) { + g_free(ptr->val); + g_free(ptr); + } + ptr = g_malloc0(sizeof(struct tds_block_data)); + ptr->val = g_memdup(value, len); + ptr->len = len; + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *unregister_tds_provider(DBusConnection *conn, DBusMessage *msg, + void *user_data) +{ + struct tds_service_adapter *tsadapter = user_data; + + if (tsadapter->adapter == NULL) { + DBG("Adapter is NULL"); + return btd_error_invalid_args(msg); + } + + tds_service_unregister(tsadapter); + return dbus_message_new_method_return(msg); +} + +static const GDBusMethodTable tds_provider_adapter_methods[] = { + { GDBUS_METHOD("RegisterTdsProvider", NULL, NULL, + register_tds_proider) }, + { GDBUS_METHOD("UnregisterTdsProvider", NULL, NULL, + unregister_tds_provider) }, + { GDBUS_METHOD("SetTdsBlockData", + GDBUS_ARGS({ "value", "ay" }), NULL, + set_tds_block_data) }, + { } +}; + +void tds_unregister_provider_interface(struct btd_adapter *adapter) +{ + struct tds_service_adapter *tsadapter = find_tds_service_adapter(adapter); + if (!tsadapter) + return; + tds_service_unregister(tsadapter); + + tds_service_adapters = g_slist_remove(tds_service_adapters, tsadapter); + g_free(tsadapter); +} + +void tds_register_provider_interface(struct btd_adapter *adapter) +{ + struct tds_service_adapter *tsadapter; + const char *path = adapter_get_path(adapter); + + tsadapter = g_new0(struct tds_service_adapter, 1); + tsadapter->adapter = adapter; + + g_dbus_register_interface(btd_get_dbus_connection(), path, + TDS_SERVICE_PROVIDER_INTERFACE, + tds_provider_adapter_methods, + NULL, NULL, tsadapter, NULL); + tds_service_adapters = g_slist_append(tds_service_adapters, tsadapter); +} diff --git a/profiles/tds/tds.h b/profiles/tds/tds.h new file mode 100755 index 00000000..85b9b100 --- /dev/null +++ b/profiles/tds/tds.h @@ -0,0 +1,34 @@ +/* + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011 Nokia Corporation + * Copyright (C) 2011 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 + * + */ + +#define TRANSPORT_DISCOVERY_SERVICE_UUID 0x1824 +#define TDS_CONTROL_POINT_CHARACTERISTIC_UUID 0x2abc + +#define TDS_USER_CHARACTERITIC_UUID 0x2af6 +#define TDS_USER_CHARACTERITIC_DESCRIPTOR_UUID 0x2a56 + +#define TDS_SERVICE_PROVIDER_INTERFACE "org.bluez.TdsServiceProvider1" + +void tds_register_provider_interface(struct btd_adapter *adapter); + +void tds_unregister_provider_interface(struct btd_adapter *adapter); diff --git a/profiles/thermometer/thermometer.c b/profiles/thermometer/thermometer.c new file mode 100755 index 00000000..b0fc3e00 --- /dev/null +++ b/profiles/thermometer/thermometer.c @@ -0,0 +1,1321 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011 GSyC/LibreSoft, Universidad Rey Juan Carlos. + * + * 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 <stdbool.h> +#include <errno.h> + +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/uuid.h" + +#include "gdbus/gdbus.h" + +#include "src/plugin.h" +#include "src/dbus-common.h" +#include "src/adapter.h" +#include "src/device.h" +#include "src/profile.h" +#include "src/service.h" +#include "src/shared/util.h" +#include "src/error.h" +#include "src/log.h" +#include "attrib/gattrib.h" +#include "src/attio.h" +#include "attrib/att.h" +#include "attrib/gatt.h" + +#define THERMOMETER_INTERFACE "org.bluez.Thermometer1" +#define THERMOMETER_MANAGER_INTERFACE "org.bluez.ThermometerManager1" +#define THERMOMETER_WATCHER_INTERFACE "org.bluez.ThermometerWatcher1" + +/* Temperature measurement flag fields */ +#define TEMP_UNITS 0x01 +#define TEMP_TIME_STAMP 0x02 +#define TEMP_TYPE 0x04 + +#define FLOAT_MAX_MANTISSA 16777216 /* 2^24 */ + +#define VALID_RANGE_DESC_SIZE 4 +#define TEMPERATURE_TYPE_SIZE 1 +#define MEASUREMENT_INTERVAL_SIZE 2 + +struct thermometer_adapter { + struct btd_adapter *adapter; + GSList *devices; + GSList *fwatchers; /* Final measurements */ + GSList *iwatchers; /* Intermediate measurements */ +}; + +struct thermometer { + struct btd_device *dev; /* Device reference */ + struct thermometer_adapter *tadapter; + GAttrib *attrib; /* GATT connection */ + struct att_range *svc_range; /* Thermometer range */ + guint attioid; /* Att watcher id */ + /* attio id for Temperature Measurement value indications */ + guint attio_measurement_id; + /* attio id for Intermediate Temperature value notifications */ + guint attio_intermediate_id; + /* attio id for Measurement Interval value indications */ + guint attio_interval_id; + gboolean intermediate; + uint8_t type; + uint16_t interval; + uint16_t max; + uint16_t min; + gboolean has_type; + gboolean has_interval; + + uint16_t measurement_ccc_handle; + uint16_t intermediate_ccc_handle; + uint16_t interval_val_handle; +}; + +struct characteristic { + struct thermometer *t; /* Thermometer where the char belongs */ + char uuid[MAX_LEN_UUID_STR + 1]; +}; + +struct watcher { + struct thermometer_adapter *tadapter; + guint id; + char *srv; + char *path; +}; + +struct measurement { + struct thermometer *t; + int16_t exp; + int32_t mant; + uint64_t time; + gboolean suptime; + char *unit; + char *type; + char *value; +}; + +struct tmp_interval_data { + struct thermometer *thermometer; + uint16_t interval; +}; + +static GSList *thermometer_adapters = NULL; + +static const char * const temp_type[] = { + "<reserved>", + "armpit", + "body", + "ear", + "finger", + "intestines", + "mouth", + "rectum", + "toe", + "tympanum" +}; + +static const char *temptype2str(uint8_t value) +{ + if (value > 0 && value < G_N_ELEMENTS(temp_type)) + return temp_type[value]; + + error("Temperature type %d reserved for future use", value); + return NULL; +} + +static void destroy_watcher(gpointer user_data) +{ + struct watcher *watcher = user_data; + + g_free(watcher->path); + g_free(watcher->srv); + g_free(watcher); +} + +static void remove_watcher(gpointer user_data) +{ + struct watcher *watcher = user_data; + + g_dbus_remove_watch(btd_get_dbus_connection(), watcher->id); +} + +static void destroy_thermometer(gpointer user_data) +{ + struct thermometer *t = user_data; + + if (t->attioid > 0) + btd_device_remove_attio_callback(t->dev, t->attioid); + + if (t->attrib != NULL) { + g_attrib_unregister(t->attrib, t->attio_measurement_id); + g_attrib_unregister(t->attrib, t->attio_intermediate_id); + g_attrib_unregister(t->attrib, t->attio_interval_id); + g_attrib_unref(t->attrib); + } + + btd_device_unref(t->dev); + g_free(t->svc_range); + g_free(t); +} + +static void destroy_thermometer_adapter(gpointer user_data) +{ + struct thermometer_adapter *tadapter = user_data; + + if (tadapter->devices != NULL) + g_slist_free_full(tadapter->devices, destroy_thermometer); + + if (tadapter->fwatchers != NULL) + g_slist_free_full(tadapter->fwatchers, remove_watcher); + + g_free(tadapter); +} + +static int cmp_adapter(gconstpointer a, gconstpointer b) +{ + const struct thermometer_adapter *tadapter = a; + const struct btd_adapter *adapter = b; + + if (adapter == tadapter->adapter) + return 0; + + return -1; +} + +static int cmp_device(gconstpointer a, gconstpointer b) +{ + const struct thermometer *t = a; + const struct btd_device *dev = b; + + if (dev == t->dev) + return 0; + + return -1; +} + +static int cmp_watcher(gconstpointer a, gconstpointer b) +{ + const struct watcher *watcher = a; + const struct watcher *match = b; + int ret; + + ret = g_strcmp0(watcher->srv, match->srv); + if (ret != 0) + return ret; + + return g_strcmp0(watcher->path, match->path); +} + +static struct thermometer_adapter * +find_thermometer_adapter(struct btd_adapter *adapter) +{ + GSList *l = g_slist_find_custom(thermometer_adapters, adapter, + cmp_adapter); + if (!l) + return NULL; + + return l->data; +} + +static void change_property(struct thermometer *t, const char *name, + gpointer value) { + if (g_strcmp0(name, "Intermediate") == 0) { + gboolean *intermediate = value; + if (t->intermediate == *intermediate) + return; + + t->intermediate = *intermediate; + } else if (g_strcmp0(name, "Interval") == 0) { + uint16_t *interval = value; + if (t->has_interval && t->interval == *interval) + return; + + t->has_interval = TRUE; + t->interval = *interval; + } else if (g_strcmp0(name, "Maximum") == 0) { + uint16_t *max = value; + if (t->max == *max) + return; + + t->max = *max; + } else if (g_strcmp0(name, "Minimum") == 0) { + uint16_t *min = value; + if (t->min == *min) + return; + + t->min = *min; + } else { + DBG("%s is not a thermometer property", name); + return; + } + + g_dbus_emit_property_changed(btd_get_dbus_connection(), + device_get_path(t->dev), + THERMOMETER_INTERFACE, name); +} + +static void update_watcher(gpointer data, gpointer user_data) +{ + struct watcher *w = data; + struct measurement *m = user_data; + const char *path = device_get_path(m->t->dev); + DBusMessageIter iter; + DBusMessageIter dict; + DBusMessage *msg; + + msg = dbus_message_new_method_call(w->srv, w->path, + THERMOMETER_WATCHER_INTERFACE, + "MeasurementReceived"); + if (msg == NULL) + return; + + dbus_message_iter_init_append(msg, &iter); + + dbus_message_iter_append_basic(&iter, DBUS_TYPE_OBJECT_PATH , &path); + + dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, + DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING + DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING + DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); + + dict_append_entry(&dict, "Exponent", DBUS_TYPE_INT16, &m->exp); + dict_append_entry(&dict, "Mantissa", DBUS_TYPE_INT32, &m->mant); + dict_append_entry(&dict, "Unit", DBUS_TYPE_STRING, &m->unit); + + if (m->suptime) + dict_append_entry(&dict, "Time", DBUS_TYPE_UINT64, &m->time); + + dict_append_entry(&dict, "Type", DBUS_TYPE_STRING, &m->type); + dict_append_entry(&dict, "Measurement", DBUS_TYPE_STRING, &m->value); + + dbus_message_iter_close_container(&iter, &dict); + + dbus_message_set_no_reply(msg, TRUE); + g_dbus_send_message(btd_get_dbus_connection(), msg); +} + +static void recv_measurement(struct thermometer *t, struct measurement *m) +{ + GSList *wlist; + + m->t = t; + + if (g_strcmp0(m->value, "intermediate") == 0) + wlist = t->tadapter->iwatchers; + else + wlist = t->tadapter->fwatchers; + + g_slist_foreach(wlist, update_watcher, m); +} + +static void proc_measurement(struct thermometer *t, const uint8_t *pdu, + uint16_t len, gboolean final) +{ + struct measurement m; + const char *type = NULL; + uint8_t flags; + uint32_t raw; + + /* skip opcode and handle */ + pdu += 3; + len -= 3; + + if (len < 1) { + DBG("Mandatory flags are not provided"); + return; + } + + memset(&m, 0, sizeof(m)); + + flags = *pdu; + + if (flags & TEMP_UNITS) + m.unit = "fahrenheit"; + else + m.unit = "celsius"; + + pdu++; + len--; + + if (len < 4) { + DBG("Mandatory temperature measurement value is not provided"); + return; + } + + raw = get_le32(pdu); + m.mant = raw & 0x00FFFFFF; + m.exp = ((int32_t) raw) >> 24; + + if (m.mant & 0x00800000) { + /* convert to C2 negative value */ + m.mant = m.mant - FLOAT_MAX_MANTISSA; + } + + pdu += 4; + len -= 4; + + if (flags & TEMP_TIME_STAMP) { + struct tm ts; + time_t time; + + if (len < 7) { + DBG("Time stamp is not provided"); + return; + } + + ts.tm_year = get_le16(pdu) - 1900; + ts.tm_mon = *(pdu + 2) - 1; + ts.tm_mday = *(pdu + 3); + ts.tm_hour = *(pdu + 4); + ts.tm_min = *(pdu + 5); + ts.tm_sec = *(pdu + 6); + ts.tm_isdst = -1; + + time = mktime(&ts); + m.time = (uint64_t) time; + m.suptime = TRUE; + + pdu += 7; + len -= 7; + } + + if (flags & TEMP_TYPE) { + if (len < 1) { + DBG("Temperature type is not provided"); + return; + } + + type = temptype2str(*pdu); + } else if (t->has_type) { + type = temptype2str(t->type); + } + + m.type = g_strdup(type); + m.value = final ? "final" : "intermediate"; + + recv_measurement(t, &m); + g_free(m.type); +} + + +static void measurement_ind_handler(const uint8_t *pdu, uint16_t len, + gpointer user_data) +{ + struct thermometer *t = user_data; + uint8_t *opdu; + uint16_t olen; + size_t plen; + + if (len < 3) { + DBG("Bad pdu received"); + return; + } + + proc_measurement(t, pdu, len, TRUE); + + opdu = g_attrib_get_buffer(t->attrib, &plen); + olen = enc_confirmation(opdu, plen); + + if (olen > 0) + g_attrib_send(t->attrib, 0, opdu, olen, NULL, NULL, NULL); +} + +static void intermediate_notify_handler(const uint8_t *pdu, uint16_t len, + gpointer user_data) +{ + struct thermometer *t = user_data; + + if (len < 3) { + DBG("Bad pdu received"); + return; + } + + proc_measurement(t, pdu, len, FALSE); +} + +static void interval_ind_handler(const uint8_t *pdu, uint16_t len, + gpointer user_data) +{ + struct thermometer *t = user_data; + uint16_t interval; + uint8_t *opdu; + uint16_t olen; + size_t plen; + + if (len < 5) { + DBG("Bad pdu received"); + return; + } + + interval = get_le16(pdu + 3); + change_property(t, "Interval", &interval); + + opdu = g_attrib_get_buffer(t->attrib, &plen); + olen = enc_confirmation(opdu, plen); + + if (olen > 0) + g_attrib_send(t->attrib, 0, opdu, olen, NULL, NULL, NULL); +} + +static void valid_range_desc_cb(guint8 status, const guint8 *pdu, guint16 len, + gpointer user_data) +{ + struct thermometer *t = user_data; + uint8_t value[VALID_RANGE_DESC_SIZE]; + uint16_t max, min; + ssize_t vlen; + + if (status != 0) { + DBG("Valid Range descriptor read failed: %s", + att_ecode2str(status)); + return; + } + + vlen = dec_read_resp(pdu, len, value, sizeof(value)); + if (vlen < 0) { + DBG("Protocol error\n"); + return; + } + + if (vlen < 4) { + DBG("Invalid range received"); + return; + } + + min = get_le16(&value[0]); + max = get_le16(&value[2]); + + if (min == 0 || min > max) { + DBG("Invalid range"); + return; + } + + change_property(t, "Maximum", &max); + change_property(t, "Minimum", &min); +} + +static void write_ccc_cb(guint8 status, const guint8 *pdu, + guint16 len, gpointer user_data) +{ + char *msg = user_data; + + if (status != 0) + error("%s failed", msg); + + g_free(msg); +} + +static void process_thermometer_desc(struct characteristic *ch, uint16_t uuid, + uint16_t handle) +{ + uint8_t atval[2]; + uint16_t val; + char *msg; + + if (uuid == GATT_CHARAC_VALID_RANGE_UUID) { + if (g_strcmp0(ch->uuid, MEASUREMENT_INTERVAL_UUID) == 0) + gatt_read_char(ch->t->attrib, handle, + valid_range_desc_cb, ch->t); + return; + } + + if (uuid != GATT_CLIENT_CHARAC_CFG_UUID) + return; + + if (g_strcmp0(ch->uuid, TEMPERATURE_MEASUREMENT_UUID) == 0) { + ch->t->measurement_ccc_handle = handle; + + if (g_slist_length(ch->t->tadapter->fwatchers) == 0) { + val = 0x0000; + msg = g_strdup("Disable Temperature Measurement ind"); + } else { + val = GATT_CLIENT_CHARAC_CFG_IND_BIT; + msg = g_strdup("Enable Temperature Measurement ind"); + } + } else if (g_strcmp0(ch->uuid, INTERMEDIATE_TEMPERATURE_UUID) == 0) { + ch->t->intermediate_ccc_handle = handle; + + if (g_slist_length(ch->t->tadapter->iwatchers) == 0) { + val = 0x0000; + msg = g_strdup("Disable Intermediate Temperature noti"); + } else { + val = GATT_CLIENT_CHARAC_CFG_NOTIF_BIT; + msg = g_strdup("Enable Intermediate Temperature noti"); + } + } else if (g_strcmp0(ch->uuid, MEASUREMENT_INTERVAL_UUID) == 0) { + val = GATT_CLIENT_CHARAC_CFG_IND_BIT; + msg = g_strdup("Enable Measurement Interval indication"); + } else { + return; + } + + put_le16(val, atval); + gatt_write_char(ch->t->attrib, handle, atval, sizeof(atval), + write_ccc_cb, msg); +} + +static void discover_desc_cb(guint8 status, GSList *descs, gpointer user_data) +{ + struct characteristic *ch = user_data; + + if (status != 0) { + error("Discover all characteristic descriptors failed [%s]: %s", + ch->uuid, att_ecode2str(status)); + goto done; + } + + for ( ; descs; descs = descs->next) { + struct gatt_desc *desc = descs->data; + + process_thermometer_desc(ch, desc->uuid16, desc->handle); + } + +done: + g_free(ch); +} + +static void discover_desc(struct thermometer *t, struct gatt_char *c, + struct gatt_char *c_next) +{ + struct characteristic *ch; + uint16_t start, end; + + start = c->value_handle + 1; + + if (c_next != NULL) { + if (start == c_next->handle) + return; + end = c_next->handle - 1; + } else if (c->value_handle != t->svc_range->end) { + end = t->svc_range->end; + } else { + return; + } + + ch = g_new0(struct characteristic, 1); + ch->t = t; + memcpy(ch->uuid, c->uuid, sizeof(c->uuid)); + + gatt_discover_desc(t->attrib, start, end, NULL, discover_desc_cb, ch); +} + +static void read_temp_type_cb(guint8 status, const guint8 *pdu, guint16 len, + gpointer user_data) +{ + struct thermometer *t = user_data; + uint8_t value[TEMPERATURE_TYPE_SIZE]; + ssize_t vlen; + + if (status != 0) { + DBG("Temperature Type value read failed: %s", + att_ecode2str(status)); + return; + } + + vlen = dec_read_resp(pdu, len, value, sizeof(value)); + if (vlen < 0) { + DBG("Protocol error."); + return; + } + + if (vlen != 1) { + DBG("Invalid length for Temperature type"); + return; + } + + t->has_type = TRUE; + t->type = value[0]; +} + +static void read_interval_cb(guint8 status, const guint8 *pdu, guint16 len, + gpointer user_data) +{ + struct thermometer *t = user_data; + uint8_t value[MEASUREMENT_INTERVAL_SIZE]; + uint16_t interval; + ssize_t vlen; + + if (status != 0) { + DBG("Measurement Interval value read failed: %s", + att_ecode2str(status)); + return; + } + + vlen = dec_read_resp(pdu, len, value, sizeof(value)); + if (vlen < 0) { + DBG("Protocol error\n"); + return; + } + + if (vlen < 2) { + DBG("Invalid Interval received"); + return; + } + + interval = get_le16(&value[0]); + change_property(t, "Interval", &interval); +} + +static void process_thermometer_char(struct thermometer *t, + struct gatt_char *c, struct gatt_char *c_next) +{ + if (g_strcmp0(c->uuid, INTERMEDIATE_TEMPERATURE_UUID) == 0) { + gboolean intermediate = TRUE; + change_property(t, "Intermediate", &intermediate); + + t->attio_intermediate_id = g_attrib_register(t->attrib, + ATT_OP_HANDLE_NOTIFY, c->value_handle, + intermediate_notify_handler, t, NULL); + + discover_desc(t, c, c_next); + } else if (g_strcmp0(c->uuid, TEMPERATURE_MEASUREMENT_UUID) == 0) { + + t->attio_measurement_id = g_attrib_register(t->attrib, + ATT_OP_HANDLE_IND, c->value_handle, + measurement_ind_handler, t, NULL); + + discover_desc(t, c, c_next); + } else if (g_strcmp0(c->uuid, TEMPERATURE_TYPE_UUID) == 0) { + gatt_read_char(t->attrib, c->value_handle, + read_temp_type_cb, t); + } else if (g_strcmp0(c->uuid, MEASUREMENT_INTERVAL_UUID) == 0) { + bool need_desc = false; + + gatt_read_char(t->attrib, c->value_handle, read_interval_cb, t); + + if (c->properties & GATT_CHR_PROP_WRITE) { + t->interval_val_handle = c->value_handle; + need_desc = true; + } + + if (c->properties & GATT_CHR_PROP_INDICATE) { + t->attio_interval_id = g_attrib_register(t->attrib, + ATT_OP_HANDLE_IND, c->value_handle, + interval_ind_handler, t, NULL); + need_desc = true; + } + + if (need_desc) + discover_desc(t, c, c_next); + } +} + +static void configure_thermometer_cb(uint8_t status, GSList *characteristics, + void *user_data) +{ + struct thermometer *t = user_data; + GSList *l; + + if (status != 0) { + error("Discover thermometer characteristics: %s", + att_ecode2str(status)); + return; + } + + for (l = characteristics; l; l = l->next) { + struct gatt_char *c = l->data; + struct gatt_char *c_next = (l->next ? l->next->data : NULL); + + process_thermometer_char(t, c, c_next); + } +} + +static void write_interval_cb(guint8 status, const guint8 *pdu, guint16 len, + gpointer user_data) +{ + struct tmp_interval_data *data = user_data; + + if (status != 0) { + error("Interval Write Request failed %s", + att_ecode2str(status)); + goto done; + } + + if (!dec_write_resp(pdu, len)) { + error("Interval Write Request: protocol error"); + goto done; + } + + change_property(data->thermometer, "Interval", &data->interval); + +done: + g_free(user_data); +} + +static void enable_final_measurement(gpointer data, gpointer user_data) +{ + struct thermometer *t = data; + uint16_t handle = t->measurement_ccc_handle; + uint8_t value[2]; + char *msg; + + if (t->attrib == NULL || !handle) + return; + + put_le16(GATT_CLIENT_CHARAC_CFG_IND_BIT, value); + msg = g_strdup("Enable Temperature Measurement indications"); + + gatt_write_char(t->attrib, handle, value, sizeof(value), + write_ccc_cb, msg); +} + +static void enable_intermediate_measurement(gpointer data, gpointer user_data) +{ + struct thermometer *t = data; + uint16_t handle = t->intermediate_ccc_handle; + uint8_t value[2]; + char *msg; + + if (t->attrib == NULL || !handle) + return; + + put_le16(GATT_CLIENT_CHARAC_CFG_NOTIF_BIT, value); + msg = g_strdup("Enable Intermediate Temperature notifications"); + + gatt_write_char(t->attrib, handle, value, sizeof(value), + write_ccc_cb, msg); +} + +static void disable_final_measurement(gpointer data, gpointer user_data) +{ + struct thermometer *t = data; + uint16_t handle = t->measurement_ccc_handle; + uint8_t value[2]; + char *msg; + + if (t->attrib == NULL || !handle) + return; + + put_le16(0x0000, value); + msg = g_strdup("Disable Temperature Measurement indications"); + + gatt_write_char(t->attrib, handle, value, sizeof(value), + write_ccc_cb, msg); +} + +static void disable_intermediate_measurement(gpointer data, gpointer user_data) +{ + struct thermometer *t = data; + uint16_t handle = t->intermediate_ccc_handle; + uint8_t value[2]; + char *msg; + + if (t->attrib == NULL || !handle) + return; + + put_le16(0x0000, value); + msg = g_strdup("Disable Intermediate Temperature notifications"); + + gatt_write_char(t->attrib, handle, value, sizeof(value), + write_ccc_cb, msg); +} + +static void remove_int_watcher(struct thermometer_adapter *tadapter, + struct watcher *w) +{ + if (!g_slist_find(tadapter->iwatchers, w)) + return; + + tadapter->iwatchers = g_slist_remove(tadapter->iwatchers, w); + + if (g_slist_length(tadapter->iwatchers) == 0) + g_slist_foreach(tadapter->devices, + disable_intermediate_measurement, 0); +} + +static void watcher_exit(DBusConnection *conn, void *user_data) +{ + struct watcher *watcher = user_data; + struct thermometer_adapter *tadapter = watcher->tadapter; + + DBG("Thermometer watcher %s disconnected", watcher->path); + + remove_int_watcher(tadapter, watcher); + + tadapter->fwatchers = g_slist_remove(tadapter->fwatchers, watcher); + g_dbus_remove_watch(btd_get_dbus_connection(), watcher->id); + + if (g_slist_length(tadapter->fwatchers) == 0) + g_slist_foreach(tadapter->devices, + disable_final_measurement, 0); +} + +static struct watcher *find_watcher(GSList *list, const char *sender, + const char *path) +{ + struct watcher *match; + GSList *l; + + match = g_new0(struct watcher, 1); + match->srv = g_strdup(sender); + match->path = g_strdup(path); + + l = g_slist_find_custom(list, match, cmp_watcher); + destroy_watcher(match); + + if (l != NULL) + return l->data; + + return NULL; +} + +static DBusMessage *register_watcher(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + const char *sender = dbus_message_get_sender(msg); + struct thermometer_adapter *tadapter = data; + struct watcher *watcher; + char *path; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) + return btd_error_invalid_args(msg); + + watcher = find_watcher(tadapter->fwatchers, sender, path); + if (watcher != NULL) + return btd_error_already_exists(msg); + + DBG("Thermometer watcher %s registered", path); + + watcher = g_new0(struct watcher, 1); + watcher->srv = g_strdup(sender); + watcher->path = g_strdup(path); + watcher->tadapter = tadapter; + watcher->id = g_dbus_add_disconnect_watch(conn, sender, watcher_exit, + watcher, destroy_watcher); + + if (g_slist_length(tadapter->fwatchers) == 0) + g_slist_foreach(tadapter->devices, enable_final_measurement, 0); + + tadapter->fwatchers = g_slist_prepend(tadapter->fwatchers, watcher); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *unregister_watcher(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + const char *sender = dbus_message_get_sender(msg); + struct thermometer_adapter *tadapter = data; + struct watcher *watcher; + char *path; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) + return btd_error_invalid_args(msg); + + watcher = find_watcher(tadapter->fwatchers, sender, path); + if (watcher == NULL) + return btd_error_does_not_exist(msg); + + DBG("Thermometer watcher %s unregistered", path); + + remove_int_watcher(tadapter, watcher); + + tadapter->fwatchers = g_slist_remove(tadapter->fwatchers, watcher); + g_dbus_remove_watch(btd_get_dbus_connection(), watcher->id); + + if (g_slist_length(tadapter->fwatchers) == 0) + g_slist_foreach(tadapter->devices, + disable_final_measurement, 0); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *enable_intermediate(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + const char *sender = dbus_message_get_sender(msg); + struct thermometer_adapter *ta = data; + struct watcher *watcher; + char *path; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) + return btd_error_invalid_args(msg); + + watcher = find_watcher(ta->fwatchers, sender, path); + if (watcher == NULL) + return btd_error_does_not_exist(msg); + + if (find_watcher(ta->iwatchers, sender, path)) + return btd_error_already_exists(msg); + + DBG("Intermediate measurement watcher %s registered", path); + + if (g_slist_length(ta->iwatchers) == 0) + g_slist_foreach(ta->devices, + enable_intermediate_measurement, 0); + + ta->iwatchers = g_slist_prepend(ta->iwatchers, watcher); + + return dbus_message_new_method_return(msg); +} + +static DBusMessage *disable_intermediate(DBusConnection *conn, DBusMessage *msg, + void *data) +{ + const char *sender = dbus_message_get_sender(msg); + struct thermometer_adapter *ta = data; + struct watcher *watcher; + char *path; + + if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path, + DBUS_TYPE_INVALID)) + return btd_error_invalid_args(msg); + + watcher = find_watcher(ta->iwatchers, sender, path); + if (watcher == NULL) + return btd_error_does_not_exist(msg); + + DBG("Intermediate measurement %s unregistered", path); + + remove_int_watcher(ta, watcher); + + return dbus_message_new_method_return(msg); +} + +static gboolean property_get_intermediate(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct thermometer *t = data; + dbus_bool_t val; + + val = !!t->intermediate; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_BOOLEAN, &val); + + return TRUE; +} + +static gboolean property_get_interval(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct thermometer *t = data; + + if (!t->has_interval) + return FALSE; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, &t->interval); + + return TRUE; +} + +static void property_set_interval(const GDBusPropertyTable *property, + DBusMessageIter *iter, + GDBusPendingPropertySet id, void *data) +{ + struct thermometer *t = data; + struct tmp_interval_data *interval_data; + uint16_t val; + uint8_t atval[2]; + + if (t->interval_val_handle == 0) { + g_dbus_pending_property_error(id, + ERROR_INTERFACE ".NotSupported", + "Operation is not supported"); + return; + } + + if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_UINT16) { + g_dbus_pending_property_error(id, + ERROR_INTERFACE ".InvalidArguments", + "Invalid arguments in method call"); + return; + } + + dbus_message_iter_get_basic(iter, &val); + + if (val < t->min || val > t->max) { + g_dbus_pending_property_error(id, + ERROR_INTERFACE ".InvalidArguments", + "Invalid arguments in method call"); + return; + } + + put_le16(val, &atval[0]); + + interval_data = g_new0(struct tmp_interval_data, 1); + interval_data->thermometer = t; + interval_data->interval = val; + gatt_write_char(t->attrib, t->interval_val_handle, atval, sizeof(atval), + write_interval_cb, interval_data); + + g_dbus_pending_property_success(id); +} + +static gboolean property_exists_interval(const GDBusPropertyTable *property, + void *data) +{ + struct thermometer *t = data; + + return t->has_interval; +} + +static gboolean property_get_maximum(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct thermometer *t = data; + + if (!t->has_interval) + return FALSE; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, &t->max); + + return TRUE; +} + +static gboolean property_get_minimum(const GDBusPropertyTable *property, + DBusMessageIter *iter, void *data) +{ + struct thermometer *t = data; + + if (!t->has_interval) + return FALSE; + + dbus_message_iter_append_basic(iter, DBUS_TYPE_UINT16, &t->min); + + return TRUE; +} + +static const GDBusPropertyTable thermometer_properties[] = { + { "Intermediate", "b", property_get_intermediate }, + { "Interval", "q", property_get_interval, property_set_interval, + property_exists_interval }, + { "Maximum", "q", property_get_maximum, NULL, + property_exists_interval }, + { "Minimum", "q", property_get_minimum, NULL, + property_exists_interval }, + { } +}; + +static void attio_connected_cb(GAttrib *attrib, gpointer user_data) +{ + struct thermometer *t = user_data; + + t->attrib = g_attrib_ref(attrib); + + gatt_discover_char(t->attrib, t->svc_range->start, t->svc_range->end, + NULL, configure_thermometer_cb, t); +} + +static void attio_disconnected_cb(gpointer user_data) +{ + struct thermometer *t = user_data; + + DBG("GATT Disconnected"); + + if (t->attio_measurement_id > 0) { + g_attrib_unregister(t->attrib, t->attio_measurement_id); + t->attio_measurement_id = 0; + } + + if (t->attio_intermediate_id > 0) { + g_attrib_unregister(t->attrib, t->attio_intermediate_id); + t->attio_intermediate_id = 0; + } + + if (t->attio_interval_id > 0) { + g_attrib_unregister(t->attrib, t->attio_interval_id); + t->attio_interval_id = 0; + } + + g_attrib_unref(t->attrib); + t->attrib = NULL; +} + +static int thermometer_register(struct btd_device *device, + struct gatt_primary *tattr) +{ + const char *path = device_get_path(device); + struct thermometer *t; + struct btd_adapter *adapter; + struct thermometer_adapter *tadapter; + + adapter = device_get_adapter(device); + + tadapter = find_thermometer_adapter(adapter); + + if (tadapter == NULL) + return -1; + + t = g_new0(struct thermometer, 1); + t->dev = btd_device_ref(device); + t->tadapter = tadapter; + t->svc_range = g_new0(struct att_range, 1); + t->svc_range->start = tattr->range.start; + t->svc_range->end = tattr->range.end; + + tadapter->devices = g_slist_prepend(tadapter->devices, t); + + if (!g_dbus_register_interface(btd_get_dbus_connection(), + path, THERMOMETER_INTERFACE, + NULL, NULL, thermometer_properties, + t, destroy_thermometer)) { + error("D-Bus failed to register %s interface", + THERMOMETER_INTERFACE); + destroy_thermometer(t); + return -EIO; + } + + t->attioid = btd_device_add_attio_callback(device, attio_connected_cb, + attio_disconnected_cb, t); + return 0; +} + +static void thermometer_unregister(struct btd_device *device) +{ + struct thermometer *t; + struct btd_adapter *adapter; + struct thermometer_adapter *tadapter; + GSList *l; + + adapter = device_get_adapter(device); + + tadapter = find_thermometer_adapter(adapter); + + if (tadapter == NULL) + return; + + l = g_slist_find_custom(tadapter->devices, device, cmp_device); + if (l == NULL) + return; + + t = l->data; + + tadapter->devices = g_slist_remove(tadapter->devices, t); + + g_dbus_unregister_interface(btd_get_dbus_connection(), + device_get_path(t->dev), THERMOMETER_INTERFACE); +} + +static const GDBusMethodTable thermometer_manager_methods[] = { + { GDBUS_METHOD("RegisterWatcher", + GDBUS_ARGS({ "agent", "o" }), NULL, + register_watcher) }, + { GDBUS_METHOD("UnregisterWatcher", + GDBUS_ARGS({ "agent", "o" }), NULL, + unregister_watcher) }, + { GDBUS_METHOD("EnableIntermediateMeasurement", + GDBUS_ARGS({ "agent", "o" }), NULL, + enable_intermediate) }, + { GDBUS_METHOD("DisableIntermediateMeasurement", + GDBUS_ARGS({ "agent", "o" }), NULL, + disable_intermediate) }, + { } +}; + +static int thermometer_adapter_register(struct btd_adapter *adapter) +{ + struct thermometer_adapter *tadapter; + + tadapter = g_new0(struct thermometer_adapter, 1); + tadapter->adapter = adapter; + + if (!g_dbus_register_interface(btd_get_dbus_connection(), + adapter_get_path(adapter), + THERMOMETER_MANAGER_INTERFACE, + thermometer_manager_methods, + NULL, NULL, tadapter, + destroy_thermometer_adapter)) { + error("D-Bus failed to register %s interface", + THERMOMETER_MANAGER_INTERFACE); + destroy_thermometer_adapter(tadapter); + return -EIO; + } + + thermometer_adapters = g_slist_prepend(thermometer_adapters, tadapter); + + return 0; +} + +static void thermometer_adapter_unregister(struct btd_adapter *adapter) +{ + struct thermometer_adapter *tadapter; + + tadapter = find_thermometer_adapter(adapter); + if (tadapter == NULL) + return; + + thermometer_adapters = g_slist_remove(thermometer_adapters, tadapter); + + g_dbus_unregister_interface(btd_get_dbus_connection(), + adapter_get_path(tadapter->adapter), + THERMOMETER_MANAGER_INTERFACE); +} + +static int thermometer_device_probe(struct btd_service *service) +{ + struct btd_device *device = btd_service_get_device(service); + struct gatt_primary *tattr; + + tattr = btd_device_get_primary(device, HEALTH_THERMOMETER_UUID); + if (tattr == NULL) + return -EINVAL; + + return thermometer_register(device, tattr); +} + +static void thermometer_device_remove(struct btd_service *service) +{ + struct btd_device *device = btd_service_get_device(service); + + thermometer_unregister(device); +} + +static int thermometer_adapter_probe(struct btd_profile *p, + struct btd_adapter *adapter) +{ + return thermometer_adapter_register(adapter); +} + +static void thermometer_adapter_remove(struct btd_profile *p, + struct btd_adapter *adapter) +{ + thermometer_adapter_unregister(adapter); +} + +static struct btd_profile thermometer_profile = { + .name = "Health Thermometer GATT driver", + .remote_uuid = HEALTH_THERMOMETER_UUID, + .device_probe = thermometer_device_probe, + .device_remove = thermometer_device_remove, + .adapter_probe = thermometer_adapter_probe, + .adapter_remove = thermometer_adapter_remove +}; + +static int thermometer_init(void) +{ + return btd_profile_register(&thermometer_profile); +} + +static void thermometer_exit(void) +{ + btd_profile_unregister(&thermometer_profile); +} + +BLUETOOTH_PLUGIN_DEFINE(thermometer, VERSION, BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, + thermometer_init, thermometer_exit) diff --git a/profiles/time/server.c b/profiles/time/server.c new file mode 100755 index 00000000..2289c6a4 --- /dev/null +++ b/profiles/time/server.c @@ -0,0 +1,283 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2011 Nokia Corporation + * Copyright (C) 2011 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 <time.h> +#include <errno.h> +#include <string.h> +#include <stdbool.h> + +#include <glib.h> + +#include "lib/bluetooth.h" +#include "lib/sdp.h" +#include "lib/uuid.h" + +#include "src/adapter.h" +#include "src/device.h" +#include "src/profile.h" +#include "src/plugin.h" +#include "attrib/gattrib.h" +#include "attrib/att.h" +#include "attrib/gatt.h" +#include "attrib/att-database.h" +#include "src/shared/util.h" +#include "src/attrib-server.h" +#include "attrib/gatt-service.h" +#include "src/log.h" + +#define CURRENT_TIME_SVC_UUID 0x1805 +#define REF_TIME_UPDATE_SVC_UUID 0x1806 + +#define LOCAL_TIME_INFO_CHR_UUID 0x2A0F +#define TIME_UPDATE_CTRL_CHR_UUID 0x2A16 +#define TIME_UPDATE_STAT_CHR_UUID 0x2A17 +#define CT_TIME_CHR_UUID 0x2A2B + +enum { + UPDATE_RESULT_SUCCESSFUL = 0, + UPDATE_RESULT_CANCELED = 1, + UPDATE_RESULT_NO_CONN = 2, + UPDATE_RESULT_ERROR = 3, + UPDATE_RESULT_TIMEOUT = 4, + UPDATE_RESULT_NOT_ATTEMPTED = 5, +}; + +enum { + UPDATE_STATE_IDLE = 0, + UPDATE_STATE_PENDING = 1, +}; + +enum { + GET_REFERENCE_UPDATE = 1, + CANCEL_REFERENCE_UPDATE = 2, +}; + +static int encode_current_time(uint8_t value[10]) +{ + struct timespec tp; + struct tm tm; + + if (clock_gettime(CLOCK_REALTIME, &tp) == -1) { + int err = -errno; + + error("clock_gettime: %s", strerror(-err)); + return err; + } + + if (localtime_r(&tp.tv_sec, &tm) == NULL) { + error("localtime_r() failed"); + /* localtime_r() does not set errno */ + return -EINVAL; + } + + put_le16(1900 + tm.tm_year, &value[0]); /* Year */ + value[2] = tm.tm_mon + 1; /* Month */ + value[3] = tm.tm_mday; /* Day */ + value[4] = tm.tm_hour; /* Hours */ + value[5] = tm.tm_min; /* Minutes */ + value[6] = tm.tm_sec; /* Seconds */ + value[7] = tm.tm_wday == 0 ? 7 : tm.tm_wday; /* Day of Week */ + /* From Time Profile spec: "The number of 1/256 fractions of a second." + * In 1s there are 256 fractions, in 1ns there are 256/10^9 fractions. + * To avoid integer overflow, we use the equivalent 1/3906250 ratio. */ + value[8] = tp.tv_nsec / 3906250; /* Fractions256 */ + value[9] = 0x00; /* Adjust Reason */ + + return 0; +} + +static uint8_t current_time_read(struct attribute *a, + struct btd_device *device, gpointer user_data) +{ + struct btd_adapter *adapter = user_data; + uint8_t value[10]; + + if (encode_current_time(value) < 0) + return ATT_ECODE_IO; + + attrib_db_update(adapter, a->handle, NULL, value, sizeof(value), NULL); + + return 0; +} + +static uint8_t local_time_info_read(struct attribute *a, + struct btd_device *device, gpointer user_data) +{ + struct btd_adapter *adapter = user_data; + uint8_t value[2]; + + DBG("a=%p", a); + + tzset(); + + /* Convert POSIX "timezone" (seconds West of GMT) to Time Profile + * format (offset from UTC in number of 15 minutes increments). */ + value[0] = (uint8_t) (-1 * timezone / (60 * 15)); + + /* FIXME: POSIX "daylight" variable only indicates whether there + * is DST for the local time or not. The offset is unknown. */ + value[1] = daylight ? 0xff : 0x00; + + attrib_db_update(adapter, a->handle, NULL, value, sizeof(value), NULL); + + return 0; +} + +static gboolean register_current_time_service(struct btd_adapter *adapter) +{ + bt_uuid_t uuid; + + bt_uuid16_create(&uuid, CURRENT_TIME_SVC_UUID); + + /* Current Time service */ + return gatt_service_add(adapter, GATT_PRIM_SVC_UUID, &uuid, + /* CT Time characteristic */ + GATT_OPT_CHR_UUID16, CT_TIME_CHR_UUID, + GATT_OPT_CHR_PROPS, GATT_CHR_PROP_READ | + GATT_CHR_PROP_NOTIFY, + GATT_OPT_CHR_VALUE_CB, ATTRIB_READ, + current_time_read, adapter, + + /* Local Time Information characteristic */ + GATT_OPT_CHR_UUID16, LOCAL_TIME_INFO_CHR_UUID, + GATT_OPT_CHR_PROPS, GATT_CHR_PROP_READ, + GATT_OPT_CHR_VALUE_CB, ATTRIB_READ, + local_time_info_read, adapter, + + GATT_OPT_INVALID); +} + +static uint8_t time_update_control(struct attribute *a, + struct btd_device *device, + gpointer user_data) +{ + DBG("handle 0x%04x", a->handle); + + if (a->len != 1) + DBG("Invalid control point value size: %zu", a->len); + + switch (a->data[0]) { + case GET_REFERENCE_UPDATE: + DBG("Get Reference Update"); + break; + case CANCEL_REFERENCE_UPDATE: + DBG("Cancel Reference Update"); + break; + default: + DBG("Unknown command: 0x%02x", a->data[0]); + } + + return 0; +} + +static uint8_t time_update_status(struct attribute *a, + struct btd_device *device, + gpointer user_data) +{ + struct btd_adapter *adapter = user_data; + uint8_t value[2]; + + DBG("handle 0x%04x", a->handle); + + value[0] = UPDATE_STATE_IDLE; + value[1] = UPDATE_RESULT_SUCCESSFUL; + attrib_db_update(adapter, a->handle, NULL, value, sizeof(value), NULL); + + return 0; +} + +static gboolean register_ref_time_update_service(struct btd_adapter *adapter) +{ + bt_uuid_t uuid; + + bt_uuid16_create(&uuid, REF_TIME_UPDATE_SVC_UUID); + + /* Reference Time Update service */ + return gatt_service_add(adapter, GATT_PRIM_SVC_UUID, &uuid, + /* Time Update control point */ + GATT_OPT_CHR_UUID16, TIME_UPDATE_CTRL_CHR_UUID, + GATT_OPT_CHR_PROPS, + GATT_CHR_PROP_WRITE_WITHOUT_RESP, + GATT_OPT_CHR_VALUE_CB, ATTRIB_WRITE, + time_update_control, adapter, + + /* Time Update status */ + GATT_OPT_CHR_UUID16, TIME_UPDATE_STAT_CHR_UUID, + GATT_OPT_CHR_PROPS, GATT_CHR_PROP_READ, + GATT_OPT_CHR_VALUE_CB, ATTRIB_READ, + time_update_status, adapter, + + GATT_OPT_INVALID); +} + +static int time_server_init(struct btd_profile *p, struct btd_adapter *adapter) +{ + const char *path = adapter_get_path(adapter); + + DBG("path %s", path); + + if (!register_current_time_service(adapter)) { + error("Current Time Service could not be registered"); + return -EIO; + } + + if (!register_ref_time_update_service(adapter)) { + error("Reference Time Update Service could not be registered"); + return -EIO; + } + + return 0; +} + +static void time_server_exit(struct btd_profile *p, + struct btd_adapter *adapter) +{ + const char *path = adapter_get_path(adapter); + + DBG("path %s", path); +} + +struct btd_profile time_profile = { + .name = "gatt-time-server", + .adapter_probe = time_server_init, + .adapter_remove = time_server_exit, +}; + +static int time_init(void) +{ + return btd_profile_register(&time_profile); +} + +static void time_exit(void) +{ + btd_profile_unregister(&time_profile); +} + +BLUETOOTH_PLUGIN_DEFINE(time, VERSION, + BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, + time_init, time_exit) |