static void
on_content_removed_cb (TpProxy *proxy,
    const gchar *content_path,
    gpointer user_data,
    GObject *weak_object)
{
  TpyCallChannel *self = TPY_CALL_CHANNEL (proxy);
  TpyCallContent *content = NULL, *c;
  guint i;

  DEBUG ("Content removed: %s", content_path);

  for (i = 0; i < self->priv->contents->len; i++)
    {
      c = g_ptr_array_index (self->priv->contents, i);
      if (g_strcmp0 (tp_proxy_get_object_path (c), content_path) == 0)
        {
          content = c;
          break;
        }
    }

  if (content != NULL)
    {
      g_signal_emit (self, _signals[CONTENT_REMOVED], 0, content);
      g_ptr_array_remove (self->priv->contents, content);
    }
  else
    {
      g_warning ("The removed content '%s' isn't in the call!", content_path);
    }
}
static void
on_content_added_cb (TpProxy *proxy,
    const gchar *content_path,
    gpointer user_data,
    GObject *weak_object)
{
  TpyCallChannel *self = TPY_CALL_CHANNEL (proxy);
  TpyCallContent *content;

  DEBUG ("Content added: %s", content_path);

  content = g_object_new (TPY_TYPE_CALL_CONTENT,
          "bus-name", tp_proxy_get_bus_name (self),
          "dbus-daemon", tp_proxy_get_dbus_daemon (self),
          "dbus-connection", tp_proxy_get_dbus_connection (self),
          "object-path", content_path,
          NULL);

  if (content == NULL)
    {
      g_warning ("Could not create a CallContent for path %s", content_path);
      return;
    }

  g_ptr_array_add (self->priv->contents, content);
  tp_g_signal_connect_object (content, "notify::ready",
    G_CALLBACK (on_content_ready_cb), self, 0);

  g_signal_emit (self, _signals[CONTENT_ADDED], 0, content);
}
static void
mex_telepathy_channel_toggle_camera (MxAction *action,
                                     gpointer  user_data)
{
  MexTelepathyChannel *self = MEX_TELEPATHY_CHANNEL (user_data);
  MexTelepathyChannelPrivate *priv = self->priv;
  TpyCallChannel *channel = TPY_CALL_CHANNEL (self->priv->channel);

  priv->sending_video = !priv->sending_video;

  tpy_call_channel_send_video (channel, priv->sending_video);
  mex_telepathy_channel_set_camera_state (self, priv->sending_video);
}
static void
on_call_members_changed_cb (TpProxy *proxy,
    GHashTable *flags_changed,
    const GArray *removed,
    gpointer user_data,
    GObject *weak_object)
{
  TpyCallChannel *self = TPY_CALL_CHANNEL (proxy);

  DEBUG ("Call members: %d changed, %d removed",
      g_hash_table_size (flags_changed),
      removed->len);

  update_call_members (self, flags_changed, removed);

  g_signal_emit (self, _signals[MEMBERS_CHANGED], 0, self->priv->members);
}
static void
on_call_state_changed_cb (TpProxy *proxy,
    guint call_state,
    guint call_flags,
    const GValueArray *call_state_reason,
    GHashTable *call_state_details,
    gpointer user_data,
    GObject *weak_object)
{
  TpyCallChannel *self = TPY_CALL_CHANNEL (proxy);

  DEBUG ("Call state changed");

  self->priv->state = call_state;
  self->priv->flags = call_flags;

  tp_clear_pointer (&self->priv->details, g_hash_table_unref);
  self->priv->details = g_hash_table_ref (call_state_details);

  g_signal_emit (self, _signals[STATE_CHANGED], 0,
      call_state, call_flags, call_state_reason, call_state_details);
}
static void
mex_telepathy_channel_initialize_channel (MexTelepathyChannel *self)
{
  MexTelepathyChannelPrivate *priv = self->priv;

  GstBus *bus;
  GstElement *pipeline;
  GstStateChangeReturn ret;
  gboolean ready;

  TpHandle contactHandle = tp_channel_get_handle (priv->channel, NULL);
  TpContactFeature features[] = { TP_CONTACT_FEATURE_ALIAS,
                                  TP_CONTACT_FEATURE_AVATAR_DATA,
                                  TP_CONTACT_FEATURE_AVATAR_TOKEN};

  MEX_INFO ("New channel");

  if (contactHandle)
    tp_connection_get_contacts_by_handle (
      priv->connection, 1, &contactHandle, 1,
      features,
      mex_telepathy_channel_on_contact_fetched,
      self, NULL, NULL);

  pipeline = gst_pipeline_new (NULL);

  ret = gst_element_set_state (pipeline, GST_STATE_PLAYING);

  if (ret == GST_STATE_CHANGE_FAILURE)
    {
      tp_channel_close_async (TP_CHANNEL (priv->channel), NULL, NULL);
      g_object_unref (pipeline);
      MEX_WARNING ("Failed to start an empty pipeline !?");
      return;
    }

  priv->pipeline = pipeline;

  bus = gst_pipeline_get_bus (GST_PIPELINE (pipeline));
  priv->buswatch = gst_bus_add_watch (bus, mex_telepathy_channel_on_bus_watch,
                                      self);
  g_object_unref (bus);

  tf_channel_new_async (priv->channel, mex_telepathy_channel_new_tf_channel,
                        self);

  tpy_cli_channel_type_call_call_accept (TP_PROXY (priv->channel), -1,
                                         NULL, NULL, NULL, NULL);

  priv->channel = g_object_ref (priv->channel);
  g_signal_connect (priv->channel, "notify::ready",
                    G_CALLBACK (mex_telepathy_channel_on_ready),
                    self);
  g_signal_connect (priv->channel, "invalidated",
                    G_CALLBACK (mex_telepathy_channel_on_proxy_invalidated),
                    self);

  g_signal_connect (TPY_CALL_CHANNEL (priv->channel), "state-changed",
                    G_CALLBACK (mex_telepathy_channel_on_call_state_changed),
                    self);

  g_object_get (priv->channel, "ready", &ready, NULL);
  if (ready)
    mex_telepathy_channel_on_ready (TPY_CALL_CHANNEL (priv->channel), NULL, self);
}
static void
on_call_channel_get_all_properties_cb (TpProxy *proxy,
    GHashTable *properties,
    const GError *error,
    gpointer user_data,
    GObject *weak_object)
{
  TpyCallChannel *self = TPY_CALL_CHANNEL (proxy);
  GSimpleAsyncResult *result = user_data;
  GHashTable *hash_table;
  GPtrArray *contents;
  guint i;

  if (error != NULL)
    {
      g_warning ("Could not get the channel properties: %s", error->message);
      g_simple_async_result_set_from_error (result, error);
      goto out;
    }

  self->priv->state = tp_asv_get_uint32 (properties,
      "CallState", NULL);
  self->priv->flags = tp_asv_get_uint32 (properties,
      "CallFlags", NULL);
  self->priv->initial_audio = tp_asv_get_boolean (properties,
      "InitialAudio", NULL);
  self->priv->initial_video = tp_asv_get_boolean (properties,
      "InitialVideo", NULL);

  hash_table = tp_asv_get_boxed (properties,
      "CallStateDetails", TP_HASH_TYPE_STRING_VARIANT_MAP);
  if (hash_table != NULL)
    self->priv->details = g_boxed_copy (TP_HASH_TYPE_STRING_VARIANT_MAP,
        hash_table);

  hash_table = tp_asv_get_boxed (properties,
      "CallMembers", TPY_HASH_TYPE_CALL_MEMBER_MAP);
  update_call_members (self, hash_table, NULL);

  contents = tp_asv_get_boxed (properties,
      "Contents", TP_ARRAY_TYPE_OBJECT_PATH_LIST);

  for (i = 0; i < contents->len; i++)
    {
      const gchar *content_path = g_ptr_array_index (contents, i);
      TpyCallContent *content;

      DEBUG ("Content added: %s", content_path);

      content = g_object_new (TPY_TYPE_CALL_CONTENT,
              "bus-name", tp_proxy_get_bus_name (self),
              "dbus-daemon", tp_proxy_get_dbus_daemon (self),
              "dbus-connection", tp_proxy_get_dbus_connection (self),
              "object-path", content_path,
              NULL);

      if (content == NULL)
        {
          g_warning ("Could not create a CallContent for path %s", content_path);

          g_simple_async_result_set_error (result, TP_ERRORS, TP_ERROR_CONFUSED,
              "Could not create a CallContent for path %s", content_path);
          goto out;
        }

      g_ptr_array_add (self->priv->contents, content);

      tp_g_signal_connect_object (content, "notify::ready",
        G_CALLBACK (on_content_ready_cb), self, 0);
    }

  g_signal_emit (self, _signals[MEMBERS_CHANGED], 0, self->priv->members);

  self->priv->properties_retrieved = TRUE;

  maybe_go_to_ready (self);

out:
  /* TODO; ideally we should get rid of the ready property and complete once
   * all the contents have been prepared. Or maybe that should be another
   * feature? */
  g_simple_async_result_complete (result);
}