1 /* Copyright 2016 The Chromium OS Authors. All rights reserved.
2 * Use of this source code is governed by a BSD-style license that can be
3 * found in the LICENSE file.
4 */
5 #include <dbus/dbus.h>
6 #include <errno.h>
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <string.h>
10 #include <syslog.h>
11
12 #include "cras_bt_adapter.h"
13 #include "cras_bt_constants.h"
14 #include "cras_bt_player.h"
15 #include "cras_dbus_util.h"
16 #include "cras_utf8.h"
17 #include "utlist.h"
18
cras_bt_on_player_registered(DBusPendingCall * pending_call,void * data)19 static void cras_bt_on_player_registered(DBusPendingCall *pending_call,
20 void *data)
21 {
22 DBusMessage *reply;
23
24 reply = dbus_pending_call_steal_reply(pending_call);
25 dbus_pending_call_unref(pending_call);
26
27 if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) {
28 syslog(LOG_ERR, "RegisterPlayer returned error: %s",
29 dbus_message_get_error_name(reply));
30 dbus_message_unref(reply);
31 return;
32 }
33
34 dbus_message_unref(reply);
35 }
36
cras_bt_add_player(DBusConnection * conn,const struct cras_bt_adapter * adapter,struct cras_bt_player * player)37 static int cras_bt_add_player(DBusConnection *conn,
38 const struct cras_bt_adapter *adapter,
39 struct cras_bt_player *player)
40 {
41 const char *adapter_path;
42 DBusMessage *method_call;
43 DBusMessageIter message_iter, dict;
44 DBusPendingCall *pending_call;
45
46 adapter_path = cras_bt_adapter_object_path(adapter);
47 method_call = dbus_message_new_method_call(BLUEZ_SERVICE, adapter_path,
48 BLUEZ_INTERFACE_MEDIA,
49 "RegisterPlayer");
50 if (!method_call)
51 return -ENOMEM;
52
53 dbus_message_iter_init_append(method_call, &message_iter);
54 dbus_message_iter_append_basic(&message_iter, DBUS_TYPE_OBJECT_PATH,
55 &player->object_path);
56
57 dbus_message_iter_open_container(
58 &message_iter, DBUS_TYPE_ARRAY,
59 DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING
60 DBUS_TYPE_VARIANT_AS_STRING
61 DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
62 &dict);
63
64 append_key_value(&dict, "PlaybackStatus", DBUS_TYPE_STRING,
65 DBUS_TYPE_STRING_AS_STRING, &player->playback_status);
66 append_key_value(&dict, "Identity", DBUS_TYPE_STRING,
67 DBUS_TYPE_STRING_AS_STRING, &player->identity);
68 append_key_value(&dict, "LoopStatus", DBUS_TYPE_STRING,
69 DBUS_TYPE_STRING_AS_STRING, &player->loop_status);
70 append_key_value(&dict, "Position", DBUS_TYPE_INT64,
71 DBUS_TYPE_INT64_AS_STRING, &player->position);
72 append_key_value(&dict, "Shuffle", DBUS_TYPE_BOOLEAN,
73 DBUS_TYPE_BOOLEAN_AS_STRING, &player->shuffle);
74 append_key_value(&dict, "CanGoNext", DBUS_TYPE_BOOLEAN,
75 DBUS_TYPE_BOOLEAN_AS_STRING, &player->can_go_next);
76 append_key_value(&dict, "CanGoPrevious", DBUS_TYPE_BOOLEAN,
77 DBUS_TYPE_BOOLEAN_AS_STRING, &player->can_go_prev);
78 append_key_value(&dict, "CanPlay", DBUS_TYPE_BOOLEAN,
79 DBUS_TYPE_BOOLEAN_AS_STRING, &player->can_play);
80 append_key_value(&dict, "CanPause", DBUS_TYPE_BOOLEAN,
81 DBUS_TYPE_BOOLEAN_AS_STRING, &player->can_pause);
82 append_key_value(&dict, "CanControl", DBUS_TYPE_BOOLEAN,
83 DBUS_TYPE_BOOLEAN_AS_STRING, &player->can_control);
84
85 dbus_message_iter_close_container(&message_iter, &dict);
86
87 if (!dbus_connection_send_with_reply(conn, method_call, &pending_call,
88 DBUS_TIMEOUT_USE_DEFAULT)) {
89 dbus_message_unref(method_call);
90 return -ENOMEM;
91 }
92
93 dbus_message_unref(method_call);
94 if (!pending_call)
95 return -EIO;
96
97 if (!dbus_pending_call_set_notify(
98 pending_call, cras_bt_on_player_registered, player, NULL)) {
99 dbus_pending_call_cancel(pending_call);
100 dbus_pending_call_unref(pending_call);
101 return -ENOMEM;
102 }
103 return 0;
104 }
105
106 /* Note that player properties will be used mostly for AVRCP qualification and
107 * not for normal use cases. The corresponding media events won't be routed by
108 * CRAS until we have a plan to provide general system API to handle media
109 * control.
110 */
111 static struct cras_bt_player player = {
112 .object_path = CRAS_DEFAULT_PLAYER,
113 .playback_status = NULL,
114 .identity = NULL,
115 .loop_status = "None",
116 .shuffle = 0,
117 .metadata = NULL,
118 .position = 0,
119 .can_go_next = 0,
120 .can_go_prev = 0,
121 .can_play = 0,
122 .can_pause = 0,
123 .can_control = 0,
124 .message_cb = NULL,
125 };
126
cras_bt_player_handle_message(DBusConnection * conn,DBusMessage * message,void * arg)127 static DBusHandlerResult cras_bt_player_handle_message(DBusConnection *conn,
128 DBusMessage *message,
129 void *arg)
130 {
131 const char *msg = dbus_message_get_member(message);
132
133 if (player.message_cb)
134 player.message_cb(msg);
135
136 return DBUS_HANDLER_RESULT_HANDLED;
137 }
138
cras_bt_player_metadata_init()139 static struct cras_bt_player_metadata *cras_bt_player_metadata_init()
140 {
141 struct cras_bt_player_metadata *metadata =
142 malloc(sizeof(struct cras_bt_player_metadata));
143 metadata->title = calloc(1, CRAS_PLAYER_METADATA_SIZE_MAX);
144 metadata->album = calloc(1, CRAS_PLAYER_METADATA_SIZE_MAX);
145 metadata->artist = calloc(1, CRAS_PLAYER_METADATA_SIZE_MAX);
146 metadata->length = 0;
147
148 return metadata;
149 }
150
cras_bt_player_init()151 static void cras_bt_player_init()
152 {
153 player.playback_status = malloc(CRAS_PLAYER_PLAYBACK_STATUS_SIZE_MAX);
154 player.identity = malloc(CRAS_PLAYER_IDENTITY_SIZE_MAX);
155
156 strcpy(player.playback_status, CRAS_PLAYER_PLAYBACK_STATUS_DEFAULT);
157 strcpy(player.identity, CRAS_PLAYER_IDENTITY_DEFAULT);
158 player.position = 0;
159
160 player.metadata = cras_bt_player_metadata_init();
161 }
162
cras_bt_player_append_metadata_artist(DBusMessageIter * iter,const char * artist)163 static void cras_bt_player_append_metadata_artist(DBusMessageIter *iter,
164 const char *artist)
165 {
166 DBusMessageIter dict, varient, array;
167 const char *artist_key = "xesam:artist";
168
169 dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL,
170 &dict);
171 dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, &artist_key);
172 dbus_message_iter_open_container(
173 &dict, DBUS_TYPE_VARIANT,
174 DBUS_TYPE_ARRAY_AS_STRING DBUS_TYPE_STRING_AS_STRING, &varient);
175 dbus_message_iter_open_container(&varient, DBUS_TYPE_ARRAY,
176 DBUS_TYPE_STRING_AS_STRING, &array);
177 dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &artist);
178 dbus_message_iter_close_container(&varient, &array);
179 dbus_message_iter_close_container(&dict, &varient);
180 dbus_message_iter_close_container(iter, &dict);
181 }
182
cras_bt_player_append_metadata(DBusMessageIter * iter,const char * title,const char * artist,const char * album,dbus_int64_t length)183 static void cras_bt_player_append_metadata(DBusMessageIter *iter,
184 const char *title,
185 const char *artist,
186 const char *album,
187 dbus_int64_t length)
188 {
189 DBusMessageIter varient, array;
190 dbus_message_iter_open_container(
191 iter, DBUS_TYPE_VARIANT,
192 DBUS_TYPE_ARRAY_AS_STRING DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
193 DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
194 DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
195 &varient);
196 dbus_message_iter_open_container(
197 &varient, DBUS_TYPE_ARRAY,
198 DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING
199 DBUS_TYPE_VARIANT_AS_STRING
200 DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
201 &array);
202 if (!is_utf8_string(title)) {
203 syslog(LOG_INFO, "Non-utf8 title: %s", title);
204 title = "";
205 }
206 if (!is_utf8_string(album)) {
207 syslog(LOG_INFO, "Non-utf8 album: %s", album);
208 album = "";
209 }
210 if (!is_utf8_string(artist)) {
211 syslog(LOG_INFO, "Non-utf8 artist: %s", artist);
212 artist = "";
213 }
214
215 append_key_value(&array, "xesam:title", DBUS_TYPE_STRING,
216 DBUS_TYPE_STRING_AS_STRING, &title);
217 append_key_value(&array, "xesam:album", DBUS_TYPE_STRING,
218 DBUS_TYPE_STRING_AS_STRING, &album);
219 append_key_value(&array, "mpris:length", DBUS_TYPE_INT64,
220 DBUS_TYPE_INT64_AS_STRING, &length);
221 cras_bt_player_append_metadata_artist(&array, artist);
222
223 dbus_message_iter_close_container(&varient, &array);
224 dbus_message_iter_close_container(iter, &varient);
225 }
226
cras_bt_player_parse_metadata(const char * title,const char * album,const char * artist,const dbus_int64_t length)227 static bool cras_bt_player_parse_metadata(const char *title, const char *album,
228 const char *artist,
229 const dbus_int64_t length)
230 {
231 bool require_update = false;
232
233 if (title && strcmp(player.metadata->title, title)) {
234 snprintf(player.metadata->title, CRAS_PLAYER_METADATA_SIZE_MAX,
235 "%s", title);
236 require_update = true;
237 }
238 if (artist && strcmp(player.metadata->artist, artist)) {
239 snprintf(player.metadata->artist, CRAS_PLAYER_METADATA_SIZE_MAX,
240 "%s", artist);
241 require_update = true;
242 }
243 if (album && strcmp(player.metadata->album, album)) {
244 snprintf(player.metadata->album, CRAS_PLAYER_METADATA_SIZE_MAX,
245 "%s", album);
246 require_update = true;
247 }
248 if (length && player.metadata->length != length) {
249 player.metadata->length = length;
250 require_update = true;
251 }
252
253 return require_update;
254 }
255
cras_bt_player_create(DBusConnection * conn)256 int cras_bt_player_create(DBusConnection *conn)
257 {
258 static const DBusObjectPathVTable player_vtable = {
259 .message_function = cras_bt_player_handle_message
260 };
261
262 DBusError dbus_error;
263 struct cras_bt_adapter **adapters;
264 size_t num_adapters, i;
265
266 dbus_error_init(&dbus_error);
267
268 cras_bt_player_init();
269 if (!dbus_connection_register_object_path(
270 conn, player.object_path, &player_vtable, &dbus_error)) {
271 syslog(LOG_ERR, "Cannot register player %s",
272 player.object_path);
273 dbus_error_free(&dbus_error);
274 return -ENOMEM;
275 }
276
277 num_adapters = cras_bt_adapter_get_list(&adapters);
278 for (i = 0; i < num_adapters; ++i)
279 cras_bt_add_player(conn, adapters[i], &player);
280 free(adapters);
281 return 0;
282 }
283
cras_bt_register_player(DBusConnection * conn,const struct cras_bt_adapter * adapter)284 int cras_bt_register_player(DBusConnection *conn,
285 const struct cras_bt_adapter *adapter)
286 {
287 return cras_bt_add_player(conn, adapter, &player);
288 }
289
cras_bt_player_update_playback_status(DBusConnection * conn,const char * status)290 int cras_bt_player_update_playback_status(DBusConnection *conn,
291 const char *status)
292 {
293 DBusMessage *msg;
294 DBusMessageIter iter, dict;
295 const char *playerInterface = BLUEZ_INTERFACE_MEDIA_PLAYER;
296
297 if (!player.playback_status)
298 return -ENXIO;
299
300 /* Verify the string value matches one of the possible status defined in
301 * bluez/profiles/audio/avrcp.c
302 */
303 if (strcasecmp(status, "stopped") != 0 &&
304 strcasecmp(status, "playing") != 0 &&
305 strcasecmp(status, "paused") != 0 &&
306 strcasecmp(status, "forward-seek") != 0 &&
307 strcasecmp(status, "reverse-seek") != 0 &&
308 strcasecmp(status, "error") != 0)
309 return -EINVAL;
310
311 if (!strcasecmp(player.playback_status, status))
312 return 0;
313
314 strcpy(player.playback_status, status);
315
316 msg = dbus_message_new_signal(CRAS_DEFAULT_PLAYER,
317 DBUS_INTERFACE_PROPERTIES,
318 "PropertiesChanged");
319 if (!msg)
320 return -ENOMEM;
321
322 dbus_message_iter_init_append(msg, &iter);
323 dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING,
324 &playerInterface);
325 dbus_message_iter_open_container(
326 &iter, DBUS_TYPE_ARRAY,
327 DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING
328 DBUS_TYPE_VARIANT_AS_STRING
329 DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
330 &dict);
331 append_key_value(&dict, "PlaybackStatus", DBUS_TYPE_STRING,
332 DBUS_TYPE_STRING_AS_STRING, &status);
333 dbus_message_iter_close_container(&iter, &dict);
334
335 if (!dbus_connection_send(conn, msg, NULL)) {
336 dbus_message_unref(msg);
337 return -ENOMEM;
338 }
339
340 dbus_message_unref(msg);
341 return 0;
342 }
343
cras_bt_player_update_identity(DBusConnection * conn,const char * identity)344 int cras_bt_player_update_identity(DBusConnection *conn, const char *identity)
345 {
346 DBusMessage *msg;
347 DBusMessageIter iter, dict;
348 const char *playerInterface = BLUEZ_INTERFACE_MEDIA_PLAYER;
349
350 if (!player.identity)
351 return -ENXIO;
352
353 if (!identity)
354 return -EINVAL;
355
356 if (!is_utf8_string(identity)) {
357 syslog(LOG_INFO, "Non-utf8 identity: %s", identity);
358 identity = "";
359 }
360
361 if (!strcasecmp(player.identity, identity))
362 return 0;
363
364 strcpy(player.identity, identity);
365
366 msg = dbus_message_new_signal(CRAS_DEFAULT_PLAYER,
367 DBUS_INTERFACE_PROPERTIES,
368 "PropertiesChanged");
369 if (!msg)
370 return -ENOMEM;
371
372 dbus_message_iter_init_append(msg, &iter);
373 dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING,
374 &playerInterface);
375 dbus_message_iter_open_container(
376 &iter, DBUS_TYPE_ARRAY,
377 DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING
378 DBUS_TYPE_VARIANT_AS_STRING
379 DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
380 &dict);
381 append_key_value(&dict, "Identity", DBUS_TYPE_STRING,
382 DBUS_TYPE_STRING_AS_STRING, &identity);
383 dbus_message_iter_close_container(&iter, &dict);
384
385 if (!dbus_connection_send(conn, msg, NULL)) {
386 dbus_message_unref(msg);
387 return -ENOMEM;
388 }
389
390 dbus_message_unref(msg);
391 return 0;
392 }
393
cras_bt_player_update_position(DBusConnection * conn,const dbus_int64_t position)394 int cras_bt_player_update_position(DBusConnection *conn,
395 const dbus_int64_t position)
396 {
397 DBusMessage *msg;
398 DBusMessageIter iter, dict;
399 const char *playerInterface = BLUEZ_INTERFACE_MEDIA_PLAYER;
400
401 if (position < 0)
402 return -EINVAL;
403
404 player.position = position;
405
406 msg = dbus_message_new_signal(CRAS_DEFAULT_PLAYER,
407 DBUS_INTERFACE_PROPERTIES,
408 "PropertiesChanged");
409 if (!msg)
410 return -ENOMEM;
411
412 dbus_message_iter_init_append(msg, &iter);
413 dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING,
414 &playerInterface);
415 dbus_message_iter_open_container(
416 &iter, DBUS_TYPE_ARRAY,
417 DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING
418 DBUS_TYPE_VARIANT_AS_STRING
419 DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
420 &dict);
421 append_key_value(&dict, "Position", DBUS_TYPE_INT64,
422 DBUS_TYPE_INT64_AS_STRING, &player.position);
423 dbus_message_iter_close_container(&iter, &dict);
424
425 if (!dbus_connection_send(conn, msg, NULL)) {
426 dbus_message_unref(msg);
427 return -ENOMEM;
428 }
429
430 dbus_message_unref(msg);
431 return 0;
432 }
433
cras_bt_player_update_metadata(DBusConnection * conn,const char * title,const char * artist,const char * album,const dbus_int64_t length)434 int cras_bt_player_update_metadata(DBusConnection *conn, const char *title,
435 const char *artist, const char *album,
436 const dbus_int64_t length)
437 {
438 DBusMessage *msg;
439 DBusMessageIter iter, array, dict;
440 const char *property = "Metadata";
441 const char *playerInterface = BLUEZ_INTERFACE_MEDIA_PLAYER;
442
443 if (!player.metadata)
444 return -ENXIO;
445
446 msg = dbus_message_new_signal(CRAS_DEFAULT_PLAYER,
447 DBUS_INTERFACE_PROPERTIES,
448 "PropertiesChanged");
449 if (!msg)
450 return -ENOMEM;
451
452 dbus_message_iter_init_append(msg, &iter);
453 dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING,
454 &playerInterface);
455 dbus_message_iter_open_container(
456 &iter, DBUS_TYPE_ARRAY,
457 DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING
458 DBUS_TYPE_VARIANT_AS_STRING
459 DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
460 &array);
461 dbus_message_iter_open_container(&array, DBUS_TYPE_DICT_ENTRY, NULL,
462 &dict);
463 dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, &property);
464
465 if (!cras_bt_player_parse_metadata(title, album, artist, length)) {
466 /* Nothing to update. */
467 dbus_message_unref(msg);
468 return 0;
469 }
470
471 cras_bt_player_append_metadata(&dict, player.metadata->title,
472 player.metadata->artist,
473 player.metadata->album,
474 player.metadata->length);
475
476 dbus_message_iter_close_container(&array, &dict);
477 dbus_message_iter_close_container(&iter, &array);
478
479 if (!dbus_connection_send(conn, msg, NULL)) {
480 dbus_message_unref(msg);
481 return -ENOMEM;
482 }
483
484 dbus_message_unref(msg);
485 return 0;
486 }
487