bluez/audio/avrcp.c
Lucas De Marchi a2a2db8167 Refactor to share code
Refactor code when we are 'listing selected attributes' to share code
with 'listing all attributes'. This way we always keep the list of
attributes in a GList, and call player_get_media_attribute() in only one
place.
2011-10-13 13:10:45 +03:00

1175 lines
27 KiB
C

/*
*
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2006-2010 Nokia Corporation
* Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
* Copyright (C) 2011 Texas Instruments, Inc.
*
*
* 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 <stdlib.h>
#include <stdint.h>
#include <errno.h>
#include <unistd.h>
#include <assert.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/sdp.h>
#include <bluetooth/sdp_lib.h>
#include <glib.h>
#include <dbus/dbus.h>
#include <gdbus.h>
#include "log.h"
#include "error.h"
#include "device.h"
#include "manager.h"
#include "avctp.h"
#include "avrcp.h"
#include "sdpd.h"
#include "glib-helper.h"
#include "dbus-common.h"
/* Company IDs for vendor dependent commands */
#define IEEEID_BTSIG 0x001958
/* Error codes for metadata transfer */
#define E_INVALID_COMMAND 0x00
#define E_INVALID_PARAM 0x01
#define E_PARAM_NOT_FOUND 0x02
#define E_INTERNAL 0x03
/* PDU types for metadata transfer */
#define AVRCP_GET_CAPABILITIES 0x10
#define AVRCP_LIST_PLAYER_ATTRIBUTES 0X11
#define AVRCP_LIST_PLAYER_VALUES 0x12
#define AVRCP_GET_CURRENT_PLAYER_VALUE 0x13
#define AVRCP_SET_PLAYER_VALUE 0x14
#define AVRCP_GET_PLAYER_ATTRIBUTE_TEXT 0x15
#define AVRCP_GET_PLAYER_VALUE_TEXT 0x16
#define AVRCP_DISPLAYABLE_CHARSET 0x17
#define AVRCP_CT_BATTERY_STATUS 0x18
#define AVRCP_GET_ELEMENT_ATTRIBUTES 0x20
#define AVRCP_GET_PLAY_STATUS 0x30
#define AVRCP_REGISTER_NOTIFICATION 0x31
/* Capabilities for AVRCP_GET_CAPABILITIES pdu */
#define CAP_COMPANY_ID 0x02
#define CAP_EVENTS_SUPPORTED 0x03
enum battery_status {
BATTERY_STATUS_NORMAL = 0,
BATTERY_STATUS_WARNING = 1,
BATTERY_STATUS_CRITICAL = 2,
BATTERY_STATUS_EXTERNAL = 3,
BATTERY_STATUS_FULL_CHARGE = 4,
};
#if __BYTE_ORDER == __LITTLE_ENDIAN
struct avrcp_header {
uint8_t company_id[3];
uint8_t pdu_id;
uint8_t packet_type:2;
uint8_t rsvd:6;
uint16_t params_len;
uint8_t params[0];
} __attribute__ ((packed));
#define AVRCP_HEADER_LENGTH 7
#elif __BYTE_ORDER == __BIG_ENDIAN
struct avrcp_header {
uint8_t company_id[3];
uint8_t pdu_id;
uint8_t rsvd:6;
uint8_t packet_type:2;
uint16_t params_len;
uint8_t params[0];
} __attribute__ ((packed));
#define AVRCP_HEADER_LENGTH 7
#else
#error "Unknown byte order"
#endif
#define AVRCP_MTU (AVC_MTU - AVC_HEADER_LENGTH)
#define AVRCP_PDU_MTU (AVRCP_MTU - AVRCP_HEADER_LENGTH)
struct avrcp_server {
bdaddr_t src;
uint32_t tg_record_id;
uint32_t ct_record_id;
GSList *players;
struct avrcp_player *active_player;
};
struct avrcp_player {
struct avrcp_server *server;
struct avctp *session;
struct audio_device *dev;
unsigned int handler;
uint16_t registered_events;
uint8_t transaction_events[AVRCP_EVENT_TRACK_CHANGED + 1];
struct avrcp_player_cb *cb;
void *user_data;
GDestroyNotify destroy;
};
static GSList *servers = NULL;
static unsigned int avctp_id = 0;
/* Company IDs supported by this device */
static uint32_t company_ids[] = {
IEEEID_BTSIG,
};
static sdp_record_t *avrcp_ct_record(void)
{
sdp_list_t *svclass_id, *pfseq, *apseq, *root;
uuid_t root_uuid, l2cap, avctp, avrct;
sdp_profile_desc_t profile[1];
sdp_list_t *aproto, *proto[2];
sdp_record_t *record;
sdp_data_t *psm, *version, *features;
uint16_t lp = AVCTP_PSM;
uint16_t avrcp_ver = 0x0100, avctp_ver = 0x0103, feat = 0x000f;
record = sdp_record_alloc();
if (!record)
return NULL;
sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
root = sdp_list_append(0, &root_uuid);
sdp_set_browse_groups(record, root);
/* Service Class ID List */
sdp_uuid16_create(&avrct, AV_REMOTE_SVCLASS_ID);
svclass_id = sdp_list_append(0, &avrct);
sdp_set_service_classes(record, svclass_id);
/* Protocol Descriptor List */
sdp_uuid16_create(&l2cap, L2CAP_UUID);
proto[0] = sdp_list_append(0, &l2cap);
psm = sdp_data_alloc(SDP_UINT16, &lp);
proto[0] = sdp_list_append(proto[0], psm);
apseq = sdp_list_append(0, proto[0]);
sdp_uuid16_create(&avctp, AVCTP_UUID);
proto[1] = sdp_list_append(0, &avctp);
version = sdp_data_alloc(SDP_UINT16, &avctp_ver);
proto[1] = sdp_list_append(proto[1], version);
apseq = sdp_list_append(apseq, proto[1]);
aproto = sdp_list_append(0, apseq);
sdp_set_access_protos(record, aproto);
/* Bluetooth Profile Descriptor List */
sdp_uuid16_create(&profile[0].uuid, AV_REMOTE_PROFILE_ID);
profile[0].version = avrcp_ver;
pfseq = sdp_list_append(0, &profile[0]);
sdp_set_profile_descs(record, pfseq);
features = sdp_data_alloc(SDP_UINT16, &feat);
sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features);
sdp_set_info_attr(record, "AVRCP CT", 0, 0);
free(psm);
free(version);
sdp_list_free(proto[0], 0);
sdp_list_free(proto[1], 0);
sdp_list_free(apseq, 0);
sdp_list_free(pfseq, 0);
sdp_list_free(aproto, 0);
sdp_list_free(root, 0);
sdp_list_free(svclass_id, 0);
return record;
}
static sdp_record_t *avrcp_tg_record(void)
{
sdp_list_t *svclass_id, *pfseq, *apseq, *root;
uuid_t root_uuid, l2cap, avctp, avrtg;
sdp_profile_desc_t profile[1];
sdp_list_t *aproto, *proto[2];
sdp_record_t *record;
sdp_data_t *psm, *version, *features;
uint16_t lp = AVCTP_PSM;
uint16_t avrcp_ver = 0x0103, avctp_ver = 0x0103, feat = 0x000f;
record = sdp_record_alloc();
if (!record)
return NULL;
sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
root = sdp_list_append(0, &root_uuid);
sdp_set_browse_groups(record, root);
/* Service Class ID List */
sdp_uuid16_create(&avrtg, AV_REMOTE_TARGET_SVCLASS_ID);
svclass_id = sdp_list_append(0, &avrtg);
sdp_set_service_classes(record, svclass_id);
/* Protocol Descriptor List */
sdp_uuid16_create(&l2cap, L2CAP_UUID);
proto[0] = sdp_list_append(0, &l2cap);
psm = sdp_data_alloc(SDP_UINT16, &lp);
proto[0] = sdp_list_append(proto[0], psm);
apseq = sdp_list_append(0, proto[0]);
sdp_uuid16_create(&avctp, AVCTP_UUID);
proto[1] = sdp_list_append(0, &avctp);
version = sdp_data_alloc(SDP_UINT16, &avctp_ver);
proto[1] = sdp_list_append(proto[1], version);
apseq = sdp_list_append(apseq, proto[1]);
aproto = sdp_list_append(0, apseq);
sdp_set_access_protos(record, aproto);
/* Bluetooth Profile Descriptor List */
sdp_uuid16_create(&profile[0].uuid, AV_REMOTE_PROFILE_ID);
profile[0].version = avrcp_ver;
pfseq = sdp_list_append(0, &profile[0]);
sdp_set_profile_descs(record, pfseq);
features = sdp_data_alloc(SDP_UINT16, &feat);
sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features);
sdp_set_info_attr(record, "AVRCP TG", 0, 0);
free(psm);
free(version);
sdp_list_free(proto[0], 0);
sdp_list_free(proto[1], 0);
sdp_list_free(apseq, 0);
sdp_list_free(aproto, 0);
sdp_list_free(pfseq, 0);
sdp_list_free(root, 0);
sdp_list_free(svclass_id, 0);
return record;
}
static unsigned int attr_get_max_val(uint8_t attr)
{
switch (attr) {
case AVRCP_ATTRIBUTE_EQUALIZER:
return AVRCP_EQUALIZER_ON;
case AVRCP_ATTRIBUTE_REPEAT_MODE:
return AVRCP_REPEAT_MODE_GROUP;
case AVRCP_ATTRIBUTE_SHUFFLE:
return AVRCP_SHUFFLE_GROUP;
case AVRCP_ATTRIBUTE_SCAN:
return AVRCP_SCAN_GROUP;
}
return 0;
}
static const char *battery_status_to_str(enum battery_status status)
{
switch (status) {
case BATTERY_STATUS_NORMAL:
return "normal";
case BATTERY_STATUS_WARNING:
return "warning";
case BATTERY_STATUS_CRITICAL:
return "critical";
case BATTERY_STATUS_EXTERNAL:
return "external";
case BATTERY_STATUS_FULL_CHARGE:
return "fullcharge";
}
return NULL;
}
/*
* get_company_id:
*
* Get three-byte Company_ID from incoming AVRCP message
*/
static uint32_t get_company_id(const uint8_t cid[3])
{
return cid[0] << 16 | cid[1] << 8 | cid[2];
}
/*
* set_company_id:
*
* Set three-byte Company_ID into outgoing AVRCP message
*/
static void set_company_id(uint8_t cid[3], const uint32_t cid_in)
{
cid[0] = cid_in >> 16;
cid[1] = cid_in >> 8;
cid[2] = cid_in;
}
int avrcp_player_event(struct avrcp_player *player, uint8_t id, void *data)
{
uint8_t buf[AVRCP_HEADER_LENGTH + 9];
struct avrcp_header *pdu = (void *) buf;
uint16_t size;
int err;
if (player->session == NULL)
return -ENOTCONN;
if (!(player->registered_events & (1 << id)))
return 0;
memset(buf, 0, sizeof(buf));
set_company_id(pdu->company_id, IEEEID_BTSIG);
pdu->pdu_id = AVRCP_REGISTER_NOTIFICATION;
pdu->params[0] = id;
DBG("id=%u", id);
switch (id) {
case AVRCP_EVENT_STATUS_CHANGED:
size = 2;
pdu->params[1] = *((uint8_t *)data);
break;
case AVRCP_EVENT_TRACK_CHANGED: {
size = 9;
/*
* AVRCP 1.3 supports only one track identifier: PLAYING
* (0x0). When 1.4 version is added, this shall be changed to
* contain the identifier of the track.
*/
memset(&pdu->params[1], 0, 8);
break;
}
default:
error("Unknown event %u", id);
return -EINVAL;
}
pdu->params_len = htons(size);
err = avctp_send_vendordep(player->session, player->transaction_events[id],
AVC_CTYPE_CHANGED, AVC_SUBUNIT_PANEL,
buf, size + AVRCP_HEADER_LENGTH);
if (err < 0)
return err;
/* Unregister event as per AVRCP 1.3 spec, section 5.4.2 */
player->registered_events ^= 1 << id;
return 0;
}
/*
* Copy media_info field to a buffer, intended to be used in a response to
* GetElementAttributes message.
*
* It assumes there's enough space in the buffer and on success it returns the
* size written.
*
* If @param id is not valid, -EINVAL is returned. If there's no space left on
* the buffer -ENOBUFS is returned.
*/
static int player_get_media_attribute(struct avrcp_player *player,
uint32_t id, uint8_t *buf,
uint16_t maxlen)
{
struct media_info_elem {
uint32_t id;
uint16_t charset;
uint16_t len;
uint8_t val[];
};
struct media_info_elem *elem = (void *)buf;
uint16_t len;
char valstr[20];
void *value;
if (maxlen < sizeof(struct media_info_elem))
return -ENOBUFS;
/* Subtract the size of elem header from the available space */
maxlen -= sizeof(struct media_info_elem);
DBG("Get media attribute: %u", id);
if (id == AVRCP_MEDIA_ATTRIBUTE_ILLEGAL ||
id > AVRCP_MEDIA_ATTRIBUTE_LAST)
return -ENOENT;
value = player->cb->get_metadata(id, player->user_data);
if (value == NULL) {
len = 0;
goto done;
}
switch (id) {
case AVRCP_MEDIA_ATTRIBUTE_TITLE:
case AVRCP_MEDIA_ATTRIBUTE_ARTIST:
case AVRCP_MEDIA_ATTRIBUTE_ALBUM:
case AVRCP_MEDIA_ATTRIBUTE_GENRE:
len = strlen((char *) value);
if (len > maxlen)
return -ENOBUFS;
memcpy(elem->val, value, len);
break;
case AVRCP_MEDIA_ATTRIBUTE_TRACK:
case AVRCP_MEDIA_ATTRIBUTE_N_TRACKS:
case AVRCP_MEDIA_ATTRIBUTE_DURATION:
snprintf(valstr, 20, "%u", GPOINTER_TO_UINT(value));
len = strlen(valstr);
if (len > maxlen)
return -ENOBUFS;
memcpy(elem->val, valstr, len);
break;
}
done:
elem->id = htonl(id);
elem->charset = htons(0x6A); /* Always use UTF-8 */
elem->len = htons(len);
return sizeof(struct media_info_elem) + len;
}
static int player_set_attribute(struct avrcp_player *player,
uint8_t attr, uint8_t val)
{
DBG("Change attribute: %u %u", attr, val);
return player->cb->set_setting(attr, val, player->user_data);
}
static int player_get_attribute(struct avrcp_player *player, uint8_t attr)
{
int value;
DBG("attr %u", attr);
value = player->cb->get_setting(attr, player->user_data);
if (value < 0)
DBG("attr %u not supported by player", attr);
return value;
}
static uint8_t avrcp_handle_get_capabilities(struct avrcp_player *player,
struct avrcp_header *pdu,
uint8_t transaction)
{
uint16_t len = ntohs(pdu->params_len);
unsigned int i;
if (len != 1)
goto err;
DBG("id=%u", pdu->params[0]);
switch (pdu->params[0]) {
case CAP_COMPANY_ID:
for (i = 0; i < G_N_ELEMENTS(company_ids); i++) {
set_company_id(&pdu->params[2 + i * 3],
company_ids[i]);
}
pdu->params_len = htons(2 + (3 * G_N_ELEMENTS(company_ids)));
pdu->params[1] = G_N_ELEMENTS(company_ids);
return AVC_CTYPE_STABLE;
case CAP_EVENTS_SUPPORTED:
pdu->params_len = htons(4);
pdu->params[1] = 2;
pdu->params[2] = AVRCP_EVENT_STATUS_CHANGED;
pdu->params[3] = AVRCP_EVENT_TRACK_CHANGED;
return AVC_CTYPE_STABLE;
}
err:
pdu->params_len = htons(1);
pdu->params[0] = E_INVALID_PARAM;
return AVC_CTYPE_REJECTED;
}
static uint8_t avrcp_handle_list_player_attributes(struct avrcp_player *player,
struct avrcp_header *pdu,
uint8_t transaction)
{
uint16_t len = ntohs(pdu->params_len);
unsigned int i;
if (len != 0) {
pdu->params_len = htons(1);
pdu->params[0] = E_INVALID_PARAM;
return AVC_CTYPE_REJECTED;
}
if (!player)
goto done;
for (i = 1; i <= AVRCP_ATTRIBUTE_SCAN; i++) {
if (player_get_attribute(player, i) < 0)
continue;
len++;
pdu->params[len] = i;
}
done:
pdu->params[0] = len;
pdu->params_len = htons(len + 1);
return AVC_CTYPE_STABLE;
}
static uint8_t avrcp_handle_list_player_values(struct avrcp_player *player,
struct avrcp_header *pdu,
uint8_t transaction)
{
uint16_t len = ntohs(pdu->params_len);
unsigned int i;
if (len != 1 || !player)
goto err;
if (player_get_attribute(player, pdu->params[0]) < 0)
goto err;
len = attr_get_max_val(pdu->params[0]);
for (i = 1; i <= len; i++)
pdu->params[i] = i;
pdu->params[0] = len;
pdu->params_len = htons(len + 1);
return AVC_CTYPE_STABLE;
err:
pdu->params_len = htons(1);
pdu->params[0] = E_INVALID_PARAM;
return AVC_CTYPE_REJECTED;
}
static uint8_t avrcp_handle_get_element_attributes(struct avrcp_player *player,
struct avrcp_header *pdu,
uint8_t transaction)
{
uint16_t len = ntohs(pdu->params_len);
uint64_t *identifier = (uint64_t *) &pdu->params[0];
uint16_t pos;
uint8_t nattr;
int size;
GList *l, *attr_ids;
if (len < 9 || *identifier != 0)
goto err;
nattr = pdu->params[8];
if (len < nattr * sizeof(uint32_t) + 1)
goto err;
if (!nattr) {
/*
* Return all available information, at least
* title must be returned if there's a track selected.
*/
attr_ids = player->cb->list_metadata(player->user_data);
} else {
unsigned int i;
uint32_t *attr = (uint32_t *) &pdu->params[9];
for (i = 0, attr_ids = NULL; i < nattr; i++, attr++) {
uint32_t id = ntohl(bt_get_unaligned(attr));
attr_ids = g_list_prepend(attr_ids,
GUINT_TO_POINTER(id));
}
attr_ids = g_list_reverse(attr_ids);
}
for (l = attr_ids, len = 0, pos = 1; l != NULL; l = l->next) {
uint32_t attr = GPOINTER_TO_UINT(l->data);
size = player_get_media_attribute(player, attr,
&pdu->params[pos],
AVRCP_PDU_MTU - pos);
if (size >= 0) {
len++;
pos += size;
}
}
g_list_free(attr_ids);
if (!len)
goto err;
pdu->params[0] = len;
pdu->params_len = htons(pos);
return AVC_CTYPE_STABLE;
err:
pdu->params_len = htons(1);
pdu->params[0] = E_INVALID_PARAM;
return AVC_CTYPE_REJECTED;
}
static uint8_t avrcp_handle_get_current_player_value(struct avrcp_player *player,
struct avrcp_header *pdu,
uint8_t transaction)
{
uint16_t len = ntohs(pdu->params_len);
uint8_t *settings;
unsigned int i;
if (player == NULL || len <= 1 || pdu->params[0] != len - 1)
goto err;
/*
* Save a copy of requested settings because we can override them
* while responding
*/
settings = g_memdup(&pdu->params[1], pdu->params[0]);
len = 0;
/*
* From sec. 5.7 of AVRCP 1.3 spec, we should igore non-existent IDs
* and send a response with the existent ones. Only if all IDs are
* non-existent we should send an error.
*/
for (i = 0; i < pdu->params[0]; i++) {
int val;
if (settings[i] < AVRCP_ATTRIBUTE_EQUALIZER ||
settings[i] > AVRCP_ATTRIBUTE_SCAN) {
DBG("Ignoring %u", settings[i]);
continue;
}
val = player_get_attribute(player, settings[i]);
if (val < 0)
continue;
pdu->params[++len] = settings[i];
pdu->params[++len] = val;
}
g_free(settings);
if (len) {
pdu->params[0] = len / 2;
pdu->params_len = htons(len + 1);
return AVC_CTYPE_STABLE;
}
error("No valid attributes in request");
err:
pdu->params_len = htons(1);
pdu->params[0] = E_INVALID_PARAM;
return AVC_CTYPE_REJECTED;
}
static uint8_t avrcp_handle_set_player_value(struct avrcp_player *player,
struct avrcp_header *pdu,
uint8_t transaction)
{
uint16_t len = ntohs(pdu->params_len);
unsigned int i;
uint8_t *param;
if (len < 3 || len > 2 * pdu->params[0] + 1U)
goto err;
/*
* From sec. 5.7 of AVRCP 1.3 spec, we should igore non-existent IDs
* and set the existent ones. Sec. 5.2.4 is not clear however how to
* indicate that a certain ID was not accepted. If at least one
* attribute is valid, we respond with no parameters. Otherwise an
* E_INVALID_PARAM is sent.
*/
for (len = 0, i = 0, param = &pdu->params[1]; i < pdu->params[0];
i++, param += 2) {
if (player_set_attribute(player, param[0], param[1]) < 0)
continue;
len++;
}
if (len) {
pdu->params_len = 0;
return AVC_CTYPE_STABLE;
}
err:
pdu->params_len = htons(1);
pdu->params[0] = E_INVALID_PARAM;
return AVC_CTYPE_REJECTED;
}
static uint8_t avrcp_handle_displayable_charset(struct avrcp_player *player,
struct avrcp_header *pdu,
uint8_t transaction)
{
uint16_t len = ntohs(pdu->params_len);
if (len < 3) {
pdu->params_len = htons(1);
pdu->params[0] = E_INVALID_PARAM;
return AVC_CTYPE_REJECTED;
}
/*
* We acknowledge the commands, but we always use UTF-8 for
* encoding since CT is obliged to support it.
*/
pdu->params_len = 0;
return AVC_CTYPE_STABLE;
}
static uint8_t avrcp_handle_ct_battery_status(struct avrcp_player *player,
struct avrcp_header *pdu,
uint8_t transaction)
{
uint16_t len = ntohs(pdu->params_len);
const char *valstr;
if (len != 1)
goto err;
valstr = battery_status_to_str(pdu->params[0]);
if (valstr == NULL)
goto err;
pdu->params_len = 0;
return AVC_CTYPE_STABLE;
err:
pdu->params_len = htons(1);
pdu->params[0] = E_INVALID_PARAM;
return AVC_CTYPE_REJECTED;
}
static uint8_t avrcp_handle_get_play_status(struct avrcp_player *player,
struct avrcp_header *pdu,
uint8_t transaction)
{
uint16_t len = ntohs(pdu->params_len);
uint32_t position;
uint32_t duration;
if (len != 0) {
pdu->params_len = htons(1);
pdu->params[0] = E_INVALID_PARAM;
return AVC_CTYPE_REJECTED;
}
position = player->cb->get_position(player->user_data);
duration = GPOINTER_TO_UINT(player->cb->get_metadata(
AVRCP_MEDIA_ATTRIBUTE_DURATION,
player->user_data));
duration = htonl(duration);
position = htonl(position);
memcpy(&pdu->params[0], &duration, 4);
memcpy(&pdu->params[4], &position, 4);
pdu->params[8] = player->cb->get_status(player->user_data);;
pdu->params_len = htons(9);
return AVC_CTYPE_STABLE;
}
static uint8_t avrcp_handle_register_notification(struct avrcp_player *player,
struct avrcp_header *pdu,
uint8_t transaction)
{
uint16_t len = ntohs(pdu->params_len);
/*
* 1 byte for EventID, 4 bytes for Playback interval but the latest
* one is applicable only for EVENT_PLAYBACK_POS_CHANGED. See AVRCP
* 1.3 spec, section 5.4.2.
*/
if (len != 5)
goto err;
switch (pdu->params[0]) {
case AVRCP_EVENT_STATUS_CHANGED:
len = 2;
pdu->params[1] = player->cb->get_status(player->user_data);
break;
case AVRCP_EVENT_TRACK_CHANGED:
len = 9;
memset(&pdu->params[1], 0, 8);
break;
default:
/* All other events are not supported yet */
goto err;
}
/* Register event and save the transaction used */
player->registered_events |= (1 << pdu->params[0]);
player->transaction_events[pdu->params[0]] = transaction;
pdu->params_len = htons(len);
return AVC_CTYPE_INTERIM;
err:
pdu->params_len = htons(1);
pdu->params[0] = E_INVALID_PARAM;
return AVC_CTYPE_REJECTED;
}
static struct pdu_handler {
uint8_t pdu_id;
uint8_t code;
uint8_t (*func) (struct avrcp_player *player,
struct avrcp_header *pdu,
uint8_t transaction);
} handlers[] = {
{ AVRCP_GET_CAPABILITIES, AVC_CTYPE_STATUS,
avrcp_handle_get_capabilities },
{ AVRCP_LIST_PLAYER_ATTRIBUTES, AVC_CTYPE_STATUS,
avrcp_handle_list_player_attributes },
{ AVRCP_LIST_PLAYER_VALUES, AVC_CTYPE_STATUS,
avrcp_handle_list_player_values },
{ AVRCP_GET_ELEMENT_ATTRIBUTES, AVC_CTYPE_STATUS,
avrcp_handle_get_element_attributes },
{ AVRCP_GET_CURRENT_PLAYER_VALUE, AVC_CTYPE_STATUS,
avrcp_handle_get_current_player_value },
{ AVRCP_SET_PLAYER_VALUE, AVC_CTYPE_CONTROL,
avrcp_handle_set_player_value },
{ AVRCP_GET_PLAYER_ATTRIBUTE_TEXT, AVC_CTYPE_STATUS,
NULL },
{ AVRCP_GET_PLAYER_VALUE_TEXT, AVC_CTYPE_STATUS,
NULL },
{ AVRCP_DISPLAYABLE_CHARSET, AVC_CTYPE_STATUS,
avrcp_handle_displayable_charset },
{ AVRCP_CT_BATTERY_STATUS, AVC_CTYPE_STATUS,
avrcp_handle_ct_battery_status },
{ AVRCP_GET_PLAY_STATUS, AVC_CTYPE_STATUS,
avrcp_handle_get_play_status },
{ AVRCP_REGISTER_NOTIFICATION, AVC_CTYPE_NOTIFY,
avrcp_handle_register_notification },
{ },
};
/* handle vendordep pdu inside an avctp packet */
static size_t handle_vendordep_pdu(struct avctp *session, uint8_t transaction,
uint8_t *code, uint8_t *subunit,
uint8_t *operands, size_t operand_count,
void *user_data)
{
struct avrcp_player *player = user_data;
struct pdu_handler *handler;
struct avrcp_header *pdu = (void *) operands;
uint32_t company_id = get_company_id(pdu->company_id);
if (company_id != IEEEID_BTSIG) {
*code = AVC_CTYPE_NOT_IMPLEMENTED;
return 0;
}
DBG("AVRCP PDU 0x%02X, company 0x%06X len 0x%04X",
pdu->pdu_id, company_id, pdu->params_len);
pdu->packet_type = 0;
pdu->rsvd = 0;
if (operand_count + 3 < AVRCP_HEADER_LENGTH) {
pdu->params[0] = E_INVALID_COMMAND;
goto err_metadata;
}
for (handler = handlers; handler; handler++) {
if (handler->pdu_id == pdu->pdu_id)
break;
}
if (!handler || handler->code != *code) {
pdu->params[0] = E_INVALID_COMMAND;
goto err_metadata;
}
if (!handler->func) {
pdu->params[0] = E_INVALID_PARAM;
goto err_metadata;
}
*code = handler->func(player, pdu, transaction);
return AVRCP_HEADER_LENGTH + ntohs(pdu->params_len);
err_metadata:
pdu->params_len = htons(1);
*code = AVC_CTYPE_REJECTED;
return AVRCP_HEADER_LENGTH + 1;
}
static struct avrcp_server *find_server(GSList *list, const bdaddr_t *src)
{
for (; list; list = list->next) {
struct avrcp_server *server = list->data;
if (bacmp(&server->src, src) == 0)
return server;
}
return NULL;
}
static void state_changed(struct audio_device *dev, avctp_state_t old_state,
avctp_state_t new_state, void *user_data)
{
struct avrcp_server *server;
struct avrcp_player *player;
server = find_server(servers, &dev->src);
if (!server)
return;
player = server->active_player;
if (!player)
return;
switch (new_state) {
case AVCTP_STATE_DISCONNECTED:
player->session = NULL;
if (player->handler) {
avctp_unregister_pdu_handler(player->handler);
player->handler = 0;
}
break;
case AVCTP_STATE_CONNECTING:
player->session = avctp_connect(&dev->src, &dev->dst);
if (!player->handler)
player->handler = avctp_register_pdu_handler(
AVC_OP_VENDORDEP,
handle_vendordep_pdu,
player);
break;
default:
return;
}
}
gboolean avrcp_connect(struct audio_device *dev)
{
struct avctp *session;
session = avctp_connect(&dev->src, &dev->dst);
if (session)
return FALSE;
return TRUE;
}
void avrcp_disconnect(struct audio_device *dev)
{
struct avctp *session;
session = avctp_get(&dev->src, &dev->dst);
if (!session)
return;
avctp_disconnect(session);
}
int avrcp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config)
{
sdp_record_t *record;
gboolean tmp, master = TRUE;
GError *err = NULL;
struct avrcp_server *server;
if (config) {
tmp = g_key_file_get_boolean(config, "General",
"Master", &err);
if (err) {
DBG("audio.conf: %s", err->message);
g_error_free(err);
} else
master = tmp;
}
server = g_new0(struct avrcp_server, 1);
if (!server)
return -ENOMEM;
record = avrcp_tg_record();
if (!record) {
error("Unable to allocate new service record");
g_free(server);
return -1;
}
if (add_record_to_server(src, record) < 0) {
error("Unable to register AVRCP target service record");
g_free(server);
sdp_record_free(record);
return -1;
}
server->tg_record_id = record->handle;
record = avrcp_ct_record();
if (!record) {
error("Unable to allocate new service record");
g_free(server);
return -1;
}
if (add_record_to_server(src, record) < 0) {
error("Unable to register AVRCP service record");
sdp_record_free(record);
g_free(server);
return -1;
}
server->ct_record_id = record->handle;
if (avctp_register(src, master) < 0) {
remove_record_from_server(server->ct_record_id);
remove_record_from_server(server->tg_record_id);
g_free(server);
return -1;
}
bacpy(&server->src, src);
servers = g_slist_append(servers, server);
return 0;
}
static void player_destroy(gpointer data)
{
struct avrcp_player *player = data;
if (player->destroy)
player->destroy(player->user_data);
if (player->handler)
avctp_unregister_pdu_handler(player->handler);
g_free(player);
}
void avrcp_unregister(const bdaddr_t *src)
{
struct avrcp_server *server;
server = find_server(servers, src);
if (!server)
return;
g_slist_free_full(server->players, player_destroy);
servers = g_slist_remove(servers, server);
remove_record_from_server(server->ct_record_id);
remove_record_from_server(server->tg_record_id);
avctp_unregister(&server->src);
g_free(server);
if (servers)
return;
if (avctp_id) {
avctp_remove_state_cb(avctp_id);
avctp_id = 0;
}
}
struct avrcp_player *avrcp_register_player(const bdaddr_t *src,
struct avrcp_player_cb *cb,
void *user_data,
GDestroyNotify destroy)
{
struct avrcp_server *server;
struct avrcp_player *player;
server = find_server(servers, src);
if (!server)
return NULL;
player = g_new0(struct avrcp_player, 1);
player->server = server;
player->cb = cb;
player->user_data = user_data;
player->destroy = destroy;
if (!server->players)
server->active_player = player;
if (!avctp_id)
avctp_id = avctp_add_state_cb(state_changed, NULL);
server->players = g_slist_append(server->players, player);
return player;
}
void avrcp_unregister_player(struct avrcp_player *player)
{
struct avrcp_server *server = player->server;
server->players = g_slist_remove(server->players, player);
if (server->active_player == player)
server->active_player = g_slist_nth_data(server->players, 0);
player_destroy(player);
}