]> git.0d.be Git - empathy.git/blob - libempathy-gtk/empathy-location-manager.c
use the 48x48 version of the local-xmpp icon
[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-lib.h>
28
29 #include <telepathy-glib/account-manager.h>
30 #include <telepathy-glib/util.h>
31
32 #include <geoclue/geoclue-master.h>
33
34 #include <extensions/extensions.h>
35
36 #include "empathy-location-manager.h"
37
38 #include "libempathy/empathy-enum-types.h"
39 #include "libempathy/empathy-gsettings.h"
40 #include "libempathy/empathy-location.h"
41 #include "libempathy/empathy-utils.h"
42 #include "libempathy/empathy-time.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 struct _EmpathyLocationManagerPrivate {
52     gboolean geoclue_is_setup;
53     /* Contains the location to be sent to accounts.  Geoclue is used
54      * to populate it.  This HashTable uses Telepathy's style (string,
55      * GValue). Keys are defined in empathy-location.h
56      */
57     GHashTable *location;
58
59     GSettings *gsettings_loc;
60
61     GeoclueResourceFlags resources;
62     GeoclueMasterClient *gc_client;
63     GeocluePosition *gc_position;
64     GeoclueAddress *gc_address;
65
66     gboolean reduce_accuracy;
67     TpAccountManager *account_manager;
68
69     /* The idle id for publish_on_idle func */
70     guint timeout_id;
71 };
72
73 G_DEFINE_TYPE (EmpathyLocationManager, empathy_location_manager, G_TYPE_OBJECT);
74
75 static GObject *
76 location_manager_constructor (GType type,
77     guint n_construct_params,
78     GObjectConstructParam *construct_params)
79 {
80   GObject *retval;
81
82   if (location_manager == NULL)
83     {
84       retval = G_OBJECT_CLASS (empathy_location_manager_parent_class)->constructor
85           (type, n_construct_params, construct_params);
86
87       location_manager = EMPATHY_LOCATION_MANAGER (retval);
88       g_object_add_weak_pointer (retval, (gpointer) &location_manager);
89     }
90   else
91     {
92       retval = g_object_ref (location_manager);
93     }
94
95   return retval;
96 }
97
98 static void
99 location_manager_dispose (GObject *object)
100 {
101   EmpathyLocationManager *self = (EmpathyLocationManager *) object;
102   void (*dispose) (GObject *) =
103     G_OBJECT_CLASS (empathy_location_manager_parent_class)->dispose;
104
105   tp_clear_object (&self->priv->account_manager);
106   tp_clear_object (&self->priv->gsettings_loc);
107   tp_clear_object (&self->priv->gc_client);
108   tp_clear_object (&self->priv->gc_position);
109   tp_clear_object (&self->priv->gc_address);
110   tp_clear_pointer (&self->priv->location, g_hash_table_unref);
111
112   if (dispose != NULL)
113     dispose (object);
114 }
115
116 static void
117 empathy_location_manager_class_init (EmpathyLocationManagerClass *class)
118 {
119   GObjectClass *object_class;
120
121   object_class = G_OBJECT_CLASS (class);
122
123   object_class->constructor = location_manager_constructor;
124   object_class->dispose = location_manager_dispose;
125
126   g_type_class_add_private (object_class, sizeof (EmpathyLocationManagerPrivate));
127 }
128
129 static void
130 publish_location_cb (TpConnection *connection,
131                      const GError *error,
132                      gpointer user_data,
133                      GObject *weak_object)
134 {
135   if (error != NULL)
136       DEBUG ("Error setting location: %s", error->message);
137 }
138
139 static void
140 publish_location (EmpathyLocationManager *self,
141     TpConnection *conn,
142     gboolean force_publication)
143 {
144   guint connection_status = -1;
145
146   if (!conn)
147     return;
148
149   if (!force_publication)
150     {
151       if (!g_settings_get_boolean (self->priv->gsettings_loc,
152             EMPATHY_PREFS_LOCATION_PUBLISH))
153         return;
154     }
155
156   connection_status = tp_connection_get_status (conn, NULL);
157
158   if (connection_status != TP_CONNECTION_STATUS_CONNECTED)
159     return;
160
161   DEBUG ("Publishing %s location to connection %p",
162       (g_hash_table_size (self->priv->location) == 0 ? "empty" : ""),
163       conn);
164
165   tp_cli_connection_interface_location_call_set_location (conn, -1,
166       self->priv->location, publish_location_cb, NULL, NULL, G_OBJECT (self));
167 }
168
169 typedef struct
170 {
171   EmpathyLocationManager *self;
172   gboolean force_publication;
173 } PublishToAllData;
174
175 static void
176 publish_to_all_am_prepared_cb (GObject *source_object,
177     GAsyncResult *result,
178     gpointer user_data)
179 {
180   TpAccountManager *manager = TP_ACCOUNT_MANAGER (source_object);
181   PublishToAllData *data = user_data;
182   GList *accounts, *l;
183   GError *error = NULL;
184
185   if (!tp_proxy_prepare_finish (manager, result, &error))
186     {
187       DEBUG ("Failed to prepare account manager: %s", error->message);
188       g_error_free (error);
189       goto out;
190     }
191
192   accounts = tp_account_manager_get_valid_accounts (manager);
193   for (l = accounts; l; l = l->next)
194     {
195       TpConnection *conn = tp_account_get_connection (TP_ACCOUNT (l->data));
196
197       if (conn != NULL)
198         publish_location (data->self, conn, data->force_publication);
199     }
200   g_list_free (accounts);
201
202 out:
203   g_object_unref (data->self);
204   g_slice_free (PublishToAllData, data);
205 }
206
207 static void
208 publish_to_all_connections (EmpathyLocationManager *self,
209     gboolean force_publication)
210 {
211   PublishToAllData *data;
212
213   data = g_slice_new0 (PublishToAllData);
214   data->self = g_object_ref (self);
215   data->force_publication = force_publication;
216
217   tp_proxy_prepare_async (self->priv->account_manager, NULL,
218       publish_to_all_am_prepared_cb, data);
219 }
220
221 static gboolean
222 publish_on_idle (gpointer user_data)
223 {
224   EmpathyLocationManager *manager = EMPATHY_LOCATION_MANAGER (user_data);
225
226   manager->priv->timeout_id = 0;
227   publish_to_all_connections (manager, TRUE);
228   return FALSE;
229 }
230
231 static void
232 new_connection_cb (TpAccount *account,
233     guint old_status,
234     guint new_status,
235     guint reason,
236     gchar *dbus_error_name,
237     GHashTable *details,
238     gpointer user_data)
239 {
240   EmpathyLocationManager *self = user_data;
241   TpConnection *conn;
242
243   conn = tp_account_get_connection (account);
244
245   DEBUG ("New connection %p", conn);
246
247   /* Don't publish if it is already planned (ie startup) */
248   if (self->priv->timeout_id == 0)
249     {
250       publish_location (EMPATHY_LOCATION_MANAGER (self), conn,
251           FALSE);
252     }
253 }
254
255 static void
256 update_timestamp (EmpathyLocationManager *self)
257 {
258   gint64 timestamp;
259
260   timestamp = empathy_time_get_current ();
261   tp_asv_set_int64 (self->priv->location, EMPATHY_LOCATION_TIMESTAMP,
262       timestamp);
263
264   DEBUG ("\t - Timestamp: %" G_GINT64_FORMAT, timestamp);
265 }
266
267 static void
268 address_changed_cb (GeoclueAddress *address,
269                     int timestamp,
270                     GHashTable *details,
271                     GeoclueAccuracy *accuracy,
272                     gpointer user_data)
273 {
274   EmpathyLocationManager *self = user_data;
275   GeoclueAccuracyLevel level;
276   GHashTableIter iter;
277   gpointer key, value;
278
279   geoclue_accuracy_get_details (accuracy, &level, NULL, NULL);
280   DEBUG ("New address (accuracy level %d):", level);
281   /* FIXME: Publish accuracy level also considering the position's */
282
283   g_hash_table_remove (self->priv->location, EMPATHY_LOCATION_STREET);
284   g_hash_table_remove (self->priv->location, EMPATHY_LOCATION_AREA);
285   g_hash_table_remove (self->priv->location, EMPATHY_LOCATION_REGION);
286   g_hash_table_remove (self->priv->location, EMPATHY_LOCATION_COUNTRY);
287   g_hash_table_remove (self->priv->location, EMPATHY_LOCATION_COUNTRY_CODE);
288   g_hash_table_remove (self->priv->location, EMPATHY_LOCATION_POSTAL_CODE);
289
290   if (g_hash_table_size (details) == 0)
291     {
292       DEBUG ("\t - (Empty)");
293       return;
294     }
295
296   g_hash_table_iter_init (&iter, details);
297   while (g_hash_table_iter_next (&iter, &key, &value))
298     {
299       /* Discard street information if reduced accuracy is on */
300       if (self->priv->reduce_accuracy &&
301           !tp_strdiff (key, EMPATHY_LOCATION_STREET))
302         continue;
303
304       tp_asv_set_string (self->priv->location, key, value);
305
306       DEBUG ("\t - %s: %s", (gchar *) key, (gchar *) value);
307     }
308
309   update_timestamp (self);
310   if (self->priv->timeout_id == 0)
311     self->priv->timeout_id = g_timeout_add_seconds (TIMEOUT, publish_on_idle,
312         self);
313 }
314
315 static void
316 initial_address_cb (GeoclueAddress *address,
317                     int timestamp,
318                     GHashTable *details,
319                     GeoclueAccuracy *accuracy,
320                     GError *error,
321                     gpointer self)
322 {
323   if (error)
324     {
325       DEBUG ("Error: %s", error->message);
326       g_error_free (error);
327     }
328   else
329     {
330       address_changed_cb (address, timestamp, details, accuracy, self);
331     }
332 }
333
334 static void
335 position_changed_cb (GeocluePosition *position,
336                      GeocluePositionFields fields,
337                      int timestamp,
338                      double latitude,
339                      double longitude,
340                      double altitude,
341                      GeoclueAccuracy *accuracy,
342                      gpointer user_data)
343 {
344   EmpathyLocationManager *self = user_data;
345   GeoclueAccuracyLevel level;
346   gdouble mean, horizontal, vertical;
347
348   geoclue_accuracy_get_details (accuracy, &level, &horizontal, &vertical);
349   DEBUG ("New position (accuracy level %d)", level);
350   if (level == GEOCLUE_ACCURACY_LEVEL_NONE)
351     return;
352
353   if (fields & GEOCLUE_POSITION_FIELDS_LONGITUDE)
354     {
355
356       if (self->priv->reduce_accuracy)
357         /* Truncate at 1 decimal place */
358         longitude = ((int) (longitude * 10)) / 10.0;
359
360       tp_asv_set_double (self->priv->location, EMPATHY_LOCATION_LON, longitude);
361
362       DEBUG ("\t - Longitude: %f", longitude);
363     }
364   else
365     {
366       g_hash_table_remove (self->priv->location, EMPATHY_LOCATION_LON);
367     }
368
369   if (fields & GEOCLUE_POSITION_FIELDS_LATITUDE)
370     {
371       if (self->priv->reduce_accuracy)
372         /* Truncate at 1 decimal place */
373         latitude = ((int) (latitude * 10)) / 10.0;
374
375       tp_asv_set_double (self->priv->location, EMPATHY_LOCATION_LAT, latitude);
376
377       DEBUG ("\t - Latitude: %f", latitude);
378     }
379   else
380     {
381       g_hash_table_remove (self->priv->location, EMPATHY_LOCATION_LAT);
382     }
383
384   if (fields & GEOCLUE_POSITION_FIELDS_ALTITUDE)
385     {
386       tp_asv_set_double (self->priv->location, EMPATHY_LOCATION_ALT, altitude);
387
388       DEBUG ("\t - Altitude: %f", altitude);
389     }
390   else
391     {
392       g_hash_table_remove (self->priv->location, EMPATHY_LOCATION_ALT);
393     }
394
395   if (level == GEOCLUE_ACCURACY_LEVEL_DETAILED)
396     {
397       mean = (horizontal + vertical) / 2.0;
398       tp_asv_set_double (self->priv->location, EMPATHY_LOCATION_ACCURACY, mean);
399
400       DEBUG ("\t - Accuracy: %f", mean);
401     }
402   else
403     {
404       g_hash_table_remove (self->priv->location, EMPATHY_LOCATION_ACCURACY);
405     }
406
407   update_timestamp (self);
408   if (self->priv->timeout_id == 0)
409     self->priv->timeout_id = g_timeout_add_seconds (TIMEOUT, publish_on_idle,
410         self);
411 }
412
413 static void
414 initial_position_cb (GeocluePosition *position,
415                      GeocluePositionFields fields,
416                      int timestamp,
417                      double latitude,
418                      double longitude,
419                      double altitude,
420                      GeoclueAccuracy *accuracy,
421                      GError *error,
422                      gpointer self)
423 {
424   if (error)
425     {
426       DEBUG ("Error: %s", error->message);
427       g_error_free (error);
428     }
429   else
430     {
431       position_changed_cb (position, fields, timestamp, latitude, longitude,
432           altitude, accuracy, self);
433     }
434 }
435
436 static gboolean
437 set_requirements (EmpathyLocationManager *self)
438 {
439   GError *error = NULL;
440
441   if (!geoclue_master_client_set_requirements (self->priv->gc_client,
442           GEOCLUE_ACCURACY_LEVEL_COUNTRY, 0, FALSE, self->priv->resources,
443           &error))
444     {
445       DEBUG ("set_requirements failed: %s", error->message);
446       g_error_free (error);
447       return FALSE;
448     }
449
450   return TRUE;
451 }
452
453 static void
454 update_resources (EmpathyLocationManager *self)
455 {
456   DEBUG ("Updating resources %d", self->priv->resources);
457
458   if (!self->priv->geoclue_is_setup)
459     return;
460
461   /* As per Geoclue bug #15126, using NONE results in no address
462    * being found as geoclue-manual report an empty address with
463    * accuracy = NONE */
464   if (!set_requirements (self))
465     return;
466
467   geoclue_address_get_address_async (self->priv->gc_address,
468       initial_address_cb, self);
469   geoclue_position_get_position_async (self->priv->gc_position,
470       initial_position_cb, self);
471 }
472
473 static void
474 setup_geoclue (EmpathyLocationManager *self)
475 {
476   GeoclueMaster *master;
477   GError *error = NULL;
478
479   DEBUG ("Setting up Geoclue");
480   master = geoclue_master_get_default ();
481   self->priv->gc_client = geoclue_master_create_client (master, NULL, &error);
482   g_object_unref (master);
483
484   if (self->priv->gc_client == NULL)
485     {
486       DEBUG ("Failed to GeoclueMasterClient: %s", error->message);
487       g_error_free (error);
488       return;
489     }
490
491   if (!set_requirements (self))
492     return;
493
494   /* Get updated when the position is changes */
495   self->priv->gc_position = geoclue_master_client_create_position (
496       self->priv->gc_client, &error);
497   if (self->priv->gc_position == NULL)
498     {
499       DEBUG ("Failed to create GeocluePosition: %s", error->message);
500       g_error_free (error);
501       return;
502     }
503
504   g_signal_connect (G_OBJECT (self->priv->gc_position), "position-changed",
505       G_CALLBACK (position_changed_cb), self);
506
507   /* Get updated when the address changes */
508   self->priv->gc_address = geoclue_master_client_create_address (
509       self->priv->gc_client, &error);
510   if (self->priv->gc_address == NULL)
511     {
512       DEBUG ("Failed to create GeoclueAddress: %s", error->message);
513       g_error_free (error);
514       return;
515     }
516
517   g_signal_connect (G_OBJECT (self->priv->gc_address), "address-changed",
518       G_CALLBACK (address_changed_cb), self);
519
520   self->priv->geoclue_is_setup = TRUE;
521 }
522
523 static void
524 publish_cb (GSettings *gsettings_loc,
525             const gchar *key,
526             gpointer user_data)
527 {
528   EmpathyLocationManager *self = EMPATHY_LOCATION_MANAGER (user_data);
529
530   DEBUG ("Publish Conf changed");
531
532   if (g_settings_get_boolean (gsettings_loc, key))
533     {
534       if (!self->priv->geoclue_is_setup)
535         setup_geoclue (self);
536       /* if still not setup than the init failed */
537       if (!self->priv->geoclue_is_setup)
538         return;
539
540       geoclue_address_get_address_async (self->priv->gc_address,
541           initial_address_cb, self);
542       geoclue_position_get_position_async (self->priv->gc_position,
543           initial_position_cb, self);
544     }
545   else
546     {
547       /* As per XEP-0080: send an empty location to have remove current
548        * location from the servers
549        */
550       g_hash_table_remove_all (self->priv->location);
551       publish_to_all_connections (self, TRUE);
552     }
553
554 }
555
556 static void
557 resource_cb (GSettings *gsettings_loc,
558              const gchar *key,
559              gpointer user_data)
560 {
561   EmpathyLocationManager *self = EMPATHY_LOCATION_MANAGER (user_data);
562   GeoclueResourceFlags resource = 0;
563
564   DEBUG ("%s changed", key);
565
566   if (!tp_strdiff (key, EMPATHY_PREFS_LOCATION_RESOURCE_NETWORK))
567     resource = GEOCLUE_RESOURCE_NETWORK;
568   if (!tp_strdiff (key, EMPATHY_PREFS_LOCATION_RESOURCE_CELL))
569     resource = GEOCLUE_RESOURCE_CELL;
570   if (!tp_strdiff (key, EMPATHY_PREFS_LOCATION_RESOURCE_GPS))
571     resource = GEOCLUE_RESOURCE_GPS;
572
573   if (g_settings_get_boolean (gsettings_loc, key))
574     self->priv->resources |= resource;
575   else
576     self->priv->resources &= ~resource;
577
578   if (self->priv->geoclue_is_setup)
579     update_resources (self);
580 }
581
582 static void
583 accuracy_cb (GSettings *gsettings_loc,
584              const gchar *key,
585              gpointer user_data)
586 {
587   EmpathyLocationManager *self = EMPATHY_LOCATION_MANAGER (user_data);
588
589   DEBUG ("%s changed", key);
590
591   self->priv->reduce_accuracy = g_settings_get_boolean (gsettings_loc, key);
592
593   if (!self->priv->geoclue_is_setup)
594     return;
595
596   geoclue_address_get_address_async (self->priv->gc_address,
597       initial_address_cb, self);
598   geoclue_position_get_position_async (self->priv->gc_position,
599       initial_position_cb, self);
600 }
601
602 static void
603 account_manager_prepared_cb (GObject *source_object,
604     GAsyncResult *result,
605     gpointer user_data)
606 {
607   GList *accounts, *l;
608   TpAccountManager *account_manager = TP_ACCOUNT_MANAGER (source_object);
609   EmpathyLocationManager *self = user_data;
610   GError *error = NULL;
611
612   if (!tp_proxy_prepare_finish (account_manager, result, &error))
613     {
614       DEBUG ("Failed to prepare account manager: %s", error->message);
615       g_error_free (error);
616       return;
617     }
618
619   accounts = tp_account_manager_get_valid_accounts (account_manager);
620   for (l = accounts; l != NULL; l = l->next)
621     {
622       TpAccount *account = TP_ACCOUNT (l->data);
623
624       tp_g_signal_connect_object (account, "status-changed",
625           G_CALLBACK (new_connection_cb), self, 0);
626     }
627   g_list_free (accounts);
628 }
629
630 static void
631 empathy_location_manager_init (EmpathyLocationManager *self)
632 {
633   EmpathyLocationManagerPrivate *priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
634       EMPATHY_TYPE_LOCATION_MANAGER, EmpathyLocationManagerPrivate);
635
636   self->priv = priv;
637   priv->geoclue_is_setup = FALSE;
638   priv->location = tp_asv_new (NULL, NULL);
639   priv->gsettings_loc = g_settings_new (EMPATHY_PREFS_LOCATION_SCHEMA);
640
641   /* Setup account status callbacks */
642   priv->account_manager = tp_account_manager_dup ();
643
644   tp_proxy_prepare_async (priv->account_manager, NULL,
645       account_manager_prepared_cb, self);
646
647   /* Setup settings status callbacks */
648   g_signal_connect (priv->gsettings_loc,
649       "changed::" EMPATHY_PREFS_LOCATION_PUBLISH,
650       G_CALLBACK (publish_cb), self);
651   g_signal_connect (priv->gsettings_loc,
652       "changed::" EMPATHY_PREFS_LOCATION_RESOURCE_NETWORK,
653       G_CALLBACK (resource_cb), self);
654   g_signal_connect (priv->gsettings_loc,
655       "changed::" EMPATHY_PREFS_LOCATION_RESOURCE_CELL,
656       G_CALLBACK (resource_cb), self);
657   g_signal_connect (priv->gsettings_loc,
658       "changed::" EMPATHY_PREFS_LOCATION_RESOURCE_GPS,
659       G_CALLBACK (resource_cb), self);
660   g_signal_connect (priv->gsettings_loc,
661       "changed::" EMPATHY_PREFS_LOCATION_REDUCE_ACCURACY,
662       G_CALLBACK (accuracy_cb), self);
663
664   resource_cb (priv->gsettings_loc, EMPATHY_PREFS_LOCATION_RESOURCE_NETWORK,
665       self);
666   resource_cb (priv->gsettings_loc, EMPATHY_PREFS_LOCATION_RESOURCE_CELL, self);
667   resource_cb (priv->gsettings_loc, EMPATHY_PREFS_LOCATION_RESOURCE_GPS, self);
668   accuracy_cb (priv->gsettings_loc, EMPATHY_PREFS_LOCATION_REDUCE_ACCURACY,
669       self);
670   publish_cb (priv->gsettings_loc, EMPATHY_PREFS_LOCATION_PUBLISH, self);
671 }
672
673 EmpathyLocationManager *
674 empathy_location_manager_dup_singleton (void)
675 {
676   return EMPATHY_LOCATION_MANAGER (g_object_new (EMPATHY_TYPE_LOCATION_MANAGER,
677       NULL));
678 }