• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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