]> git.0d.be Git - empathy.git/blob - src/empathy-call-observer.c
Merge branch 'speedup-protocol-chooser'
[empathy.git] / src / empathy-call-observer.c
1 /*
2  * Copyright (C) 2011 Collabora Ltd.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17  *
18  * Authors: Emilio Pozuelo Monfort <emilio.pozuelo@collabora.co.uk>
19  */
20
21 #include <config.h>
22
23 #include <glib/gi18n-lib.h>
24
25 #include <telepathy-glib/telepathy-glib.h>
26
27 #include <libnotify/notification.h>
28
29 #include <libempathy/empathy-channel-factory.h>
30 #include <libempathy/empathy-utils.h>
31
32 #include <libempathy-gtk/empathy-images.h>
33 #include <libempathy-gtk/empathy-notify-manager.h>
34
35 #include <extensions/extensions.h>
36
37 #include "empathy-call-observer.h"
38
39 #define DEBUG_FLAG EMPATHY_DEBUG_VOIP
40 #include <libempathy/empathy-debug.h>
41
42 struct _EmpathyCallObserverPriv {
43   EmpathyNotifyManager *notify_mgr;
44
45   TpBaseClient *observer;
46
47   /* Ongoing calls, as reffed TpChannels */
48   GList *channels;
49 };
50
51 /* The Call Observer looks at incoming and outgoing calls, and
52  * autorejects incoming ones if there are ongoing ones, since
53  * we don't cope with simultaneous calls quite well yet.
54  * At some point, we should ask the user if he wants to put the
55  * current call on hold and answer the incoming one instead,
56  * see https://bugzilla.gnome.org/show_bug.cgi?id=623348
57  */
58 G_DEFINE_TYPE (EmpathyCallObserver, empathy_call_observer, G_TYPE_OBJECT);
59
60 static EmpathyCallObserver * observer_singleton = NULL;
61
62 static void
63 on_channel_closed (TpProxy *proxy,
64     guint    domain,
65     gint     code,
66     gchar   *message,
67     EmpathyCallObserver *self)
68 {
69   DEBUG ("channel %s has been invalidated; stop observing it",
70       tp_proxy_get_object_path (proxy));
71
72   self->priv->channels = g_list_remove (self->priv->channels, proxy);
73   g_object_unref (proxy);
74 }
75
76 typedef struct
77 {
78   EmpathyCallObserver *self;
79   TpObserveChannelsContext *context;
80   TpChannel *main_channel;
81 } AutoRejectCtx;
82
83 static AutoRejectCtx *
84 auto_reject_ctx_new (EmpathyCallObserver *self,
85     TpObserveChannelsContext *context,
86     TpChannel *main_channel)
87 {
88   AutoRejectCtx *ctx = g_slice_new (AutoRejectCtx);
89
90   ctx->self = g_object_ref (self);
91   ctx->context = g_object_ref (context);
92   ctx->main_channel = g_object_ref (main_channel);
93   return ctx;
94 }
95
96 static void
97 auto_reject_ctx_free (AutoRejectCtx *ctx)
98 {
99   g_object_unref (ctx->self);
100   g_object_unref (ctx->context);
101   g_object_unref (ctx->main_channel);
102   g_slice_free (AutoRejectCtx, ctx);
103 }
104
105 static void
106 get_contact_cb (TpConnection *connection,
107     guint n_contacts,
108     TpContact * const *contacts,
109     guint n_failed,
110     const TpHandle *failed,
111     const GError *error,
112     gpointer user_data,
113     GObject *weak_object)
114 {
115   EmpathyCallObserver *self = (EmpathyCallObserver *) weak_object;
116   NotifyNotification *notification;
117   TpContact *contact;
118   gchar *summary, *body;
119   EmpathyContact *emp_contact;
120   GdkPixbuf *pixbuf;
121
122   if (n_contacts != 1)
123     {
124       DEBUG ("Failed to get TpContact; ignoring notification bubble");
125       return;
126     }
127
128   contact = contacts[0];
129
130   summary = g_strdup_printf (_("Missed call from %s"),
131       tp_contact_get_alias (contact));
132   body = g_strdup_printf (
133       _("%s just tried to call you, but you were in another call."),
134       tp_contact_get_alias (contact));
135
136   notification = notify_notification_new (summary, body, NULL);
137
138   emp_contact = empathy_contact_dup_from_tp_contact (contact);
139   pixbuf = empathy_notify_manager_get_pixbuf_for_notification (
140       self->priv->notify_mgr, emp_contact, EMPATHY_IMAGE_AVATAR_DEFAULT);
141
142   if (pixbuf != NULL)
143     {
144       notify_notification_set_icon_from_pixbuf (notification, pixbuf);
145       g_object_unref (pixbuf);
146     }
147
148   notify_notification_show (notification, NULL);
149
150   g_object_unref (notification);
151   g_free (summary);
152   g_free (body);
153   g_object_unref (emp_contact);
154 }
155
156 static void
157 display_reject_notification (EmpathyCallObserver *self,
158     TpChannel *channel)
159 {
160   TpHandle handle;
161   TpContactFeature features[] = { TP_CONTACT_FEATURE_ALIAS,
162       TP_CONTACT_FEATURE_AVATAR_DATA };
163
164   handle = tp_channel_get_handle (channel, NULL);
165
166   tp_connection_get_contacts_by_handle (tp_channel_borrow_connection (channel),
167       1, &handle, G_N_ELEMENTS (features), features, get_contact_cb,
168       g_object_ref (channel), g_object_unref, G_OBJECT (self));
169 }
170
171 static void
172 on_cdo_claim_cb (GObject *source_object,
173     GAsyncResult *result,
174     gpointer user_data)
175 {
176   AutoRejectCtx *ctx = user_data;
177   TpChannelDispatchOperation *cdo;
178   GError *error = NULL;
179   GPtrArray *channels;
180   guint i;
181
182   cdo = TP_CHANNEL_DISPATCH_OPERATION (source_object);
183
184   tp_channel_dispatch_operation_claim_with_finish (cdo, result, &error);
185   if (error != NULL)
186     {
187       DEBUG ("Could not claim CDO: %s", error->message);
188       g_error_free (error);
189       goto out;
190     }
191
192   channels = tp_channel_dispatch_operation_borrow_channels (cdo);
193   for (i = 0; i < channels->len; i++)
194     {
195       TpChannel *channel = g_ptr_array_index (channels, i);
196
197       tp_channel_leave_async (channel,
198           TP_CHANNEL_GROUP_CHANGE_REASON_BUSY, "Already in a call",
199           NULL, NULL);
200     }
201
202   display_reject_notification (ctx->self, ctx->main_channel);
203
204 out:
205   auto_reject_ctx_free (ctx);
206 }
207
208 static void
209 cdo_prepare_cb (GObject *source_object,
210     GAsyncResult *result,
211     gpointer user_data)
212 {
213   AutoRejectCtx *ctx = user_data;
214   GError *error = NULL;
215   TpChannelDispatchOperation *cdo;
216
217   cdo = TP_CHANNEL_DISPATCH_OPERATION (source_object);
218
219   if (!tp_proxy_prepare_finish (source_object, result, &error))
220     {
221       DEBUG ("Failed to prepare ChannelDispatchOperation: %s", error->message);
222
223       tp_observe_channels_context_fail (ctx->context, error);
224
225       g_error_free (error);
226       auto_reject_ctx_free (ctx);
227       return;
228     }
229
230   tp_channel_dispatch_operation_claim_with_async (cdo,
231       ctx->self->priv->observer, on_cdo_claim_cb, ctx);
232
233   tp_observe_channels_context_accept (ctx->context);
234 }
235
236 static TpChannel *
237 find_main_channel (GList *channels)
238 {
239   GList *l;
240
241   for (l = channels; l != NULL; l = g_list_next (l))
242     {
243       TpChannel *channel = l->data;
244       GQuark channel_type;
245
246       if (tp_proxy_get_invalidated (channel) != NULL)
247         continue;
248
249       channel_type = tp_channel_get_channel_type_id (channel);
250
251       if (channel_type == TP_IFACE_QUARK_CHANNEL_TYPE_STREAMED_MEDIA)
252         return channel;
253     }
254
255   return NULL;
256 }
257
258 static gboolean
259 has_ongoing_calls (EmpathyCallObserver *self)
260 {
261   if (self->priv->channels != NULL)
262     return TRUE;
263
264   return FALSE;
265 }
266
267 static void
268 observe_channels (TpSimpleObserver *observer,
269     TpAccount *account,
270     TpConnection *connection,
271     GList *channels,
272     TpChannelDispatchOperation *dispatch_operation,
273     GList *requests,
274     TpObserveChannelsContext *context,
275     gpointer user_data)
276 {
277   EmpathyCallObserver *self = EMPATHY_CALL_OBSERVER (user_data);
278   TpChannel *channel;
279   const GError *error;
280
281   channel = find_main_channel (channels);
282   if (channel == NULL)
283     {
284       GError err = { TP_ERRORS, TP_ERROR_INVALID_ARGUMENT,
285           "Unknown channel type" };
286
287       DEBUG ("Didn't find any Call or StreamedMedia channel; ignoring");
288
289       tp_observe_channels_context_fail (context, &err);
290       return;
291     }
292
293   /* Autoreject if there are other ongoing calls */
294   if (has_ongoing_calls (self))
295     {
296       AutoRejectCtx *ctx = auto_reject_ctx_new (self, context, channel);
297       GQuark features[] = { TP_CHANNEL_DISPATCH_OPERATION_FEATURE_CORE, 0 };
298
299       DEBUG ("Autorejecting incoming call since there are others in "
300           "progress: %s", tp_proxy_get_object_path (channel));
301
302       /* We have to prepare dispatch_operation so we'll be able to get the
303        * channels from it. */
304       tp_proxy_prepare_async (dispatch_operation, features,
305           cdo_prepare_cb, ctx);
306
307       tp_observe_channels_context_delay (context);
308       return;
309     }
310
311   if ((error = tp_proxy_get_invalidated (channel)) != NULL)
312     {
313       DEBUG ("The channel has already been invalidated: %s",
314           error->message);
315
316       tp_observe_channels_context_fail (context, error);
317       return;
318     }
319
320   DEBUG ("Observing channel %s", tp_proxy_get_object_path (channel));
321
322   tp_g_signal_connect_object (channel, "invalidated",
323       G_CALLBACK (on_channel_closed), self, 0);
324   self->priv->channels = g_list_prepend (self->priv->channels,
325       g_object_ref (channel));
326
327   tp_observe_channels_context_accept (context);
328 }
329
330 static GObject *
331 observer_constructor (GType type,
332     guint n_props,
333     GObjectConstructParam *props)
334 {
335   GObject *retval;
336
337   if (observer_singleton)
338     {
339       retval = g_object_ref (observer_singleton);
340     }
341   else
342     {
343       retval = G_OBJECT_CLASS (empathy_call_observer_parent_class)->constructor
344           (type, n_props, props);
345
346       observer_singleton = EMPATHY_CALL_OBSERVER (retval);
347       g_object_add_weak_pointer (retval, (gpointer) &observer_singleton);
348     }
349
350   return retval;
351 }
352
353 static void
354 observer_dispose (GObject *object)
355 {
356   EmpathyCallObserver *self = EMPATHY_CALL_OBSERVER (object);
357
358   tp_clear_object (&self->priv->notify_mgr);
359   tp_clear_object (&self->priv->observer);
360   g_list_free_full (self->priv->channels, g_object_unref);
361   self->priv->channels = NULL;
362 }
363
364 static void
365 empathy_call_observer_class_init (EmpathyCallObserverClass *klass)
366 {
367   GObjectClass *object_class = G_OBJECT_CLASS (klass);
368
369   object_class->dispose = observer_dispose;
370   object_class->constructor = observer_constructor;
371
372   g_type_class_add_private (object_class, sizeof (EmpathyCallObserverPriv));
373 }
374
375 static void
376 empathy_call_observer_init (EmpathyCallObserver *self)
377 {
378   EmpathyCallObserverPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
379     EMPATHY_TYPE_CALL_OBSERVER, EmpathyCallObserverPriv);
380   TpDBusDaemon *dbus;
381   EmpathyChannelFactory *factory;
382   GError *error = NULL;
383
384   self->priv = priv;
385
386   self->priv->notify_mgr = empathy_notify_manager_dup_singleton ();
387
388   dbus = tp_dbus_daemon_dup (&error);
389   if (dbus == NULL)
390     {
391       DEBUG ("Failed to get TpDBusDaemon: %s", error->message);
392       g_error_free (error);
393       return;
394     }
395
396   self->priv->observer = tp_simple_observer_new (dbus, TRUE,
397       "Empathy.CallObserver", FALSE,
398       observe_channels, self, NULL);
399
400   factory = empathy_channel_factory_dup ();
401   tp_base_client_set_channel_factory (self->priv->observer,
402       TP_CLIENT_CHANNEL_FACTORY (factory));
403   g_object_unref (factory);
404
405   /* Observe StreamedMedia channels */
406   tp_base_client_take_observer_filter (self->priv->observer,
407       tp_asv_new (
408         TP_PROP_CHANNEL_CHANNEL_TYPE, G_TYPE_STRING,
409           TP_IFACE_CHANNEL_TYPE_STREAMED_MEDIA,
410         TP_PROP_CHANNEL_TARGET_HANDLE_TYPE, G_TYPE_UINT,
411           TP_HANDLE_TYPE_CONTACT,
412         NULL));
413
414   tp_base_client_set_observer_delay_approvers (self->priv->observer, TRUE);
415
416   if (!tp_base_client_register (self->priv->observer, &error))
417     {
418       DEBUG ("Failed to register observer: %s", error->message);
419       g_error_free (error);
420     }
421
422   g_object_unref (dbus);
423 }
424
425 EmpathyCallObserver *
426 empathy_call_observer_dup_singleton (void)
427 {
428   return g_object_new (EMPATHY_TYPE_CALL_OBSERVER, NULL);
429 }