]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-location-manager.c
Get rid of Mission Control as EmpathyAccountManager does all that too
[empathy.git] / libempathy-gtk / empathy-location-manager.c
1 /*
2  * Copyright (C) 2009 Collabora Ltd.
3  *
4  * This program is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU General Public License as
6  * published by the Free Software Foundation; either version 2 of the
7  * License, or (at your option) any later version.
8  *
9  * This program 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  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public
15  * License along with this program; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17  * Boston, MA  02110-1301  USA
18  *
19  * Authors: Pierre-Luc Beaudoin <pierre-luc.beaudoin@collabora.co.uk>
20  */
21
22 #include "config.h"
23
24 #include <string.h>
25 #include <time.h>
26
27 #include <glib/gi18n.h>
28
29 #include <telepathy-glib/util.h>
30
31 #include <geoclue/geoclue-master.h>
32
33 #include <extensions/extensions.h>
34
35 #include "empathy-location-manager.h"
36 #include "empathy-conf.h"
37
38 #include "libempathy/empathy-account-manager.h"
39 #include "libempathy/empathy-enum-types.h"
40 #include "libempathy/empathy-location.h"
41 #include "libempathy/empathy-tp-contact-factory.h"
42 #include "libempathy/empathy-utils.h"
43
44 #define DEBUG_FLAG EMPATHY_DEBUG_LOCATION
45 #include "libempathy/empathy-debug.h"
46
47 /* Seconds before updating the location */
48 #define TIMEOUT 10
49 static EmpathyLocationManager *location_manager = NULL;
50
51 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyLocationManager)
52 typedef struct {
53     gboolean geoclue_is_setup;
54     /* Contains the location to be sent to accounts.  Geoclue is used
55      * to populate it.  This HashTable uses Telepathy's style (string,
56      * GValue). Keys are defined in empathy-location.h
57      */
58     GHashTable *location;
59     gpointer token;
60
61     GeoclueResourceFlags resources;
62     GeoclueMasterClient *gc_client;
63     GeocluePosition *gc_position;
64     GeoclueAddress *gc_address;
65
66     gboolean reduce_accuracy;
67     gdouble reduce_value;
68     EmpathyAccountManager *account_manager;
69
70     /* The idle id for publish_on_idle func */
71     guint timeout_id;
72 } EmpathyLocationManagerPriv;
73
74 G_DEFINE_TYPE (EmpathyLocationManager, empathy_location_manager, G_TYPE_OBJECT);
75
76 static GObject *
77 location_manager_constructor (GType type,
78     guint n_construct_params,
79     GObjectConstructParam *construct_params)
80 {
81   GObject *retval;
82
83   if (location_manager == NULL)
84     {
85       retval = G_OBJECT_CLASS (empathy_location_manager_parent_class)->constructor
86           (type, n_construct_params, construct_params);
87
88       location_manager = EMPATHY_LOCATION_MANAGER (retval);
89       g_object_add_weak_pointer (retval, (gpointer) &location_manager);
90     }
91   else
92     {
93       retval = g_object_ref (location_manager);
94     }
95
96   return retval;
97 }
98
99 static void
100 location_manager_dispose (GObject *object)
101 {
102   EmpathyLocationManagerPriv *priv = GET_PRIV (object);
103
104   if (priv->account_manager != NULL)
105   {
106     g_object_unref (priv->account_manager);
107     priv->account_manager = NULL;
108   }
109
110   if (priv->gc_client != NULL)
111   {
112     g_object_unref (priv->gc_client);
113     priv->gc_client = NULL;
114   }
115
116   if (priv->gc_position != NULL)
117   {
118     g_object_unref (priv->gc_position);
119     priv->gc_position = NULL;
120   }
121
122   if (priv->gc_address != NULL)
123   {
124     g_object_unref (priv->gc_address);
125     priv->gc_address = NULL;
126   }
127
128   if (priv->location != NULL)
129   {
130     g_hash_table_unref (priv->location);
131     priv->location = NULL;
132   }
133
134   G_OBJECT_CLASS (empathy_location_manager_parent_class)->finalize (object);
135 }
136
137 static void
138 location_manager_get_property (GObject *object,
139                       guint param_id,
140                       GValue *value,
141                       GParamSpec *pspec)
142 {
143   /*EmpathyLocationManagerPriv *priv = GET_PRIV (object); */
144
145   switch (param_id)
146     {
147       default:
148         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
149         break;
150     };
151 }
152
153 static void
154 location_manager_set_property (GObject *object,
155                       guint param_id,
156                       const GValue *value,
157                       GParamSpec *pspec)
158 {
159   /* EmpathyLocationManagerPriv *priv = GET_PRIV (object); */
160
161   switch (param_id)
162     {
163       default:
164         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
165         break;
166     };
167 }
168
169 static void
170 empathy_location_manager_class_init (EmpathyLocationManagerClass *class)
171 {
172   GObjectClass *object_class;
173
174   object_class = G_OBJECT_CLASS (class);
175
176   object_class->constructor = location_manager_constructor;
177   object_class->dispose = location_manager_dispose;
178   object_class->get_property = location_manager_get_property;
179   object_class->set_property = location_manager_set_property;
180
181   g_type_class_add_private (object_class, sizeof (EmpathyLocationManagerPriv));
182 }
183
184 static void
185 publish_location (EmpathyLocationManager *location_manager,
186     McAccount *account,
187     gboolean force_publication)
188 {
189   EmpathyLocationManagerPriv *priv = GET_PRIV (location_manager);
190   guint connection_status = -1;
191   gboolean can_publish;
192   TpConnection *conn;
193   EmpathyConf *conf = empathy_conf_get ();
194   EmpathyTpContactFactory *factory;
195
196   conn = empathy_account_manager_get_connection (priv->account_manager,
197       account);
198   if (!conn)
199     return;
200
201   if (force_publication == FALSE)
202     {
203       if (!empathy_conf_get_bool (conf, EMPATHY_PREFS_LOCATION_PUBLISH,
204             &can_publish))
205         return;
206
207       if (can_publish == FALSE)
208         return;
209     }
210
211   connection_status = tp_connection_get_status (conn, NULL);
212
213   if (connection_status != TP_CONNECTION_STATUS_CONNECTED)
214     return;
215
216   DEBUG ("Publishing %s location to connection %p",
217       (g_hash_table_size (priv->location) == 0 ? "empty" : ""),
218       conn);
219
220   factory = empathy_tp_contact_factory_dup_singleton (conn);
221   empathy_tp_contact_factory_set_location (factory, priv->location);
222   g_object_unref (factory);
223 }
224
225 static void
226 publish_to_all_accounts (EmpathyLocationManager *location_manager,
227                                   gboolean force_publication)
228 {
229   GList *accounts = NULL, *l;
230
231   accounts = mc_accounts_list_by_enabled (TRUE);
232   for (l = accounts; l; l = l->next)
233     {
234       publish_location (location_manager, l->data, force_publication);
235     }
236
237   mc_accounts_list_free (accounts);
238 }
239
240 static gboolean
241 publish_on_idle (gpointer user_data)
242 {
243   EmpathyLocationManager *manager = EMPATHY_LOCATION_MANAGER (user_data);
244   EmpathyLocationManagerPriv *priv = GET_PRIV (manager);
245
246   priv->timeout_id = 0;
247   publish_to_all_accounts (manager, TRUE);
248   return FALSE;
249 }
250
251 static void
252 account_connection_changed_cb (EmpathyAccountManager *manager,
253                                McAccount *account,
254                                TpConnectionStatusReason reason,
255                                TpConnectionStatus current,
256                                TpConnectionStatus previous,
257                                gpointer *location_manager)
258 {
259   DEBUG ("Account %s changed status from %d to %d", mc_account_get_display_name (account),
260       previous, current);
261
262   if (account && current == TP_CONNECTION_STATUS_CONNECTED)
263     publish_location (EMPATHY_LOCATION_MANAGER (location_manager), account,
264         FALSE);
265 }
266
267 static void
268 update_timestamp (EmpathyLocationManager *location_manager)
269 {
270   EmpathyLocationManagerPriv *priv= GET_PRIV (location_manager);
271   GValue *new_value;
272   gint64 stamp64;
273   time_t timestamp;
274
275   timestamp = time (NULL);
276   stamp64 = (gint64) timestamp;
277   new_value = tp_g_value_slice_new_int64 (stamp64);
278   g_hash_table_insert (priv->location, g_strdup (EMPATHY_LOCATION_TIMESTAMP),
279       new_value);
280   DEBUG ("\t - Timestamp: %" G_GINT64_FORMAT, stamp64);
281 }
282
283 static void
284 address_changed_cb (GeoclueAddress *address,
285                     int timestamp,
286                     GHashTable *details,
287                     GeoclueAccuracy *accuracy,
288                     gpointer location_manager)
289 {
290   GeoclueAccuracyLevel level;
291   EmpathyLocationManagerPriv *priv = GET_PRIV (location_manager);
292   GHashTableIter iter;
293   gpointer key, value;
294
295   DEBUG ("New address (accuracy level %d):", level);
296
297   geoclue_accuracy_get_details (accuracy, &level, NULL, NULL);
298   g_hash_table_remove_all (priv->location);
299
300   if (g_hash_table_size (details) == 0)
301     return;
302
303   g_hash_table_iter_init (&iter, details);
304   while (g_hash_table_iter_next (&iter, &key, &value))
305     {
306       GValue *new_value;
307       /* Discard street information if reduced accuracy is on */
308       if (priv->reduce_accuracy && strcmp (key, EMPATHY_LOCATION_STREET) == 0)
309         continue;
310
311       new_value = tp_g_value_slice_new_string (value);
312       g_hash_table_insert (priv->location, g_strdup (key), new_value);
313
314       DEBUG ("\t - %s: %s", (gchar *) key, (gchar *) value);
315     }
316
317   update_timestamp (location_manager);
318   if (priv->timeout_id == 0)
319     priv->timeout_id = g_timeout_add_seconds (TIMEOUT, publish_on_idle, location_manager);
320 }
321
322 static void
323 initial_address_cb (GeoclueAddress *address,
324                     int timestamp,
325                     GHashTable *details,
326                     GeoclueAccuracy *accuracy,
327                     GError *error,
328                     gpointer location_manager)
329 {
330   if (error)
331     {
332       DEBUG ("Error: %s", error->message);
333       g_error_free (error);
334     }
335   else
336     {
337       address_changed_cb (address, timestamp, details, accuracy, location_manager);
338     }
339 }
340
341 static void
342 position_changed_cb (GeocluePosition *position,
343                      GeocluePositionFields fields,
344                      int timestamp,
345                      double latitude,
346                      double longitude,
347                      double altitude,
348                      GeoclueAccuracy *accuracy,
349                      gpointer location_manager)
350 {
351   EmpathyLocationManagerPriv *priv = GET_PRIV (location_manager);
352   GeoclueAccuracyLevel level;
353   gdouble mean, horizontal, vertical;
354   GValue *new_value;
355
356   geoclue_accuracy_get_details (accuracy, &level, &horizontal, &vertical);
357   DEBUG ("New position (accuracy level %d)", level);
358   if (level == GEOCLUE_ACCURACY_LEVEL_NONE)
359     return;
360
361   if (fields & GEOCLUE_POSITION_FIELDS_LONGITUDE)
362     {
363       longitude += priv->reduce_value;
364       new_value = tp_g_value_slice_new_double (longitude);
365       g_hash_table_insert (priv->location, g_strdup (EMPATHY_LOCATION_LON),
366           new_value);
367       DEBUG ("\t - Longitude: %f", longitude);
368     }
369   if (fields & GEOCLUE_POSITION_FIELDS_LATITUDE)
370     {
371       latitude += priv->reduce_value;
372       new_value = tp_g_value_slice_new_double (latitude);
373       g_hash_table_insert (priv->location, g_strdup (EMPATHY_LOCATION_LAT),
374           new_value);
375       DEBUG ("\t - Latitude: %f", latitude);
376     }
377   if (fields & GEOCLUE_POSITION_FIELDS_ALTITUDE)
378     {
379       new_value = tp_g_value_slice_new_double (altitude);
380       g_hash_table_insert (priv->location, g_strdup (EMPATHY_LOCATION_ALT),
381           new_value);
382       DEBUG ("\t - Altitude: %f", altitude);
383     }
384
385   if (level == GEOCLUE_ACCURACY_LEVEL_DETAILED)
386     {
387       mean = (horizontal + vertical) / 2.0;
388       new_value = tp_g_value_slice_new_double (mean);
389       g_hash_table_insert (priv->location,
390           g_strdup (EMPATHY_LOCATION_ACCURACY), new_value);
391       DEBUG ("\t - Accuracy: %f", mean);
392     }
393
394   update_timestamp (location_manager);
395   if (priv->timeout_id == 0)
396     priv->timeout_id = g_timeout_add_seconds (TIMEOUT, publish_on_idle, location_manager);
397 }
398
399 static void
400 initial_position_cb (GeocluePosition *position,
401                      GeocluePositionFields fields,
402                      int timestamp,
403                      double latitude,
404                      double longitude,
405                      double altitude,
406                      GeoclueAccuracy *accuracy,
407                      GError *error,
408                      gpointer location_manager)
409 {
410   if (error)
411     {
412       DEBUG ("Error: %s", error->message);
413       g_error_free (error);
414     }
415   else
416     {
417       position_changed_cb (position, fields, timestamp, latitude, longitude,
418           altitude, accuracy, location_manager);
419     }
420 }
421
422 static void
423 update_resources (EmpathyLocationManager *location_manager)
424 {
425   EmpathyLocationManagerPriv *priv = GET_PRIV (location_manager);
426
427   DEBUG ("Updating resources %d", priv->resources);
428
429   if (!geoclue_master_client_set_requirements (priv->gc_client,
430           GEOCLUE_ACCURACY_LEVEL_NONE, 0, TRUE, priv->resources,
431           NULL))
432     {
433       DEBUG ("set_requirements failed");
434       return;
435     }
436
437   if (!priv->geoclue_is_setup)
438     return;
439
440   geoclue_address_get_address_async (priv->gc_address,
441       initial_address_cb, location_manager);
442   geoclue_position_get_position_async (priv->gc_position,
443       initial_position_cb, location_manager);
444 }
445
446 static void
447 setup_geoclue (EmpathyLocationManager *location_manager)
448 {
449   EmpathyLocationManagerPriv *priv = GET_PRIV (location_manager);
450
451   GeoclueMaster *master;
452   GError *error = NULL;
453
454   DEBUG ("Setting up Geoclue");
455   master = geoclue_master_get_default ();
456   priv->gc_client = geoclue_master_create_client (master, NULL, NULL);
457   g_object_unref (master);
458
459   update_resources (location_manager);
460
461   /* Get updated when the position is changes */
462   priv->gc_position = geoclue_master_client_create_position (
463       priv->gc_client, &error);
464   if (priv->gc_position == NULL)
465     {
466       DEBUG ("Failed to create GeocluePosition: %s", error->message);
467       g_error_free (error);
468       return;
469     }
470
471   g_signal_connect (G_OBJECT (priv->gc_position), "position-changed",
472       G_CALLBACK (position_changed_cb), location_manager);
473
474   /* Get updated when the address changes */
475   priv->gc_address = geoclue_master_client_create_address (
476       priv->gc_client, &error);
477   if (priv->gc_address == NULL)
478     {
479       DEBUG ("Failed to create GeoclueAddress: %s", error->message);
480       g_error_free (error);
481       return;
482     }
483
484   g_signal_connect (G_OBJECT (priv->gc_address), "address-changed",
485       G_CALLBACK (address_changed_cb), location_manager);
486
487   priv->geoclue_is_setup = TRUE;
488 }
489
490 static void
491 publish_cb (EmpathyConf *conf,
492             const gchar *key,
493             gpointer user_data)
494 {
495   EmpathyLocationManager *manager = EMPATHY_LOCATION_MANAGER (user_data);
496   EmpathyLocationManagerPriv *priv = GET_PRIV (manager);
497   gboolean can_publish;
498
499   DEBUG ("Publish Conf changed");
500
501
502   if (empathy_conf_get_bool (conf, key, &can_publish) == FALSE)
503     return;
504
505   if (can_publish == TRUE)
506     {
507       if (priv->geoclue_is_setup == FALSE)
508         setup_geoclue (manager);
509       /* if still not setup than the init failed */
510       if (priv->geoclue_is_setup == FALSE)
511         return;
512
513       geoclue_address_get_address_async (priv->gc_address,
514           initial_address_cb, manager);
515       geoclue_position_get_position_async (priv->gc_position,
516           initial_position_cb, manager);
517     }
518   else
519     {
520       /* As per XEP-0080: send an empty location to have remove current
521        * location from the servers
522        */
523       g_hash_table_remove_all (priv->location);
524       publish_to_all_accounts (manager, TRUE);
525     }
526
527 }
528
529 static void
530 resource_cb (EmpathyConf  *conf,
531              const gchar *key,
532              gpointer user_data)
533 {
534   EmpathyLocationManager *manager = EMPATHY_LOCATION_MANAGER (user_data);
535   EmpathyLocationManagerPriv *priv = GET_PRIV (manager);
536   GeoclueResourceFlags resource = 0;
537   gboolean resource_enabled;
538
539   DEBUG ("%s changed", key);
540
541   if (!empathy_conf_get_bool (conf, key, &resource_enabled))
542     return;
543
544   if (strcmp (key, EMPATHY_PREFS_LOCATION_RESOURCE_NETWORK) == 0)
545     resource = GEOCLUE_RESOURCE_NETWORK;
546   if (strcmp (key, EMPATHY_PREFS_LOCATION_RESOURCE_CELL) == 0)
547     resource = GEOCLUE_RESOURCE_CELL;
548   if (strcmp (key, EMPATHY_PREFS_LOCATION_RESOURCE_GPS) == 0)
549     resource = GEOCLUE_RESOURCE_GPS;
550
551   if (resource_enabled)
552     priv->resources |= resource;
553   else
554     priv->resources &= ~resource;
555
556   if (priv->geoclue_is_setup)
557     update_resources (manager);
558 }
559
560 static void
561 accuracy_cb (EmpathyConf  *conf,
562              const gchar *key,
563              gpointer user_data)
564 {
565   EmpathyLocationManager *manager = EMPATHY_LOCATION_MANAGER (user_data);
566   EmpathyLocationManagerPriv *priv = GET_PRIV (manager);
567
568   gboolean enabled;
569
570   DEBUG ("%s changed", key);
571
572   if (!empathy_conf_get_bool (conf, key, &enabled))
573     return;
574   priv->reduce_accuracy = enabled;
575
576   if (enabled)
577     {
578       GRand *rand = g_rand_new_with_seed (time (NULL));
579       priv->reduce_value = g_rand_double_range (rand, -0.25, 0.25);
580       g_rand_free (rand);
581     }
582   else
583     {
584       priv->reduce_value = 0.0;
585     }
586
587   if (!priv->geoclue_is_setup)
588     return;
589
590   geoclue_address_get_address_async (priv->gc_address,
591       initial_address_cb, manager);
592   geoclue_position_get_position_async (priv->gc_position,
593       initial_position_cb, manager);
594 }
595
596 static void
597 empathy_location_manager_init (EmpathyLocationManager *location_manager)
598 {
599   EmpathyConf               *conf;
600   EmpathyLocationManagerPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (location_manager,
601       EMPATHY_TYPE_LOCATION_MANAGER, EmpathyLocationManagerPriv);
602
603   location_manager->priv = priv;
604   priv->geoclue_is_setup = FALSE;
605   priv->location = g_hash_table_new_full (g_direct_hash, g_direct_equal,
606       g_free, (GDestroyNotify) tp_g_value_slice_free);
607
608   /* Setup settings status callbacks */
609   conf = empathy_conf_get ();
610   empathy_conf_notify_add (conf, EMPATHY_PREFS_LOCATION_PUBLISH, publish_cb,
611       location_manager);
612   empathy_conf_notify_add (conf, EMPATHY_PREFS_LOCATION_RESOURCE_NETWORK,
613       resource_cb, location_manager);
614   empathy_conf_notify_add (conf, EMPATHY_PREFS_LOCATION_RESOURCE_CELL,
615       resource_cb, location_manager);
616   empathy_conf_notify_add (conf, EMPATHY_PREFS_LOCATION_RESOURCE_GPS,
617       resource_cb, location_manager);
618   empathy_conf_notify_add (conf, EMPATHY_PREFS_LOCATION_REDUCE_ACCURACY,
619       accuracy_cb, location_manager);
620
621   resource_cb (conf, EMPATHY_PREFS_LOCATION_RESOURCE_NETWORK, location_manager);
622   resource_cb (conf, EMPATHY_PREFS_LOCATION_RESOURCE_CELL, location_manager);
623   resource_cb (conf, EMPATHY_PREFS_LOCATION_RESOURCE_GPS, location_manager);
624   accuracy_cb (conf, EMPATHY_PREFS_LOCATION_REDUCE_ACCURACY, location_manager);
625   publish_cb (conf, EMPATHY_PREFS_LOCATION_PUBLISH, location_manager);
626
627   /* Setup account status callbacks */
628   priv->account_manager = empathy_account_manager_dup_singleton ();
629   g_signal_connect (priv->account_manager,
630     "account-connection-changed",
631     G_CALLBACK (account_connection_changed_cb), location_manager);
632 }
633
634 EmpathyLocationManager *
635 empathy_location_manager_dup_singleton (void)
636 {
637   return EMPATHY_LOCATION_MANAGER (g_object_new (EMPATHY_TYPE_LOCATION_MANAGER,
638       NULL));
639 }