]> git.0d.be Git - empathy.git/blobdiff - libempathy/empathy-contact.c
Merge branch 'sasl'
[empathy.git] / libempathy / empathy-contact.c
index a5b799793b28d0bf6983684dd08a49aa5721ab78..5383187c1b07bce2ef8327b86b8a9cf7c49f6d0d 100644 (file)
 #include <telepathy-glib/interfaces.h>
 #include <telepathy-glib/util.h>
 
+#include <telepathy-logger/log-manager.h>
+
 #include <folks/folks.h>
 #include <folks/folks-telepathy.h>
 
-#if HAVE_GEOCLUE
+#ifdef HAVE_GEOCLUE
 #include <geoclue/geoclue-geocode.h>
 #endif
 
 #include "empathy-contact.h"
+#include "empathy-individual-manager.h"
 #include "empathy-utils.h"
 #include "empathy-enum-types.h"
 #include "empathy-marshal.h"
@@ -54,7 +57,6 @@ typedef struct {
   gchar *alias;
   EmpathyAvatar *avatar;
   TpConnectionPresenceType presence;
-  gchar *presence_message;
   guint handle;
   EmpathyCapabilities capabilities;
   gboolean is_user;
@@ -68,6 +70,7 @@ typedef struct {
    */
   GHashTable *location;
   GHashTable *groups;
+  gchar **client_types;
 } EmpathyContactPriv;
 
 static void contact_finalize (GObject *object);
@@ -76,17 +79,24 @@ static void contact_get_property (GObject *object, guint param_id,
 static void contact_set_property (GObject *object, guint param_id,
     const GValue *value, GParamSpec *pspec);
 
-#if HAVE_GEOCLUE
+#ifdef HAVE_GEOCLUE
 static void update_geocode (EmpathyContact *contact);
 #endif
 
 static void empathy_contact_set_location (EmpathyContact *contact,
     GHashTable *location);
 
+static void contact_set_client_types (EmpathyContact *contact,
+    const gchar * const *types);
+
 static void set_capabilities_from_tp_caps (EmpathyContact *self,
     TpCapabilities *caps);
 
+static void contact_set_avatar (EmpathyContact *contact,
+    EmpathyAvatar *avatar);
 static void contact_set_avatar_from_tp_contact (EmpathyContact *contact);
+static gboolean contact_load_avatar_cache (EmpathyContact *contact,
+    const gchar *token);
 
 G_DEFINE_TYPE (EmpathyContact, empathy_contact, G_TYPE_OBJECT);
 
@@ -104,7 +114,8 @@ enum
   PROP_HANDLE,
   PROP_CAPABILITIES,
   PROP_IS_USER,
-  PROP_LOCATION
+  PROP_LOCATION,
+  PROP_CLIENT_TYPES
 };
 
 enum {
@@ -136,8 +147,6 @@ tp_contact_notify_cb (TpContact *tp_contact,
     priv->presence = presence;
     g_object_notify (contact, "presence");
   }
-  else if (!tp_strdiff (param->name, "presence-message"))
-    g_object_notify (contact, "presence-message");
   else if (!tp_strdiff (param->name, "identifier"))
     g_object_notify (contact, "id");
   else if (!tp_strdiff (param->name, "handle"))
@@ -159,6 +168,20 @@ tp_contact_notify_cb (TpContact *tp_contact,
     {
       contact_set_avatar_from_tp_contact (EMPATHY_CONTACT (contact));
     }
+  else if (!tp_strdiff (param->name, "client-types"))
+    {
+      contact_set_client_types (EMPATHY_CONTACT (contact),
+          tp_contact_get_client_types (tp_contact));
+    }
+}
+
+static void
+folks_persona_notify_cb (FolksPersona *folks_persona,
+                         GParamSpec *param,
+                         GObject *contact)
+{
+  if (!tp_strdiff (param->name, "presence-message"))
+    g_object_notify (contact, "presence-message");
 }
 
 static void
@@ -180,7 +203,11 @@ contact_dispose (GObject *object)
   priv->account = NULL;
 
   if (priv->persona)
-    g_object_unref (priv->persona);
+    {
+      g_signal_handlers_disconnect_by_func (priv->persona,
+          folks_persona_notify_cb, object);
+      g_object_unref (priv->persona);
+    }
   priv->persona = NULL;
 
   if (priv->avatar != NULL)
@@ -198,6 +225,46 @@ contact_dispose (GObject *object)
   G_OBJECT_CLASS (empathy_contact_parent_class)->dispose (object);
 }
 
+static void
+contact_constructed (GObject *object)
+{
+  EmpathyContact *contact = (EmpathyContact *) object;
+  EmpathyContactPriv *priv = GET_PRIV (contact);
+  GHashTable *location;
+  TpHandle self_handle;
+  TpHandle handle;
+  const gchar * const *client_types;
+
+  if (priv->tp_contact == NULL)
+    return;
+
+  priv->presence = empathy_contact_get_presence (contact);
+
+  location = tp_contact_get_location (priv->tp_contact);
+  if (location != NULL)
+    empathy_contact_set_location (contact, location);
+
+  client_types = tp_contact_get_client_types (priv->tp_contact);
+  if (client_types != NULL)
+    contact_set_client_types (contact, client_types);
+
+  set_capabilities_from_tp_caps (contact,
+      tp_contact_get_capabilities (priv->tp_contact));
+
+  contact_set_avatar_from_tp_contact (contact);
+
+  /* Set is-user property. Note that it could still be the handle is
+   * different from the connection's self handle, in the case the handle
+   * comes from a group interface. */
+  self_handle = tp_connection_get_self_handle (
+      tp_contact_get_connection (priv->tp_contact));
+  handle = tp_contact_get_handle (priv->tp_contact);
+  empathy_contact_set_is_user (contact, self_handle == handle);
+
+  g_signal_connect (priv->tp_contact, "notify",
+    G_CALLBACK (tp_contact_notify_cb), contact);
+}
+
 static void
 empathy_contact_class_init (EmpathyContactClass *class)
 {
@@ -209,6 +276,7 @@ empathy_contact_class_init (EmpathyContactClass *class)
   object_class->dispose = contact_dispose;
   object_class->get_property = contact_get_property;
   object_class->set_property = contact_set_property;
+  object_class->constructed = contact_constructed;
 
   g_object_class_install_property (object_class,
       PROP_TP_CONTACT,
@@ -256,7 +324,7 @@ empathy_contact_class_init (EmpathyContactClass *class)
         "Avatar image",
         "The avatar image",
         EMPATHY_TYPE_AVATAR,
-        G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
+        G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
 
   g_object_class_install_property (object_class,
       PROP_PRESENCE,
@@ -312,6 +380,14 @@ empathy_contact_class_init (EmpathyContactClass *class)
         G_TYPE_HASH_TABLE,
         G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
 
+  g_object_class_install_property (object_class,
+      PROP_CLIENT_TYPES,
+      g_param_spec_boxed ("client-types",
+        "Contact client types",
+        "Client types of the contact",
+        G_TYPE_STRV,
+        G_PARAM_READABLE | G_PARAM_STATIC_STRINGS));
+
   signals[PRESENCE_CHANGED] =
     g_signal_new ("presence-changed",
                   G_TYPE_FROM_CLASS (class),
@@ -335,6 +411,7 @@ empathy_contact_init (EmpathyContact *contact)
   contact->priv = priv;
 
   priv->location = NULL;
+  priv->client_types = NULL;
   priv->groups = NULL;
 }
 
@@ -351,46 +428,110 @@ contact_finalize (GObject *object)
     g_hash_table_destroy (priv->groups);
   g_free (priv->alias);
   g_free (priv->id);
-  g_free (priv->presence_message);
+  g_strfreev (priv->client_types);
 
   G_OBJECT_CLASS (empathy_contact_parent_class)->finalize (object);
 }
 
 static void
-set_tp_contact (EmpathyContact *contact,
-                TpContact *tp_contact)
+empathy_contact_set_capabilities (EmpathyContact *contact,
+                                  EmpathyCapabilities capabilities)
 {
-  EmpathyContactPriv *priv = GET_PRIV (contact);
-  GHashTable *location;
-  TpHandle self_handle;
-  TpHandle handle;
+  EmpathyContactPriv *priv;
+
+  g_return_if_fail (EMPATHY_IS_CONTACT (contact));
+
+  priv = GET_PRIV (contact);
 
-  if (tp_contact == NULL)
+  if (priv->capabilities == capabilities)
     return;
 
-  g_assert (priv->tp_contact == NULL);
-  priv->tp_contact = g_object_ref (tp_contact);
-  priv->presence = empathy_contact_get_presence (contact);
+  priv->capabilities = capabilities;
 
-  location = tp_contact_get_location (tp_contact);
-  if (location != NULL)
-    empathy_contact_set_location (contact, location);
+  g_object_notify (G_OBJECT (contact), "capabilities");
+}
 
-  set_capabilities_from_tp_caps (contact,
-      tp_contact_get_capabilities (tp_contact));
+static void
+empathy_contact_set_id (EmpathyContact *contact,
+                        const gchar *id)
+{
+  EmpathyContactPriv *priv;
 
-  contact_set_avatar_from_tp_contact (contact);
+  g_return_if_fail (EMPATHY_IS_CONTACT (contact));
+  g_return_if_fail (id != NULL);
 
-  /* Set is-user property. Note that it could still be the handle is
-   * different from the connection's self handle, in the case the handle
-   * comes from a group interface. */
-  self_handle = tp_connection_get_self_handle (
-      tp_contact_get_connection (tp_contact));
-  handle = tp_contact_get_handle (tp_contact);
-  empathy_contact_set_is_user (contact, self_handle == handle);
+  priv = GET_PRIV (contact);
 
-  g_signal_connect (priv->tp_contact, "notify",
-    G_CALLBACK (tp_contact_notify_cb), contact);
+  /* We temporally ref the contact because it could be destroyed
+   * during the signal emition */
+  g_object_ref (contact);
+  if (tp_strdiff (id, priv->id))
+    {
+      g_free (priv->id);
+      priv->id = g_strdup (id);
+
+      g_object_notify (G_OBJECT (contact), "id");
+      if (EMP_STR_EMPTY (priv->alias))
+          g_object_notify (G_OBJECT (contact), "alias");
+    }
+
+  g_object_unref (contact);
+}
+
+static void
+empathy_contact_set_presence (EmpathyContact *contact,
+                              TpConnectionPresenceType presence)
+{
+  EmpathyContactPriv *priv;
+  TpConnectionPresenceType old_presence;
+
+  g_return_if_fail (EMPATHY_IS_CONTACT (contact));
+
+  priv = GET_PRIV (contact);
+
+  if (presence == priv->presence)
+    return;
+
+  old_presence = priv->presence;
+  priv->presence = presence;
+
+  g_signal_emit (contact, signals[PRESENCE_CHANGED], 0, presence, old_presence);
+
+  g_object_notify (G_OBJECT (contact), "presence");
+}
+
+static void
+empathy_contact_set_presence_message (EmpathyContact *contact,
+                                      const gchar *message)
+{
+  EmpathyContactPriv *priv = GET_PRIV (contact);
+
+  g_return_if_fail (EMPATHY_IS_CONTACT (contact));
+
+  if (priv->persona != NULL)
+    {
+      folks_presence_set_presence_message (FOLKS_PRESENCE (priv->persona),
+          message);
+    }
+}
+
+static void
+empathy_contact_set_handle (EmpathyContact *contact,
+                            guint handle)
+{
+  EmpathyContactPriv *priv;
+
+  g_return_if_fail (EMPATHY_IS_CONTACT (contact));
+
+  priv = GET_PRIV (contact);
+
+  g_object_ref (contact);
+  if (handle != priv->handle)
+    {
+      priv->handle = handle;
+      g_object_notify (G_OBJECT (contact), "handle");
+    }
+  g_object_unref (contact);
 }
 
 static void
@@ -454,7 +595,7 @@ contact_set_property (GObject *object,
   switch (param_id)
     {
       case PROP_TP_CONTACT:
-        set_tp_contact (contact, g_value_get_object (value));
+        priv->tp_contact = g_value_dup_object (value);
         break;
       case PROP_ACCOUNT:
         g_assert (priv->account == NULL);
@@ -469,9 +610,6 @@ contact_set_property (GObject *object,
       case PROP_ALIAS:
         empathy_contact_set_alias (contact, g_value_get_string (value));
         break;
-      case PROP_AVATAR:
-        empathy_contact_set_avatar (contact, g_value_get_boxed (value));
-        break;
       case PROP_PRESENCE:
         empathy_contact_set_presence (contact, g_value_get_uint (value));
         break;
@@ -493,7 +631,7 @@ contact_set_property (GObject *object,
     };
 }
 
-EmpathyContact *
+static EmpathyContact *
 empathy_contact_new (TpContact *tp_contact)
 {
   g_return_val_if_fail (TP_IS_CONTACT (tp_contact), NULL);
@@ -522,29 +660,12 @@ empathy_contact_from_tpl_contact (TpAccount *account,
       NULL);
 
   if (!EMP_STR_EMPTY (tpl_entity_get_avatar_token (tpl_entity)))
-    empathy_contact_load_avatar_cache (retval,
+    contact_load_avatar_cache (retval,
         tpl_entity_get_avatar_token (tpl_entity));
 
   return retval;
 }
 
-EmpathyContact *
-empathy_contact_new_for_log (TpAccount *account,
-                             const gchar *id,
-                             const gchar *alias,
-                             gboolean is_user)
-{
-  g_return_val_if_fail (id != NULL, NULL);
-  g_assert (account != NULL);
-
-  return g_object_new (EMPATHY_TYPE_CONTACT,
-      "account", account,
-      "id", id,
-      "alias", alias,
-      "is-user", is_user,
-      NULL);
-}
-
 TpContact *
 empathy_contact_get_tp_contact (EmpathyContact *contact)
 {
@@ -572,33 +693,6 @@ empathy_contact_get_id (EmpathyContact *contact)
   return priv->id;
 }
 
-void
-empathy_contact_set_id (EmpathyContact *contact,
-                        const gchar *id)
-{
-  EmpathyContactPriv *priv;
-
-  g_return_if_fail (EMPATHY_IS_CONTACT (contact));
-  g_return_if_fail (id != NULL);
-
-  priv = GET_PRIV (contact);
-
-  /* We temporally ref the contact because it could be destroyed
-   * during the signal emition */
-  g_object_ref (contact);
-  if (tp_strdiff (id, priv->id))
-    {
-      g_free (priv->id);
-      priv->id = g_strdup (id);
-
-      g_object_notify (G_OBJECT (contact), "id");
-      if (EMP_STR_EMPTY (priv->alias))
-          g_object_notify (G_OBJECT (contact), "alias");
-    }
-
-  g_object_unref (contact);
-}
-
 const gchar *
 empathy_contact_get_alias (EmpathyContact *contact)
 {
@@ -635,12 +729,12 @@ empathy_contact_set_alias (EmpathyContact *contact,
 
   /* Set the alias on the persona if possible */
   persona = empathy_contact_get_persona (contact);
-  if (persona != NULL && FOLKS_IS_ALIAS (persona))
+  if (persona != NULL && FOLKS_IS_ALIASABLE (persona))
     {
       DEBUG ("Setting alias for contact %s to %s",
           empathy_contact_get_id (contact), alias);
 
-      folks_alias_set_alias (FOLKS_ALIAS (persona), alias);
+      folks_aliasable_set_alias (FOLKS_ALIASABLE (persona), alias);
     }
 
   if (tp_strdiff (alias, priv->alias))
@@ -653,6 +747,22 @@ empathy_contact_set_alias (EmpathyContact *contact,
   g_object_unref (contact);
 }
 
+static void
+groups_change_group_cb (GObject *source,
+    GAsyncResult *result,
+    gpointer user_data)
+{
+  FolksGroupable *groupable = FOLKS_GROUPABLE (source);
+  GError *error = NULL;
+
+  folks_groupable_change_group_finish (groupable, result, &error);
+  if (error != NULL)
+    {
+      g_warning ("failed to change group: %s", error->message);
+      g_clear_error (&error);
+    }
+}
+
 void
 empathy_contact_change_group (EmpathyContact *contact, const gchar *group,
     gboolean is_member)
@@ -669,8 +779,9 @@ empathy_contact_change_group (EmpathyContact *contact, const gchar *group,
   persona = empathy_contact_get_persona (contact);
   if (persona != NULL)
     {
-      if (FOLKS_IS_GROUPS (persona))
-        folks_groups_change_group (FOLKS_GROUPS (persona), group, is_member);
+      if (FOLKS_IS_GROUPABLE (persona))
+        folks_groupable_change_group (FOLKS_GROUPABLE (persona), group, is_member,
+          groups_change_group_cb, contact);
       return;
     }
 
@@ -698,9 +809,9 @@ empathy_contact_get_avatar (EmpathyContact *contact)
   return priv->avatar;
 }
 
-void
-empathy_contact_set_avatar (EmpathyContact *contact,
-                            EmpathyAvatar *avatar)
+static void
+contact_set_avatar (EmpathyContact *contact,
+                    EmpathyAvatar *avatar)
 {
   EmpathyContactPriv *priv;
 
@@ -781,7 +892,8 @@ empathy_contact_get_persona (EmpathyContact *contact)
                   if (tp_contact == priv->tp_contact)
                     {
                       /* Found the right persona */
-                      priv->persona = g_object_ref (persona);
+                      empathy_contact_set_persona (contact,
+                          (FolksPersona *) persona);
                       goto finished;
                     }
                 }
@@ -803,7 +915,7 @@ empathy_contact_set_persona (EmpathyContact *contact,
   EmpathyContactPriv *priv;
 
   g_return_if_fail (EMPATHY_IS_CONTACT (contact));
-  g_return_if_fail (FOLKS_IS_PERSONA (persona));
+  g_return_if_fail (TPF_IS_PERSONA (persona));
 
   priv = GET_PRIV (contact);
 
@@ -811,22 +923,28 @@ empathy_contact_set_persona (EmpathyContact *contact,
     return;
 
   if (priv->persona != NULL)
-    g_object_unref (priv->persona);
+    {
+      g_signal_handlers_disconnect_by_func (priv->persona,
+          folks_persona_notify_cb, contact);
+      g_object_unref (priv->persona);
+    }
   priv->persona = g_object_ref (persona);
 
+  g_signal_connect (priv->persona, "notify",
+    G_CALLBACK (folks_persona_notify_cb), contact);
+
   g_object_notify (G_OBJECT (contact), "persona");
 
   /* Set the persona's alias, since ours could've been set using
    * empathy_contact_set_alias() before we had a persona; this happens when
    * adding a contact. */
-  empathy_contact_set_alias (contact, priv->alias);
+  if (priv->alias != NULL)
+    empathy_contact_set_alias (contact, priv->alias);
 
   /* Set the persona's groups */
   if (priv->groups != NULL)
     {
-      if (FOLKS_IS_GROUPS (persona))
-        folks_groups_set_groups (FOLKS_GROUPS (persona), priv->groups);
-
+      folks_groupable_set_groups (FOLKS_GROUPABLE (persona), priv->groups);
       g_hash_table_destroy (priv->groups);
       priv->groups = NULL;
     }
@@ -863,28 +981,6 @@ empathy_contact_get_presence (EmpathyContact *contact)
   return priv->presence;
 }
 
-void
-empathy_contact_set_presence (EmpathyContact *contact,
-                              TpConnectionPresenceType presence)
-{
-  EmpathyContactPriv *priv;
-  TpConnectionPresenceType old_presence;
-
-  g_return_if_fail (EMPATHY_IS_CONTACT (contact));
-
-  priv = GET_PRIV (contact);
-
-  if (presence == priv->presence)
-    return;
-
-  old_presence = priv->presence;
-  priv->presence = presence;
-
-  g_signal_emit (contact, signals[PRESENCE_CHANGED], 0, presence, old_presence);
-
-  g_object_notify (G_OBJECT (contact), "presence");
-}
-
 const gchar *
 empathy_contact_get_presence_message (EmpathyContact *contact)
 {
@@ -894,27 +990,10 @@ empathy_contact_get_presence_message (EmpathyContact *contact)
 
   priv = GET_PRIV (contact);
 
-  if (priv->tp_contact != NULL)
-    return tp_contact_get_presence_message (priv->tp_contact);
-
-  return priv->presence_message;
-}
-
-void
-empathy_contact_set_presence_message (EmpathyContact *contact,
-                                      const gchar *message)
-{
-  EmpathyContactPriv *priv = GET_PRIV (contact);
-
-  g_return_if_fail (EMPATHY_IS_CONTACT (contact));
-
-  if (!tp_strdiff (message, priv->presence_message))
-    return;
-
-  g_free (priv->presence_message);
-  priv->presence_message = g_strdup (message);
+  if (priv->persona != NULL)
+    return folks_presence_get_presence_message (FOLKS_PRESENCE (priv->persona));
 
-  g_object_notify (G_OBJECT (contact), "presence-message");
+  return NULL;
 }
 
 guint
@@ -932,25 +1011,6 @@ empathy_contact_get_handle (EmpathyContact *contact)
   return priv->handle;
 }
 
-void
-empathy_contact_set_handle (EmpathyContact *contact,
-                            guint handle)
-{
-  EmpathyContactPriv *priv;
-
-  g_return_if_fail (EMPATHY_IS_CONTACT (contact));
-
-  priv = GET_PRIV (contact);
-
-  g_object_ref (contact);
-  if (handle != priv->handle)
-    {
-      priv->handle = handle;
-      g_object_notify (G_OBJECT (contact), "handle");
-    }
-  g_object_unref (contact);
-}
-
 EmpathyCapabilities
 empathy_contact_get_capabilities (EmpathyContact *contact)
 {
@@ -963,24 +1023,6 @@ empathy_contact_get_capabilities (EmpathyContact *contact)
   return priv->capabilities;
 }
 
-void
-empathy_contact_set_capabilities (EmpathyContact *contact,
-                                  EmpathyCapabilities capabilities)
-{
-  EmpathyContactPriv *priv;
-
-  g_return_if_fail (EMPATHY_IS_CONTACT (contact));
-
-  priv = GET_PRIV (contact);
-
-  if (priv->capabilities == capabilities)
-    return;
-
-  priv->capabilities = capabilities;
-
-  g_object_notify (G_OBJECT (contact), "capabilities");
-}
-
 gboolean
 empathy_contact_is_user (EmpathyContact *contact)
 {
@@ -1022,6 +1064,14 @@ empathy_contact_is_online (EmpathyContact *contact)
       case TP_CONNECTION_PRESENCE_TYPE_UNKNOWN:
       case TP_CONNECTION_PRESENCE_TYPE_ERROR:
         return FALSE;
+      /* Contacts without presence are considered online so we can display IRC
+       * contacts in rooms. */
+      case TP_CONNECTION_PRESENCE_TYPE_UNSET:
+      case TP_CONNECTION_PRESENCE_TYPE_AVAILABLE:
+      case TP_CONNECTION_PRESENCE_TYPE_AWAY:
+      case TP_CONNECTION_PRESENCE_TYPE_EXTENDED_AWAY:
+      case TP_CONNECTION_PRESENCE_TYPE_HIDDEN:
+      case TP_CONNECTION_PRESENCE_TYPE_BUSY:
       default:
         return TRUE;
     }
@@ -1103,6 +1153,54 @@ empathy_contact_can_use_rfb_stream_tube (EmpathyContact *contact)
   return priv->capabilities & EMPATHY_CAPABILITIES_RFB_STREAM_TUBE;
 }
 
+static gboolean
+contact_has_log (EmpathyContact *contact)
+{
+  TplLogManager *manager;
+  gboolean have_log;
+
+  manager = tpl_log_manager_dup_singleton ();
+  have_log = tpl_log_manager_exists (manager,
+      empathy_contact_get_account (contact), empathy_contact_get_id (contact),
+      FALSE);
+  g_object_unref (manager);
+
+  return have_log;
+}
+
+gboolean
+empathy_contact_can_do_action (EmpathyContact *self,
+    EmpathyActionType action_type)
+{
+  gboolean sensitivity = FALSE;
+
+  switch (action_type)
+    {
+      case EMPATHY_ACTION_CHAT:
+        sensitivity = TRUE;
+        break;
+      case EMPATHY_ACTION_AUDIO_CALL:
+        sensitivity = empathy_contact_can_voip_audio (self);
+        break;
+      case EMPATHY_ACTION_VIDEO_CALL:
+        sensitivity = empathy_contact_can_voip_video (self);
+        break;
+      case EMPATHY_ACTION_VIEW_LOGS:
+        sensitivity = contact_has_log (self);
+        break;
+      case EMPATHY_ACTION_SEND_FILE:
+        sensitivity = empathy_contact_can_send_files (self);
+        break;
+      case EMPATHY_ACTION_SHARE_MY_DESKTOP:
+        sensitivity = empathy_contact_can_use_rfb_stream_tube (self);
+        break;
+      default:
+        g_assert_not_reached ();
+    }
+
+  return (sensitivity ? TRUE : FALSE);
+}
+
 static gchar *
 contact_get_avatar_filename (EmpathyContact *contact,
                              const gchar *token)
@@ -1134,9 +1232,9 @@ contact_get_avatar_filename (EmpathyContact *contact,
   return avatar_file;
 }
 
-gboolean
-empathy_contact_load_avatar_cache (EmpathyContact *contact,
-                                   const gchar *token)
+static gboolean
+contact_load_avatar_cache (EmpathyContact *contact,
+                           const gchar *token)
 {
   EmpathyAvatar *avatar = NULL;
   gchar *filename;
@@ -1162,9 +1260,8 @@ empathy_contact_load_avatar_cache (EmpathyContact *contact,
   if (data)
     {
       DEBUG ("Avatar loaded from %s", filename);
-      avatar = empathy_avatar_new ((guchar *) data, len, NULL, g_strdup (token),
-          filename);
-      empathy_contact_set_avatar (contact, avatar);
+      avatar = empathy_avatar_new ((guchar *) data, len, NULL, filename);
+      contact_set_avatar (contact, avatar);
       empathy_avatar_unref (avatar);
     }
   else
@@ -1195,11 +1292,10 @@ empathy_avatar_get_type (void)
  * @data: the avatar data
  * @len: the size of avatar data
  * @format: the mime type of the avatar image
- * @token: the token of the avatar
  * @filename: the filename where the avatar is stored in cache
  *
  * Create a #EmpathyAvatar from the provided data. This function takes the
- * ownership of @data, @format, @token and @filename.
+ * ownership of @data, @format and @filename.
  *
  * Returns: a new #EmpathyAvatar
  */
@@ -1207,7 +1303,6 @@ EmpathyAvatar *
 empathy_avatar_new (guchar *data,
                     gsize len,
                     gchar *format,
-                    gchar *token,
                     gchar *filename)
 {
   EmpathyAvatar *avatar;
@@ -1216,7 +1311,6 @@ empathy_avatar_new (guchar *data,
   avatar->data = data;
   avatar->len = len;
   avatar->format = format;
-  avatar->token = token;
   avatar->filename = filename;
   avatar->refcount = 1;
 
@@ -1233,7 +1327,6 @@ empathy_avatar_unref (EmpathyAvatar *avatar)
     {
       g_free (avatar->data);
       g_free (avatar->format);
-      g_free (avatar->token);
       g_free (avatar->filename);
       g_slice_free (EmpathyAvatar, avatar);
     }
@@ -1322,12 +1415,37 @@ empathy_contact_set_location (EmpathyContact *contact,
     g_hash_table_unref (priv->location);
 
   priv->location = g_hash_table_ref (location);
-#if HAVE_GEOCLUE
+#ifdef HAVE_GEOCLUE
   update_geocode (contact);
 #endif
   g_object_notify (G_OBJECT (contact), "location");
 }
 
+const gchar * const *
+empathy_contact_get_client_types (EmpathyContact *contact)
+{
+  EmpathyContactPriv *priv;
+
+  g_return_val_if_fail (EMPATHY_IS_CONTACT (contact), NULL);
+
+  priv = GET_PRIV (contact);
+
+  return (const gchar * const *) priv->client_types;
+}
+
+static void
+contact_set_client_types (EmpathyContact *contact,
+    const gchar * const *client_types)
+{
+  EmpathyContactPriv *priv = GET_PRIV (contact);
+
+  if (priv->client_types != NULL)
+    g_strfreev (priv->client_types);
+
+  priv->client_types = g_strdupv ((gchar **) client_types);
+  g_object_notify (G_OBJECT (contact), "client-types");
+}
+
 /**
  * empathy_contact_equal:
  * @contact1: an #EmpathyContact
@@ -1365,7 +1483,7 @@ empathy_contact_equal (gconstpointer contact1,
   return FALSE;
 }
 
-#if HAVE_GEOCLUE
+#ifdef HAVE_GEOCLUE
 #define GEOCODE_SERVICE "org.freedesktop.Geoclue.Providers.Yahoo"
 #define GEOCODE_PATH "/org/freedesktop/Geoclue/Providers/Yahoo"
 
@@ -1617,10 +1735,8 @@ contact_set_avatar_from_tp_contact (EmpathyContact *contact)
 {
   EmpathyContactPriv *priv = GET_PRIV (contact);
   const gchar *mime;
-  const gchar *token;
   GFile *file;
 
-  token = tp_contact_get_avatar_token (priv->tp_contact);
   mime = tp_contact_get_avatar_mime_type (priv->tp_contact);
   file = tp_contact_get_avatar_file (priv->tp_contact);
 
@@ -1631,14 +1747,14 @@ contact_set_avatar_from_tp_contact (EmpathyContact *contact)
       gsize len;
 
       g_file_load_contents (file, NULL, &data, &len, NULL, NULL);
-      avatar = empathy_avatar_new ((guchar *) data, len, g_strdup (mime), g_strdup (token),
+      avatar = empathy_avatar_new ((guchar *) data, len, g_strdup (mime),
           g_file_get_path (file));
-      empathy_contact_set_avatar (contact, avatar);
+      contact_set_avatar (contact, avatar);
       empathy_avatar_unref (avatar);
     }
   else
     {
-      empathy_contact_set_avatar (contact, NULL);
+      contact_set_avatar (contact, NULL);
     }
 }
 
@@ -1671,3 +1787,200 @@ empathy_contact_dup_from_tp_contact (TpContact *tp_contact)
   return contact;
 }
 
+static int
+presence_cmp_func (EmpathyContact *a,
+    EmpathyContact *b)
+{
+  FolksPresence *presence_a, *presence_b;
+
+  presence_a = FOLKS_PRESENCE (empathy_contact_get_persona (a));
+  presence_b = FOLKS_PRESENCE (empathy_contact_get_persona (b));
+
+  /* We negate the result because we're sorting in reverse order (i.e. such that
+   * the Personas with the highest presence are at the beginning of the list. */
+  return -folks_presence_typecmp (folks_presence_get_presence_type (presence_a),
+      folks_presence_get_presence_type (presence_b));
+}
+
+static gint
+voip_cmp_func (EmpathyContact *a,
+    EmpathyContact *b)
+{
+  gboolean has_audio_a, has_audio_b;
+  gboolean has_video_a, has_video_b;
+
+  has_audio_a = empathy_contact_can_voip_audio (a);
+  has_audio_b = empathy_contact_can_voip_audio (b);
+  has_video_a = empathy_contact_can_voip_video (a);
+  has_video_b = empathy_contact_can_voip_video (b);
+
+  /* First check video */
+  if (has_video_a == has_video_b)
+    {
+      /* Use audio to to break tie */
+      if (has_audio_a == has_audio_b)
+        return 0;
+      else if (has_audio_a)
+        return -1;
+      else
+        return 1;
+    }
+  else if (has_video_a)
+    return -1;
+  else
+    return 1;
+}
+
+static gint
+ft_cmp_func (EmpathyContact *a,
+    EmpathyContact *b)
+{
+  gboolean can_send_files_a, can_send_files_b;
+
+  can_send_files_a = empathy_contact_can_send_files (a);
+  can_send_files_b = empathy_contact_can_send_files (b);
+
+  if (can_send_files_a == can_send_files_b)
+    return 0;
+  else if (can_send_files_a)
+    return -1;
+  else
+    return 1;
+}
+
+static gint
+rfb_stream_tube_cmp_func (EmpathyContact *a,
+    EmpathyContact *b)
+{
+  gboolean rfb_a, rfb_b;
+
+  rfb_a = empathy_contact_can_use_rfb_stream_tube (a);
+  rfb_b = empathy_contact_can_use_rfb_stream_tube (b);
+
+  if (rfb_a == rfb_b)
+    return 0;
+  else if (rfb_a)
+    return -1;
+  else
+    return 1;
+}
+
+/* Sort by presence as with presence_cmp_func(), but if the two contacts have
+ * the same presence, prefer the one which can do both audio *and* video calls,
+ * over the one which can only do one of the two. */
+static int
+voip_sort_func (EmpathyContact *a, EmpathyContact *b)
+{
+  gint presence_sort = presence_cmp_func (a, b);
+
+  if (presence_sort != 0)
+    return presence_sort;
+
+  return voip_cmp_func (a, b);
+}
+
+/* Sort by presence as with presence_cmp_func() and then break ties using the
+ * most "capable" individual. So users will be able to pick more actions on
+ * the contact in the "Contact" menu of the chat window. */
+static gint
+chat_sort_func (EmpathyContact *a,
+    EmpathyContact *b)
+{
+  gint result;
+
+  result = presence_cmp_func (a, b);
+  if (result != 0)
+    return result;
+
+  /* Prefer individual supporting file transfer */
+  result = ft_cmp_func (a, b);
+  if (result != 0)
+    return result;
+
+  /* Check audio/video capabilities */
+  result = voip_cmp_func (a, b);
+  if (result != 0)
+    return result;
+
+  /* Check 'Share my destop' feature */
+  return rfb_stream_tube_cmp_func (a, b);
+}
+
+static GCompareFunc
+get_sort_func_for_action (EmpathyActionType action_type)
+{
+  switch (action_type)
+    {
+      case EMPATHY_ACTION_AUDIO_CALL:
+      case EMPATHY_ACTION_VIDEO_CALL:
+        return (GCompareFunc) voip_sort_func;
+      case EMPATHY_ACTION_CHAT:
+        return (GCompareFunc) chat_sort_func;
+      case EMPATHY_ACTION_VIEW_LOGS:
+      case EMPATHY_ACTION_SEND_FILE:
+      case EMPATHY_ACTION_SHARE_MY_DESKTOP:
+      default:
+        return (GCompareFunc) presence_cmp_func;
+    }
+}
+
+/**
+ * empathy_contact_dup_best_for_action:
+ * @individual: a #FolksIndividual
+ * @action_type: the type of action to be performed on the contact
+ *
+ * Chooses a #FolksPersona from the given @individual which is best-suited for
+ * the given @action_type. "Best-suited" is determined by choosing the persona
+ * with the highest presence out of all the personas which can perform the given
+ * @action_type (e.g. are capable of video calling).
+ *
+ * Return value: an #EmpathyContact for the best persona, or %NULL;
+ * unref with g_object_unref()
+ */
+EmpathyContact *
+empathy_contact_dup_best_for_action (FolksIndividual *individual,
+    EmpathyActionType action_type)
+{
+  GList *personas, *contacts, *l;
+  EmpathyContact *best_contact = NULL;
+
+  /* Build a list of EmpathyContacts that we can sort */
+  personas = folks_individual_get_personas (individual);
+  contacts = NULL;
+
+  for (l = personas; l != NULL; l = l->next)
+    {
+      TpContact *tp_contact;
+      EmpathyContact *contact;
+
+      if (!TPF_IS_PERSONA (l->data))
+        continue;
+
+      tp_contact = tpf_persona_get_contact (TPF_PERSONA (l->data));
+      contact = empathy_contact_dup_from_tp_contact (tp_contact);
+      empathy_contact_set_persona (contact, FOLKS_PERSONA (l->data));
+
+      /* Only choose the contact if they're actually capable of the specified
+       * action. */
+      if (!empathy_contact_can_do_action (contact, action_type))
+        {
+          g_object_unref (contact);
+          continue;
+        }
+
+      contacts = g_list_prepend (contacts, contact);
+    }
+
+  /* Sort the contacts by some heuristic based on the action type, then take
+   * the top contact. */
+  if (contacts != NULL)
+    {
+      contacts = g_list_sort (contacts, get_sort_func_for_action (action_type));
+      best_contact = g_object_ref (contacts->data);
+    }
+
+  g_list_foreach (contacts, (GFunc) g_object_unref, NULL);
+  g_list_free (contacts);
+
+  return best_contact;
+}