summaryrefslogtreecommitdiff
path: root/profiles
diff options
context:
space:
mode:
Diffstat (limited to 'profiles')
-rwxr-xr-xprofiles/alert/server.c1044
-rwxr-xr-xprofiles/audio/a2dp.c135
-rwxr-xr-xprofiles/audio/avctp.c77
-rwxr-xr-xprofiles/audio/avctp.h3
-rwxr-xr-xprofiles/audio/avdtp.c450
-rwxr-xr-xprofiles/audio/avdtp.h4
-rwxr-xr-xprofiles/audio/avrcp.c384
-rwxr-xr-xprofiles/audio/media.c419
-rwxr-xr-xprofiles/audio/player.c106
-rwxr-xr-xprofiles/audio/player.h7
-rwxr-xr-xprofiles/audio/sink.c30
-rwxr-xr-xprofiles/cyclingspeed/cyclingspeed.c1266
-rwxr-xr-xprofiles/gap/gas.c38
-rwxr-xr-xprofiles/heartrate/heartrate.c870
-rwxr-xr-xprofiles/input/device.c227
-rwxr-xr-xprofiles/input/device.h9
-rwxr-xr-xprofiles/input/manager.c38
-rwxr-xr-xprofiles/input/server.c84
-rwxr-xr-xprofiles/network/bnep.c78
-rwxr-xr-xprofiles/network/bnep.h4
-rwxr-xr-xprofiles/network/connection.c18
-rwxr-xr-xprofiles/network/server.c235
-rwxr-xr-xprofiles/proximity/immalert.c458
-rwxr-xr-xprofiles/proximity/immalert.h26
-rwxr-xr-xprofiles/proximity/linkloss.c547
-rwxr-xr-xprofiles/proximity/linkloss.h26
-rwxr-xr-xprofiles/proximity/main.c81
-rwxr-xr-xprofiles/proximity/manager.c196
-rwxr-xr-xprofiles/proximity/manager.h26
-rwxr-xr-xprofiles/proximity/monitor.c822
-rwxr-xr-xprofiles/proximity/monitor.h43
-rwxr-xr-xprofiles/proximity/reporter.c330
-rwxr-xr-xprofiles/proximity/reporter.h46
-rwxr-xr-xprofiles/tds/manager.c68
-rwxr-xr-xprofiles/tds/tds.c785
-rwxr-xr-xprofiles/tds/tds.h34
-rwxr-xr-xprofiles/thermometer/thermometer.c1321
-rwxr-xr-xprofiles/time/server.c283
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, &param, 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)