/* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2011 Nokia Corporation * Copyright (C) 2011 Marcel Holtmann * * * 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 #endif #include #include #include #include #include #include "lib/uuid.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; 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)