2 * Copyright (C) 2008 Collabora Ltd.
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.
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.
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., 59 Temple Place - Suite 330,
17 * Boston, MA 02111-1307, USA.
19 * Authors: Pierre-Luc Beaudoin <pierre-luc@pierlux.com>
27 #include <glib/gi18n.h>
29 #include <telepathy-glib/util.h>
32 #include <geoclue/geoclue-master.h>
34 #include <extensions/extensions.h>
36 #include "empathy-location-manager.h"
37 #include "empathy-conf.h"
39 #include "libempathy/empathy-account-manager.h"
40 #include "libempathy/empathy-enum-types.h"
41 #include "libempathy/empathy-location.h"
42 #include "libempathy/empathy-tp-contact-factory.h"
43 #include "libempathy/empathy-utils.h"
45 #define DEBUG_FLAG EMPATHY_DEBUG_LOCATION
46 #include "libempathy/empathy-debug.h"
48 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyLocationManager)
55 GeoclueResourceFlags resources;
56 GeoclueMasterClient *gc_client;
57 GeocluePosition *gc_position;
58 GeoclueAddress *gc_address;
60 gboolean reduce_accuracy;
62 EmpathyAccountManager *account_manager;
63 } EmpathyLocationManagerPriv;
65 static void location_manager_finalize (GObject *object);
66 static void location_manager_get_property (GObject *object, guint param_id,
67 GValue *value, GParamSpec *pspec);
68 static void location_manager_set_property (GObject *object, guint param_id,
69 const GValue *value, GParamSpec *pspec);
70 static void position_changed_cb (GeocluePosition *position,
71 GeocluePositionFields fields, int timestamp, double latitude,
72 double longitude, double altitude, GeoclueAccuracy *accuracy,
74 static void address_changed_cb (GeoclueAddress *address, int timestamp,
75 GHashTable *details, GeoclueAccuracy *accuracy, gpointer user_data);
76 static void setup_geoclue (EmpathyLocationManager *location_manager);
77 static void publish_cb (EmpathyConf *conf, const gchar *key,
79 static void update_resources (EmpathyLocationManager *location_manager);
80 static void resource_cb (EmpathyConf *conf, const gchar *key,
82 static void accuracy_cb (EmpathyConf *conf, const gchar *key,
84 static void account_connection_changed_cb (EmpathyAccountManager *manager,
85 McAccount *account, TpConnectionStatusReason reason,
86 TpConnectionStatus current, TpConnectionStatus previous,
87 gpointer *location_manager);
89 G_DEFINE_TYPE (EmpathyLocationManager, empathy_location_manager, G_TYPE_OBJECT);
97 empathy_location_manager_class_init (EmpathyLocationManagerClass *class)
99 GObjectClass *object_class;
101 object_class = G_OBJECT_CLASS (class);
103 object_class->finalize = location_manager_finalize;
104 object_class->get_property = location_manager_get_property;
105 object_class->set_property = location_manager_set_property;
107 g_type_class_add_private (object_class, sizeof (EmpathyLocationManagerPriv));
111 publish_location (EmpathyLocationManager *location_manager,
113 gboolean force_publication)
115 EmpathyLocationManagerPriv *priv;
116 guint connection_status = -1;
117 gboolean can_publish;
118 EmpathyConf *conf = empathy_conf_get ();
120 EmpathyTpContactFactory *factory;
122 priv = GET_PRIV (location_manager);
124 conn = mission_control_get_tpconnection (priv->mc, account, NULL);
128 factory = empathy_tp_contact_factory_dup_singleton (conn);
130 if (force_publication == FALSE)
132 if (!empathy_conf_get_bool (conf, EMPATHY_PREFS_LOCATION_PUBLISH,
136 if (can_publish == FALSE)
140 connection_status = mission_control_get_connection_status (priv->mc,
143 if (connection_status != TP_CONNECTION_STATUS_CONNECTED)
146 DEBUG ("Publishing location to account %s",
147 mc_account_get_display_name (account));
149 empathy_tp_contact_factory_set_location (factory, priv->location);
150 g_object_unref (factory);
154 publish_location_to_all_accounts (EmpathyLocationManager *location_manager,
155 gboolean force_publication)
157 GList *accounts = NULL, *l;
159 accounts = mc_accounts_list_by_enabled (TRUE);
160 for (l = accounts; l; l = l->next)
162 publish_location (location_manager, l->data, force_publication);
165 mc_accounts_list_free (accounts);
169 account_connection_changed_cb (EmpathyAccountManager *manager,
171 TpConnectionStatusReason reason,
172 TpConnectionStatus current,
173 TpConnectionStatus previous,
174 gpointer *location_manager)
176 DEBUG ("Account %s changed status from %d to %d", mc_account_get_display_name (account),
179 if (account && current == TP_CONNECTION_STATUS_CONNECTED)
180 publish_location (EMPATHY_LOCATION_MANAGER (location_manager), account,
185 empathy_location_manager_init (EmpathyLocationManager *location_manager)
188 EmpathyLocationManagerPriv *priv = G_TYPE_INSTANCE_GET_PRIVATE (location_manager,
189 EMPATHY_TYPE_LOCATION_MANAGER, EmpathyLocationManagerPriv);
191 location_manager->priv = priv;
192 priv->is_setup = FALSE;
193 priv->mc = empathy_mission_control_dup_singleton ();
194 priv->location = g_hash_table_new_full (g_direct_hash, g_direct_equal,
195 g_free, (GDestroyNotify) tp_g_value_slice_free);
197 // Setup settings status callbacks
198 conf = empathy_conf_get ();
199 empathy_conf_notify_add (conf, EMPATHY_PREFS_LOCATION_PUBLISH, publish_cb,
201 empathy_conf_notify_add (conf, EMPATHY_PREFS_LOCATION_RESOURCE_NETWORK,
202 resource_cb, location_manager);
203 empathy_conf_notify_add (conf, EMPATHY_PREFS_LOCATION_RESOURCE_CELL,
204 resource_cb, location_manager);
205 empathy_conf_notify_add (conf, EMPATHY_PREFS_LOCATION_RESOURCE_GPS,
206 resource_cb, location_manager);
207 empathy_conf_notify_add (conf, EMPATHY_PREFS_LOCATION_REDUCE_ACCURACY,
208 accuracy_cb, location_manager);
210 resource_cb (conf, EMPATHY_PREFS_LOCATION_RESOURCE_NETWORK, location_manager);
211 resource_cb (conf, EMPATHY_PREFS_LOCATION_RESOURCE_CELL, location_manager);
212 resource_cb (conf, EMPATHY_PREFS_LOCATION_RESOURCE_GPS, location_manager);
213 accuracy_cb (conf, EMPATHY_PREFS_LOCATION_REDUCE_ACCURACY, location_manager);
214 publish_cb (conf, EMPATHY_PREFS_LOCATION_PUBLISH, location_manager);
216 // Setup account status callbacks
217 priv->account_manager = empathy_account_manager_dup_singleton ();
218 g_signal_connect (priv->account_manager,
219 "account-connection-changed",
220 G_CALLBACK (account_connection_changed_cb), location_manager);
225 location_manager_finalize (GObject *object)
227 EmpathyLocationManagerPriv *priv;
229 priv = GET_PRIV (object);
231 DEBUG ("finalize: %p", object);
232 g_object_unref (priv->account_manager);
234 G_OBJECT_CLASS (empathy_location_manager_parent_class)->finalize (object);
239 location_manager_get_property (GObject *object,
244 EmpathyLocationManagerPriv *priv;
246 priv = GET_PRIV (object);
251 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
258 location_manager_set_property (GObject *object,
263 EmpathyLocationManagerPriv *priv;
265 priv = GET_PRIV (object);
270 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, param_id, pspec);
276 EmpathyLocationManager *
277 empathy_location_manager_get_default (void)
279 static EmpathyLocationManager *singleton = NULL;
280 if (singleton == NULL)
281 singleton = g_object_new (EMPATHY_TYPE_LOCATION_MANAGER, NULL);
286 update_timestamp (EmpathyLocationManager *location_manager)
288 EmpathyLocationManagerPriv *priv;
289 priv = GET_PRIV (location_manager);
294 timestamp = time (NULL);
295 stamp64 = (gint64) timestamp;
296 new_value = tp_g_value_slice_new (G_TYPE_INT64);
297 g_value_set_int64 (new_value, stamp64);
298 g_hash_table_insert (priv->location, g_strdup (EMPATHY_LOCATION_TIMESTAMP),
300 DEBUG ("\t - Timestamp: %" G_GINT64_FORMAT, stamp64);
304 initial_position_cb (GeocluePosition *position,
305 GeocluePositionFields fields,
310 GeoclueAccuracy *accuracy,
312 gpointer location_manager)
316 DEBUG ("Error: %s", error->message);
317 g_error_free (error);
320 position_changed_cb (position, fields, timestamp, latitude, longitude,
321 altitude, accuracy, location_manager);
325 position_changed_cb (GeocluePosition *position,
326 GeocluePositionFields fields,
331 GeoclueAccuracy *accuracy,
332 gpointer location_manager)
334 EmpathyLocationManagerPriv *priv;
335 priv = GET_PRIV (location_manager);
336 GeoclueAccuracyLevel level;
337 gdouble mean, horizontal, vertical;
340 geoclue_accuracy_get_details (accuracy, &level, &horizontal, &vertical);
341 DEBUG ("New position (accuracy level %d)", level);
342 if (level == GEOCLUE_ACCURACY_LEVEL_NONE)
345 if (fields & GEOCLUE_POSITION_FIELDS_LONGITUDE)
347 longitude += priv->reduce_value;
348 new_value = tp_g_value_slice_new (G_TYPE_DOUBLE);
349 g_value_set_double (new_value, longitude);
350 g_hash_table_insert (priv->location, g_strdup (EMPATHY_LOCATION_LON),
352 DEBUG ("\t - Longitude: %f", longitude);
354 if (fields & GEOCLUE_POSITION_FIELDS_LATITUDE)
356 latitude += priv->reduce_value;
357 new_value = tp_g_value_slice_new (G_TYPE_DOUBLE);
358 g_value_set_double (new_value, latitude);
359 g_hash_table_insert (priv->location, g_strdup (EMPATHY_LOCATION_LAT),
361 DEBUG ("\t - Latitude: %f", latitude);
363 if (fields & GEOCLUE_POSITION_FIELDS_ALTITUDE)
365 new_value = tp_g_value_slice_new (G_TYPE_DOUBLE);
366 g_value_set_double (new_value, altitude);
367 g_hash_table_insert (priv->location, g_strdup (EMPATHY_LOCATION_ALT),
369 DEBUG ("\t - Altitude: %f", altitude);
372 if (level == GEOCLUE_ACCURACY_LEVEL_DETAILED)
374 mean = (horizontal + vertical) / 2.0;
375 new_value = tp_g_value_slice_new (G_TYPE_DOUBLE);
376 g_value_set_double (new_value, mean);
377 g_hash_table_insert (priv->location,
378 g_strdup (EMPATHY_LOCATION_ACCURACY), new_value);
379 DEBUG ("\t - Accuracy: %f", mean);
382 update_timestamp (location_manager);
383 publish_location_to_all_accounts (EMPATHY_LOCATION_MANAGER (location_manager),
389 address_foreach_cb (gpointer key,
391 gpointer location_manager)
393 if (location_manager == NULL)
396 EmpathyLocationManagerPriv *priv;
397 priv = GET_PRIV (location_manager);
399 // Discard street information if reduced accuracy is on
400 if (priv->reduce_accuracy && strcmp (key, EMPATHY_LOCATION_STREET) == 0)
403 GValue *new_value = tp_g_value_slice_new (G_TYPE_STRING);
404 g_value_set_string (new_value, value);
406 g_hash_table_insert (priv->location, g_strdup (key), new_value);
407 DEBUG ("\t - %s: %s", (char*) key, (char*) value);
411 initial_address_cb (GeoclueAddress *address,
414 GeoclueAccuracy *accuracy,
416 gpointer location_manager)
420 DEBUG ("Error: %s", error->message);
421 g_error_free (error);
424 address_changed_cb (address, timestamp, details, accuracy, location_manager);
428 address_changed_cb (GeoclueAddress *address,
431 GeoclueAccuracy *accuracy,
432 gpointer location_manager)
434 GeoclueAccuracyLevel level;
435 geoclue_accuracy_get_details (accuracy, &level, NULL, NULL);
436 EmpathyLocationManagerPriv *priv;
438 DEBUG ("New address (accuracy level %d):", level);
440 priv = GET_PRIV (location_manager);
441 g_hash_table_remove_all (priv->location);
443 if (g_hash_table_size (details) == 0)
446 g_hash_table_foreach (details, address_foreach_cb, (gpointer)location_manager);
448 update_timestamp (location_manager);
449 publish_location_to_all_accounts (EMPATHY_LOCATION_MANAGER (location_manager),
455 update_resources (EmpathyLocationManager *location_manager)
457 EmpathyLocationManagerPriv *priv;
459 priv = GET_PRIV (location_manager);
461 DEBUG ("Updating resources %d", priv->resources);
463 if (!geoclue_master_client_set_requirements (priv->gc_client,
464 GEOCLUE_ACCURACY_LEVEL_NONE, 0, TRUE, priv->resources,
467 g_printerr ("set_requirements failed");
474 geoclue_address_get_address_async (priv->gc_address,
475 initial_address_cb, location_manager);
476 geoclue_position_get_position_async (priv->gc_position,
477 initial_position_cb, location_manager);
483 setup_geoclue (EmpathyLocationManager *location_manager)
485 EmpathyLocationManagerPriv *priv;
487 priv = GET_PRIV (location_manager);
489 GeoclueMaster *master;
490 GError *error = NULL;
492 DEBUG ("Setting up Geoclue");
493 master = geoclue_master_get_default ();
494 priv->gc_client = geoclue_master_create_client (master, NULL, NULL);
495 g_object_unref (master);
497 update_resources (location_manager);
499 /* Get updated when the position is changes */
500 priv->gc_position = geoclue_master_client_create_position (
501 priv->gc_client, &error);
502 if (priv->gc_position == NULL)
504 g_printerr ("Failed to create GeocluePosition: %s", error->message);
505 g_error_free (error);
509 g_signal_connect (G_OBJECT (priv->gc_position), "position-changed",
510 G_CALLBACK (position_changed_cb), location_manager);
512 /* Get updated when the address changes */
513 priv->gc_address = geoclue_master_client_create_address (
514 priv->gc_client, &error);
515 if (priv->gc_address == NULL)
517 g_printerr ("Failed to create GeoclueAddress: %s", error->message);
518 g_error_free (error);
522 g_signal_connect (G_OBJECT (priv->gc_address), "address-changed",
523 G_CALLBACK (address_changed_cb), location_manager);
525 priv->is_setup = TRUE;
529 publish_cb (EmpathyConf *conf,
533 EmpathyLocationManager *manager = EMPATHY_LOCATION_MANAGER (user_data);
534 EmpathyLocationManagerPriv *priv;
535 gboolean can_publish;
537 DEBUG ("Publish Conf changed");
538 priv = GET_PRIV (manager);
541 if (empathy_conf_get_bool (conf, key, &can_publish) == FALSE)
544 if (can_publish == TRUE)
546 if (priv->is_setup == FALSE)
547 setup_geoclue (manager);
548 /* if still not setup than the init failed */
549 if (priv->is_setup == FALSE)
552 geoclue_address_get_address_async (priv->gc_address,
553 initial_address_cb, manager);
554 geoclue_position_get_position_async (priv->gc_position,
555 initial_position_cb, manager);
556 publish_location_to_all_accounts (manager, FALSE);
560 /* As per XEP-0080: send an empty location to have remove current
561 * location from the servers
563 g_hash_table_remove_all (priv->location);
564 publish_location_to_all_accounts (manager, TRUE);
571 accuracy_cb (EmpathyConf *conf,
575 EmpathyLocationManager *manager = EMPATHY_LOCATION_MANAGER (user_data);
576 EmpathyLocationManagerPriv *priv;
580 priv = GET_PRIV (manager);
581 DEBUG ("%s changed", key);
583 if (!empathy_conf_get_bool (conf, key, &enabled))
585 priv->reduce_accuracy = enabled;
589 GRand *rand = g_rand_new_with_seed (time (NULL));
590 priv->reduce_value = g_rand_double_range (rand, -0.25, 0.25);
594 priv->reduce_value = 0.0;
599 geoclue_address_get_address_async (priv->gc_address,
600 initial_address_cb, manager);
601 geoclue_position_get_position_async (priv->gc_position,
602 initial_position_cb, manager);
606 resource_cb (EmpathyConf *conf,
610 EmpathyLocationManager *manager = EMPATHY_LOCATION_MANAGER (user_data);
611 EmpathyLocationManagerPriv *priv;
612 GeoclueResourceFlags resource = 0;
613 gboolean resource_enabled;
615 priv = GET_PRIV (manager);
616 DEBUG ("%s changed", key);
618 if (!empathy_conf_get_bool (conf, key, &resource_enabled))
621 if (strcmp (key, EMPATHY_PREFS_LOCATION_RESOURCE_NETWORK) == 0)
622 resource = GEOCLUE_RESOURCE_NETWORK;
623 if (strcmp (key, EMPATHY_PREFS_LOCATION_RESOURCE_CELL) == 0)
624 resource = GEOCLUE_RESOURCE_CELL;
625 if (strcmp (key, EMPATHY_PREFS_LOCATION_RESOURCE_GPS) == 0)
626 resource = GEOCLUE_RESOURCE_GPS;
628 if (resource_enabled)
629 priv->resources |= resource;
631 priv->resources &= ~resource;
634 update_resources (manager);