text: /* Copyright 2016 The Chromium OS Authors. All rights reserved.
   * Use of this source code is governed by a BSD-style license that can be
   * found in the LICENSE file.
   */
  #include <dbus/dbus.h>
  #include <errno.h>
  #include <stdio.h>
  #include <stdlib.h>
  #include <string.h>
  #include <syslog.h>
  
  #include "cras_bt_adapter.h"
  #include "cras_bt_constants.h"
  #include "cras_bt_player.h"
  #include "cras_dbus_util.h"
  #include "cras_utf8.h"
  #include "utlist.h"
  
  static void cras_bt_on_player_registered(DBusPendingCall *pending_call,
  					 void *data)
  {
  	DBusMessage *reply;
  
  	reply = dbus_pending_call_steal_reply(pending_call);
  	dbus_pending_call_unref(pending_call);
  
  	if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) {
  		syslog(LOG_ERR, "RegisterPlayer returned error: %s",
  		       dbus_message_get_error_name(reply));
  		dbus_message_unref(reply);
  		return;
  	}
  
  	dbus_message_unref(reply);
  }
  
  static int cras_bt_add_player(DBusConnection *conn,
  			      const struct cras_bt_adapter *adapter,
  			      struct cras_bt_player *player)
  {
  	const char *adapter_path;
  	DBusMessage *method_call;
  	DBusMessageIter message_iter, dict;
  	DBusPendingCall *pending_call;
  
  	adapter_path = cras_bt_adapter_object_path(adapter);
  	method_call = dbus_message_new_method_call(BLUEZ_SERVICE, adapter_path,
  						   BLUEZ_INTERFACE_MEDIA,
  						   "RegisterPlayer");
  	if (!method_call)
  		return -ENOMEM;
  
  	dbus_message_iter_init_append(method_call, &message_iter);
  	dbus_message_iter_append_basic(&message_iter, DBUS_TYPE_OBJECT_PATH,
  				       &player->object_path);
  
  	dbus_message_iter_open_container(
  		&message_iter, DBUS_TYPE_ARRAY,
  		DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING
  			DBUS_TYPE_VARIANT_AS_STRING
  				DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
  		&dict);
  
  	append_key_value(&dict, "PlaybackStatus", DBUS_TYPE_STRING,
  			 DBUS_TYPE_STRING_AS_STRING, &player->playback_status);
  	append_key_value(&dict, "Identity", DBUS_TYPE_STRING,
  			 DBUS_TYPE_STRING_AS_STRING, &player->identity);
  	append_key_value(&dict, "LoopStatus", DBUS_TYPE_STRING,
  			 DBUS_TYPE_STRING_AS_STRING, &player->loop_status);
  	append_key_value(&dict, "Position", DBUS_TYPE_INT64,
  			 DBUS_TYPE_INT64_AS_STRING, &player->position);
  	append_key_value(&dict, "Shuffle", DBUS_TYPE_BOOLEAN,
  			 DBUS_TYPE_BOOLEAN_AS_STRING, &player->shuffle);
  	append_key_value(&dict, "CanGoNext", DBUS_TYPE_BOOLEAN,
  			 DBUS_TYPE_BOOLEAN_AS_STRING, &player->can_go_next);
  	append_key_value(&dict, "CanGoPrevious", DBUS_TYPE_BOOLEAN,
  			 DBUS_TYPE_BOOLEAN_AS_STRING, &player->can_go_prev);
  	append_key_value(&dict, "CanPlay", DBUS_TYPE_BOOLEAN,
  			 DBUS_TYPE_BOOLEAN_AS_STRING, &player->can_play);
  	append_key_value(&dict, "CanPause", DBUS_TYPE_BOOLEAN,
  			 DBUS_TYPE_BOOLEAN_AS_STRING, &player->can_pause);
  	append_key_value(&dict, "CanControl", DBUS_TYPE_BOOLEAN,
  			 DBUS_TYPE_BOOLEAN_AS_STRING, &player->can_control);
  
  	dbus_message_iter_close_container(&message_iter, &dict);
  
  	if (!dbus_connection_send_with_reply(conn, method_call, &pending_call,
  					     DBUS_TIMEOUT_USE_DEFAULT)) {
  		dbus_message_unref(method_call);
  		return -ENOMEM;
  	}
  
  	dbus_message_unref(method_call);
  	if (!pending_call)
  		return -EIO;
  
  	if (!dbus_pending_call_set_notify(
  		    pending_call, cras_bt_on_player_registered, player, NULL)) {
  		dbus_pending_call_cancel(pending_call);
  		dbus_pending_call_unref(pending_call);
  		return -ENOMEM;
  	}
  	return 0;
  }
  
  /* Note that player properties will be used mostly for AVRCP qualification and
   * not for normal use cases. The corresponding media events won't be routed by
   * CRAS until we have a plan to provide general system API to handle media
   * control.
   */
  static struct cras_bt_player player = {
  	.object_path = CRAS_DEFAULT_PLAYER,
  	.playback_status = NULL,
  	.identity = NULL,
  	.loop_status = "None",
  	.shuffle = 0,
  	.metadata = NULL,
  	.position = 0,
  	.can_go_next = 0,
  	.can_go_prev = 0,
  	.can_play = 0,
  	.can_pause = 0,
  	.can_control = 0,
  	.message_cb = NULL,
  };
  
  static DBusHandlerResult cras_bt_player_handle_message(DBusConnection *conn,
  						       DBusMessage *message,
  						       void *arg)
  {
  	const char *msg = dbus_message_get_member(message);
  
  	if (player.message_cb)
  		player.message_cb(msg);
  
  	return DBUS_HANDLER_RESULT_HANDLED;
  }
  
  static struct cras_bt_player_metadata *cras_bt_player_metadata_init()
  {
  	struct cras_bt_player_metadata *metadata =
  		malloc(sizeof(struct cras_bt_player_metadata));
  	metadata->title = calloc(1, CRAS_PLAYER_METADATA_SIZE_MAX);
  	metadata->album = calloc(1, CRAS_PLAYER_METADATA_SIZE_MAX);
  	metadata->artist = calloc(1, CRAS_PLAYER_METADATA_SIZE_MAX);
  	metadata->length = 0;
  
  	return metadata;
  }
  
  static void cras_bt_player_init()
  {
  	player.playback_status = malloc(CRAS_PLAYER_PLAYBACK_STATUS_SIZE_MAX);
  	player.identity = malloc(CRAS_PLAYER_IDENTITY_SIZE_MAX);
  
  	strcpy(player.playback_status, CRAS_PLAYER_PLAYBACK_STATUS_DEFAULT);
  	strcpy(player.identity, CRAS_PLAYER_IDENTITY_DEFAULT);
  	player.position = 0;
  
  	player.metadata = cras_bt_player_metadata_init();
  }
  
  static void cras_bt_player_append_metadata_artist(DBusMessageIter *iter,
  						  const char *artist)
  {
  	DBusMessageIter dict, varient, array;
  	const char *artist_key = "xesam:artist";
  
  	dbus_message_iter_open_container(iter, DBUS_TYPE_DICT_ENTRY, NULL,
  					 &dict);
  	dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, &artist_key);
  	dbus_message_iter_open_container(
  		&dict, DBUS_TYPE_VARIANT,
  		DBUS_TYPE_ARRAY_AS_STRING DBUS_TYPE_STRING_AS_STRING, &varient);
  	dbus_message_iter_open_container(&varient, DBUS_TYPE_ARRAY,
  					 DBUS_TYPE_STRING_AS_STRING, &array);
  	dbus_message_iter_append_basic(&array, DBUS_TYPE_STRING, &artist);
  	dbus_message_iter_close_container(&varient, &array);
  	dbus_message_iter_close_container(&dict, &varient);
  	dbus_message_iter_close_container(iter, &dict);
  }
  
  static void cras_bt_player_append_metadata(DBusMessageIter *iter,
  					   const char *title,
  					   const char *artist,
  					   const char *album,
  					   dbus_int64_t length)
  {
  	DBusMessageIter varient, array;
  	dbus_message_iter_open_container(
  		iter, DBUS_TYPE_VARIANT,
  		DBUS_TYPE_ARRAY_AS_STRING DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
  			DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
  				DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
  		&varient);
  	dbus_message_iter_open_container(
  		&varient, DBUS_TYPE_ARRAY,
  		DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING
  			DBUS_TYPE_VARIANT_AS_STRING
  				DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
  		&array);
  	if (!is_utf8_string(title)) {
  		syslog(LOG_INFO, "Non-utf8 title: %s", title);
  		title = "";
  	}
  	if (!is_utf8_string(album)) {
  		syslog(LOG_INFO, "Non-utf8 album: %s", album);
  		album = "";
  	}
  	if (!is_utf8_string(artist)) {
  		syslog(LOG_INFO, "Non-utf8 artist: %s", artist);
  		artist = "";
  	}
  
  	append_key_value(&array, "xesam:title", DBUS_TYPE_STRING,
  			 DBUS_TYPE_STRING_AS_STRING, &title);
  	append_key_value(&array, "xesam:album", DBUS_TYPE_STRING,
  			 DBUS_TYPE_STRING_AS_STRING, &album);
  	append_key_value(&array, "mpris:length", DBUS_TYPE_INT64,
  			 DBUS_TYPE_INT64_AS_STRING, &length);
  	cras_bt_player_append_metadata_artist(&array, artist);
  
  	dbus_message_iter_close_container(&varient, &array);
  	dbus_message_iter_close_container(iter, &varient);
  }
  
  static bool cras_bt_player_parse_metadata(const char *title, const char *album,
  					  const char *artist,
  					  const dbus_int64_t length)
  {
  	bool require_update = false;
  
  	if (title && strcmp(player.metadata->title, title)) {
  		snprintf(player.metadata->title, CRAS_PLAYER_METADATA_SIZE_MAX,
  			 "%s", title);
  		require_update = true;
  	}
  	if (artist && strcmp(player.metadata->artist, artist)) {
  		snprintf(player.metadata->artist, CRAS_PLAYER_METADATA_SIZE_MAX,
  			 "%s", artist);
  		require_update = true;
  	}
  	if (album && strcmp(player.metadata->album, album)) {
  		snprintf(player.metadata->album, CRAS_PLAYER_METADATA_SIZE_MAX,
  			 "%s", album);
  		require_update = true;
  	}
  	if (length && player.metadata->length != length) {
  		player.metadata->length = length;
  		require_update = true;
  	}
  
  	return require_update;
  }
  
  int cras_bt_player_create(DBusConnection *conn)
  {
  	static const DBusObjectPathVTable player_vtable = {
  		.message_function = cras_bt_player_handle_message
  	};
  
  	DBusError dbus_error;
  	struct cras_bt_adapter **adapters;
  	size_t num_adapters, i;
  
  	dbus_error_init(&dbus_error);
  
  	cras_bt_player_init();
  	if (!dbus_connection_register_object_path(
  		    conn, player.object_path, &player_vtable, &dbus_error)) {
  		syslog(LOG_ERR, "Cannot register player %s",
  		       player.object_path);
  		dbus_error_free(&dbus_error);
  		return -ENOMEM;
  	}
  
  	num_adapters = cras_bt_adapter_get_list(&adapters);
  	for (i = 0; i < num_adapters; ++i)
  		cras_bt_add_player(conn, adapters[i], &player);
  	free(adapters);
  	return 0;
  }
  
  int cras_bt_register_player(DBusConnection *conn,
  			    const struct cras_bt_adapter *adapter)
  {
  	return cras_bt_add_player(conn, adapter, &player);
  }
  
  int cras_bt_player_update_playback_status(DBusConnection *conn,
  					  const char *status)
  {
  	DBusMessage *msg;
  	DBusMessageIter iter, dict;
  	const char *playerInterface = BLUEZ_INTERFACE_MEDIA_PLAYER;
  
  	if (!player.playback_status)
  		return -ENXIO;
  
  	/* Verify the string value matches one of the possible status defined in
  	 * bluez/profiles/audio/avrcp.c
  	 */
  	if (strcasecmp(status, "stopped") != 0 &&
  	    strcasecmp(status, "playing") != 0 &&
  	    strcasecmp(status, "paused") != 0 &&
  	    strcasecmp(status, "forward-seek") != 0 &&
  	    strcasecmp(status, "reverse-seek") != 0 &&
  	    strcasecmp(status, "error") != 0)
  		return -EINVAL;
  
  	if (!strcasecmp(player.playback_status, status))
  		return 0;
  
  	strcpy(player.playback_status, status);
  
  	msg = dbus_message_new_signal(CRAS_DEFAULT_PLAYER,
  				      DBUS_INTERFACE_PROPERTIES,
  				      "PropertiesChanged");
  	if (!msg)
  		return -ENOMEM;
  
  	dbus_message_iter_init_append(msg, &iter);
  	dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING,
  				       &playerInterface);
  	dbus_message_iter_open_container(
  		&iter, DBUS_TYPE_ARRAY,
  		DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING
  			DBUS_TYPE_VARIANT_AS_STRING
  				DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
  		&dict);
  	append_key_value(&dict, "PlaybackStatus", DBUS_TYPE_STRING,
  			 DBUS_TYPE_STRING_AS_STRING, &status);
  	dbus_message_iter_close_container(&iter, &dict);
  
  	if (!dbus_connection_send(conn, msg, NULL)) {
  		dbus_message_unref(msg);
  		return -ENOMEM;
  	}
  
  	dbus_message_unref(msg);
  	return 0;
  }
  
  int cras_bt_player_update_identity(DBusConnection *conn, const char *identity)
  {
  	DBusMessage *msg;
  	DBusMessageIter iter, dict;
  	const char *playerInterface = BLUEZ_INTERFACE_MEDIA_PLAYER;
  
  	if (!player.identity)
  		return -ENXIO;
  
  	if (!identity)
  		return -EINVAL;
  
  	if (!is_utf8_string(identity)) {
  		syslog(LOG_INFO, "Non-utf8 identity: %s", identity);
  		identity = "";
  	}
  
  	if (!strcasecmp(player.identity, identity))
  		return 0;
  
  	strcpy(player.identity, identity);
  
  	msg = dbus_message_new_signal(CRAS_DEFAULT_PLAYER,
  				      DBUS_INTERFACE_PROPERTIES,
  				      "PropertiesChanged");
  	if (!msg)
  		return -ENOMEM;
  
  	dbus_message_iter_init_append(msg, &iter);
  	dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING,
  				       &playerInterface);
  	dbus_message_iter_open_container(
  		&iter, DBUS_TYPE_ARRAY,
  		DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING
  			DBUS_TYPE_VARIANT_AS_STRING
  				DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
  		&dict);
  	append_key_value(&dict, "Identity", DBUS_TYPE_STRING,
  			 DBUS_TYPE_STRING_AS_STRING, &identity);
  	dbus_message_iter_close_container(&iter, &dict);
  
  	if (!dbus_connection_send(conn, msg, NULL)) {
  		dbus_message_unref(msg);
  		return -ENOMEM;
  	}
  
  	dbus_message_unref(msg);
  	return 0;
  }
  
  int cras_bt_player_update_position(DBusConnection *conn,
  				   const dbus_int64_t position)
  {
  	DBusMessage *msg;
  	DBusMessageIter iter, dict;
  	const char *playerInterface = BLUEZ_INTERFACE_MEDIA_PLAYER;
  
  	if (position < 0)
  		return -EINVAL;
  
  	player.position = position;
  
  	msg = dbus_message_new_signal(CRAS_DEFAULT_PLAYER,
  				      DBUS_INTERFACE_PROPERTIES,
  				      "PropertiesChanged");
  	if (!msg)
  		return -ENOMEM;
  
  	dbus_message_iter_init_append(msg, &iter);
  	dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING,
  				       &playerInterface);
  	dbus_message_iter_open_container(
  		&iter, DBUS_TYPE_ARRAY,
  		DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING
  			DBUS_TYPE_VARIANT_AS_STRING
  				DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
  		&dict);
  	append_key_value(&dict, "Position", DBUS_TYPE_INT64,
  			 DBUS_TYPE_INT64_AS_STRING, &player.position);
  	dbus_message_iter_close_container(&iter, &dict);
  
  	if (!dbus_connection_send(conn, msg, NULL)) {
  		dbus_message_unref(msg);
  		return -ENOMEM;
  	}
  
  	dbus_message_unref(msg);
  	return 0;
  }
  
  int cras_bt_player_update_metadata(DBusConnection *conn, const char *title,
  				   const char *artist, const char *album,
  				   const dbus_int64_t length)
  {
  	DBusMessage *msg;
  	DBusMessageIter iter, array, dict;
  	const char *property = "Metadata";
  	const char *playerInterface = BLUEZ_INTERFACE_MEDIA_PLAYER;
  
  	if (!player.metadata)
  		return -ENXIO;
  
  	msg = dbus_message_new_signal(CRAS_DEFAULT_PLAYER,
  				      DBUS_INTERFACE_PROPERTIES,
  				      "PropertiesChanged");
  	if (!msg)
  		return -ENOMEM;
  
  	dbus_message_iter_init_append(msg, &iter);
  	dbus_message_iter_append_basic(&iter, DBUS_TYPE_STRING,
  				       &playerInterface);
  	dbus_message_iter_open_container(
  		&iter, DBUS_TYPE_ARRAY,
  		DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING DBUS_TYPE_STRING_AS_STRING
  			DBUS_TYPE_VARIANT_AS_STRING
  				DBUS_DICT_ENTRY_END_CHAR_AS_STRING,
  		&array);
  	dbus_message_iter_open_container(&array, DBUS_TYPE_DICT_ENTRY, NULL,
  					 &dict);
  	dbus_message_iter_append_basic(&dict, DBUS_TYPE_STRING, &property);
  
  	if (!cras_bt_player_parse_metadata(title, album, artist, length)) {
  		/* Nothing to update. */
  		dbus_message_unref(msg);
  		return 0;
  	}
  
  	cras_bt_player_append_metadata(&dict, player.metadata->title,
  				       player.metadata->artist,
  				       player.metadata->album,
  				       player.metadata->length);
  
  	dbus_message_iter_close_container(&array, &dict);
  	dbus_message_iter_close_container(&iter, &array);
  
  	if (!dbus_connection_send(conn, msg, NULL)) {
  		dbus_message_unref(msg);
  		return -ENOMEM;
  	}
  
  	dbus_message_unref(msg);
  	return 0;
  }
  