1034 lines
25 KiB
C
1034 lines
25 KiB
C
/*
|
|
*
|
|
* 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 <gdbus/gdbus.h>
|
|
#include <glib.h>
|
|
#include <stdlib.h>
|
|
|
|
#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)
|