2 * Copyright (C) 2009 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., 51 Franklin St, Fifth Floor,
17 * Boston, MA 02110-1301 USA
19 * Authors: Pierre-Luc Beaudoin <pierre-luc.beaudoin@collabora.co.uk>
27 #include <glib/gi18n.h>
29 #include <telepathy-glib/account-manager.h>
30 #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-enum-types.h"
40 #include "libempathy/empathy-location.h"
41 #include "libempathy/empathy-tp-contact-factory.h"
42 #include "libempathy/empathy-utils.h"
44 #define DEBUG_FLAG EMPATHY_DEBUG_LOCATION
45 #include "libempathy/empathy-debug.h"
47 /* Seconds before updating the location */
49 static EmpathyLocationManager
*location_manager
= NULL
;
51 #define GET_PRIV(obj) EMPATHY_GET_PRIV (obj, EmpathyLocationManager)
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
60 GeoclueResourceFlags resources
;
61 GeoclueMasterClient
*gc_client
;
62 GeocluePosition
*gc_position
;
63 GeoclueAddress
*gc_address
;
65 gboolean reduce_accuracy
;
66 TpAccountManager
*account_manager
;
68 /* The idle id for publish_on_idle func */
70 } EmpathyLocationManagerPriv
;
72 G_DEFINE_TYPE (EmpathyLocationManager
, empathy_location_manager
, G_TYPE_OBJECT
);
75 location_manager_constructor (GType type
,
76 guint n_construct_params
,
77 GObjectConstructParam
*construct_params
)
81 if (location_manager
== NULL
)
83 retval
= G_OBJECT_CLASS (empathy_location_manager_parent_class
)->constructor
84 (type
, n_construct_params
, construct_params
);
86 location_manager
= EMPATHY_LOCATION_MANAGER (retval
);
87 g_object_add_weak_pointer (retval
, (gpointer
) &location_manager
);
91 retval
= g_object_ref (location_manager
);
98 location_manager_dispose (GObject
*object
)
100 EmpathyLocationManagerPriv
*priv
= GET_PRIV (object
);
101 void (*dispose
) (GObject
*) =
102 G_OBJECT_CLASS (empathy_location_manager_parent_class
)->dispose
;
104 if (priv
->account_manager
!= NULL
)
106 g_object_unref (priv
->account_manager
);
107 priv
->account_manager
= NULL
;
110 if (priv
->gc_client
!= NULL
)
112 g_object_unref (priv
->gc_client
);
113 priv
->gc_client
= NULL
;
116 if (priv
->gc_position
!= NULL
)
118 g_object_unref (priv
->gc_position
);
119 priv
->gc_position
= NULL
;
122 if (priv
->gc_address
!= NULL
)
124 g_object_unref (priv
->gc_address
);
125 priv
->gc_address
= NULL
;
128 if (priv
->location
!= NULL
)
130 g_hash_table_unref (priv
->location
);
131 priv
->location
= NULL
;
139 location_manager_get_property (GObject
*object
,
144 /*EmpathyLocationManagerPriv *priv = GET_PRIV (object); */
149 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, param_id
, pspec
);
155 location_manager_set_property (GObject
*object
,
160 /* EmpathyLocationManagerPriv *priv = GET_PRIV (object); */
165 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, param_id
, pspec
);
171 empathy_location_manager_class_init (EmpathyLocationManagerClass
*class)
173 GObjectClass
*object_class
;
175 object_class
= G_OBJECT_CLASS (class);
177 object_class
->constructor
= location_manager_constructor
;
178 object_class
->dispose
= location_manager_dispose
;
179 object_class
->get_property
= location_manager_get_property
;
180 object_class
->set_property
= location_manager_set_property
;
182 g_type_class_add_private (object_class
, sizeof (EmpathyLocationManagerPriv
));
186 publish_location (EmpathyLocationManager
*self
,
188 gboolean force_publication
)
190 EmpathyLocationManagerPriv
*priv
= GET_PRIV (self
);
191 guint connection_status
= -1;
192 gboolean can_publish
;
193 EmpathyConf
*conf
= empathy_conf_get ();
194 EmpathyTpContactFactory
*factory
;
199 if (!force_publication
)
201 if (!empathy_conf_get_bool (conf
, EMPATHY_PREFS_LOCATION_PUBLISH
,
209 connection_status
= tp_connection_get_status (conn
, NULL
);
211 if (connection_status
!= TP_CONNECTION_STATUS_CONNECTED
)
214 DEBUG ("Publishing %s location to connection %p",
215 (g_hash_table_size (priv
->location
) == 0 ? "empty" : ""),
218 factory
= empathy_tp_contact_factory_dup_singleton (conn
);
219 empathy_tp_contact_factory_set_location (factory
, priv
->location
);
220 g_object_unref (factory
);
225 EmpathyLocationManager
*self
;
226 gboolean force_publication
;
230 publish_to_all_am_prepared_cb (GObject
*source_object
,
231 GAsyncResult
*result
,
234 TpAccountManager
*manager
= TP_ACCOUNT_MANAGER (source_object
);
235 PublishToAllData
*data
= user_data
;
237 GError
*error
= NULL
;
239 if (!tp_account_manager_prepare_finish (manager
, result
, &error
))
241 DEBUG ("Failed to prepare account manager: %s", error
->message
);
242 g_error_free (error
);
246 accounts
= tp_account_manager_get_valid_accounts (manager
);
247 for (l
= accounts
; l
; l
= l
->next
)
249 TpConnection
*conn
= tp_account_get_connection (TP_ACCOUNT (l
->data
));
252 publish_location (data
->self
, conn
, data
->force_publication
);
254 g_list_free (accounts
);
257 g_object_unref (data
->self
);
258 g_slice_free (PublishToAllData
, data
);
262 publish_to_all_connections (EmpathyLocationManager
*self
,
263 gboolean force_publication
)
265 EmpathyLocationManagerPriv
*priv
= GET_PRIV (self
);
266 PublishToAllData
*data
;
268 data
= g_slice_new0 (PublishToAllData
);
269 data
->self
= g_object_ref (self
);
270 data
->force_publication
= force_publication
;
272 tp_account_manager_prepare_async (priv
->account_manager
, NULL
,
273 publish_to_all_am_prepared_cb
, data
);
277 publish_on_idle (gpointer user_data
)
279 EmpathyLocationManager
*manager
= EMPATHY_LOCATION_MANAGER (user_data
);
280 EmpathyLocationManagerPriv
*priv
= GET_PRIV (manager
);
282 priv
->timeout_id
= 0;
283 publish_to_all_connections (manager
, TRUE
);
288 new_connection_cb (TpAccount
*account
,
292 gchar
*dbus_error_name
,
296 EmpathyLocationManagerPriv
*priv
= GET_PRIV (self
);
299 conn
= tp_account_get_connection (account
);
301 DEBUG ("New connection %p", conn
);
303 /* Don't publish if it is already planned (ie startup) */
304 if (priv
->timeout_id
== 0)
306 publish_location (EMPATHY_LOCATION_MANAGER (self
), conn
,
312 update_timestamp (EmpathyLocationManager
*self
)
314 EmpathyLocationManagerPriv
*priv
= GET_PRIV (self
);
319 timestamp
= time (NULL
);
320 stamp64
= (gint64
) timestamp
;
321 new_value
= tp_g_value_slice_new_int64 (stamp64
);
322 g_hash_table_insert (priv
->location
, g_strdup (EMPATHY_LOCATION_TIMESTAMP
),
324 DEBUG ("\t - Timestamp: %" G_GINT64_FORMAT
, stamp64
);
328 address_changed_cb (GeoclueAddress
*address
,
331 GeoclueAccuracy
*accuracy
,
334 GeoclueAccuracyLevel level
;
335 EmpathyLocationManagerPriv
*priv
= GET_PRIV (self
);
339 geoclue_accuracy_get_details (accuracy
, &level
, NULL
, NULL
);
340 DEBUG ("New address (accuracy level %d):", level
);
341 /* FIXME: Publish accuracy level also considering the position's */
343 g_hash_table_remove (priv
->location
, EMPATHY_LOCATION_STREET
);
344 g_hash_table_remove (priv
->location
, EMPATHY_LOCATION_AREA
);
345 g_hash_table_remove (priv
->location
, EMPATHY_LOCATION_REGION
);
346 g_hash_table_remove (priv
->location
, EMPATHY_LOCATION_COUNTRY
);
347 g_hash_table_remove (priv
->location
, EMPATHY_LOCATION_COUNTRY_CODE
);
348 g_hash_table_remove (priv
->location
, EMPATHY_LOCATION_POSTAL_CODE
);
350 if (g_hash_table_size (details
) == 0)
352 DEBUG ("\t - (Empty)");
356 g_hash_table_iter_init (&iter
, details
);
357 while (g_hash_table_iter_next (&iter
, &key
, &value
))
360 /* Discard street information if reduced accuracy is on */
361 if (priv
->reduce_accuracy
&&
362 !tp_strdiff (key
, EMPATHY_LOCATION_STREET
))
365 new_value
= tp_g_value_slice_new_string (value
);
366 g_hash_table_insert (priv
->location
, g_strdup (key
), new_value
);
368 DEBUG ("\t - %s: %s", (gchar
*) key
, (gchar
*) value
);
371 update_timestamp (self
);
372 if (priv
->timeout_id
== 0)
373 priv
->timeout_id
= g_timeout_add_seconds (TIMEOUT
, publish_on_idle
, self
);
377 initial_address_cb (GeoclueAddress
*address
,
380 GeoclueAccuracy
*accuracy
,
386 DEBUG ("Error: %s", error
->message
);
387 g_error_free (error
);
391 address_changed_cb (address
, timestamp
, details
, accuracy
, self
);
396 position_changed_cb (GeocluePosition
*position
,
397 GeocluePositionFields fields
,
402 GeoclueAccuracy
*accuracy
,
405 EmpathyLocationManagerPriv
*priv
= GET_PRIV (self
);
406 GeoclueAccuracyLevel level
;
407 gdouble mean
, horizontal
, vertical
;
411 geoclue_accuracy_get_details (accuracy
, &level
, &horizontal
, &vertical
);
412 DEBUG ("New position (accuracy level %d)", level
);
413 if (level
== GEOCLUE_ACCURACY_LEVEL_NONE
)
416 if (fields
& GEOCLUE_POSITION_FIELDS_LONGITUDE
)
419 if (priv
->reduce_accuracy
)
420 /* Truncate at 1 decimal place */
421 longitude
= ((int) (longitude
* 10)) / 10.0;
423 new_value
= tp_g_value_slice_new_double (longitude
);
424 g_hash_table_insert (priv
->location
, g_strdup (EMPATHY_LOCATION_LON
),
426 DEBUG ("\t - Longitude: %f", longitude
);
430 g_hash_table_remove (priv
->location
, EMPATHY_LOCATION_LON
);
433 if (fields
& GEOCLUE_POSITION_FIELDS_LATITUDE
)
435 if (priv
->reduce_accuracy
)
436 /* Truncate at 1 decimal place */
437 latitude
= ((int) (latitude
* 10)) / 10.0;
439 new_value
= tp_g_value_slice_new_double (latitude
);
440 g_hash_table_replace (priv
->location
, g_strdup (EMPATHY_LOCATION_LAT
),
442 DEBUG ("\t - Latitude: %f", latitude
);
446 g_hash_table_remove (priv
->location
, EMPATHY_LOCATION_LAT
);
449 if (fields
& GEOCLUE_POSITION_FIELDS_ALTITUDE
)
451 new_value
= tp_g_value_slice_new_double (altitude
);
452 g_hash_table_replace (priv
->location
, g_strdup (EMPATHY_LOCATION_ALT
),
454 DEBUG ("\t - Altitude: %f", altitude
);
458 g_hash_table_remove (priv
->location
, EMPATHY_LOCATION_ALT
);
461 if (level
== GEOCLUE_ACCURACY_LEVEL_DETAILED
)
463 mean
= (horizontal
+ vertical
) / 2.0;
464 new_value
= tp_g_value_slice_new_double (mean
);
465 g_hash_table_replace (priv
->location
,
466 g_strdup (EMPATHY_LOCATION_ACCURACY
), new_value
);
467 DEBUG ("\t - Accuracy: %f", mean
);
471 g_hash_table_remove (priv
->location
, EMPATHY_LOCATION_ACCURACY
);
474 update_timestamp (self
);
475 if (priv
->timeout_id
== 0)
476 priv
->timeout_id
= g_timeout_add_seconds (TIMEOUT
, publish_on_idle
, self
);
480 initial_position_cb (GeocluePosition
*position
,
481 GeocluePositionFields fields
,
486 GeoclueAccuracy
*accuracy
,
492 DEBUG ("Error: %s", error
->message
);
493 g_error_free (error
);
497 position_changed_cb (position
, fields
, timestamp
, latitude
, longitude
,
498 altitude
, accuracy
, self
);
503 set_requirements (EmpathyLocationManager
*self
)
505 EmpathyLocationManagerPriv
*priv
= GET_PRIV (self
);
506 GError
*error
= NULL
;
508 if (!geoclue_master_client_set_requirements (priv
->gc_client
,
509 GEOCLUE_ACCURACY_LEVEL_COUNTRY
, 0, FALSE
, priv
->resources
,
512 DEBUG ("set_requirements failed: %s", error
->message
);
513 g_error_free (error
);
521 update_resources (EmpathyLocationManager
*self
)
523 EmpathyLocationManagerPriv
*priv
= GET_PRIV (self
);
525 DEBUG ("Updating resources %d", priv
->resources
);
527 if (!priv
->geoclue_is_setup
)
530 /* As per Geoclue bug #15126, using NONE results in no address
531 * being found as geoclue-manual report an empty address with
533 if (!set_requirements (self
))
536 geoclue_address_get_address_async (priv
->gc_address
,
537 initial_address_cb
, self
);
538 geoclue_position_get_position_async (priv
->gc_position
,
539 initial_position_cb
, self
);
543 setup_geoclue (EmpathyLocationManager
*self
)
545 EmpathyLocationManagerPriv
*priv
= GET_PRIV (self
);
547 GeoclueMaster
*master
;
548 GError
*error
= NULL
;
550 DEBUG ("Setting up Geoclue");
551 master
= geoclue_master_get_default ();
552 priv
->gc_client
= geoclue_master_create_client (master
, NULL
, &error
);
553 g_object_unref (master
);
555 if (priv
->gc_client
== NULL
)
557 DEBUG ("Failed to GeoclueMasterClient: %s", error
->message
);
558 g_error_free (error
);
562 if (!set_requirements (self
))
565 /* Get updated when the position is changes */
566 priv
->gc_position
= geoclue_master_client_create_position (
567 priv
->gc_client
, &error
);
568 if (priv
->gc_position
== NULL
)
570 DEBUG ("Failed to create GeocluePosition: %s", error
->message
);
571 g_error_free (error
);
575 g_signal_connect (G_OBJECT (priv
->gc_position
), "position-changed",
576 G_CALLBACK (position_changed_cb
), self
);
578 /* Get updated when the address changes */
579 priv
->gc_address
= geoclue_master_client_create_address (
580 priv
->gc_client
, &error
);
581 if (priv
->gc_address
== NULL
)
583 DEBUG ("Failed to create GeoclueAddress: %s", error
->message
);
584 g_error_free (error
);
588 g_signal_connect (G_OBJECT (priv
->gc_address
), "address-changed",
589 G_CALLBACK (address_changed_cb
), self
);
591 priv
->geoclue_is_setup
= TRUE
;
595 publish_cb (EmpathyConf
*conf
,
599 EmpathyLocationManager
*manager
= EMPATHY_LOCATION_MANAGER (user_data
);
600 EmpathyLocationManagerPriv
*priv
= GET_PRIV (manager
);
601 gboolean can_publish
;
603 DEBUG ("Publish Conf changed");
606 if (!empathy_conf_get_bool (conf
, key
, &can_publish
))
611 if (!priv
->geoclue_is_setup
)
612 setup_geoclue (manager
);
613 /* if still not setup than the init failed */
614 if (!priv
->geoclue_is_setup
)
617 geoclue_address_get_address_async (priv
->gc_address
,
618 initial_address_cb
, manager
);
619 geoclue_position_get_position_async (priv
->gc_position
,
620 initial_position_cb
, manager
);
624 /* As per XEP-0080: send an empty location to have remove current
625 * location from the servers
627 g_hash_table_remove_all (priv
->location
);
628 publish_to_all_connections (manager
, TRUE
);
634 resource_cb (EmpathyConf
*conf
,
638 EmpathyLocationManager
*manager
= EMPATHY_LOCATION_MANAGER (user_data
);
639 EmpathyLocationManagerPriv
*priv
= GET_PRIV (manager
);
640 GeoclueResourceFlags resource
= 0;
641 gboolean resource_enabled
;
643 DEBUG ("%s changed", key
);
645 if (!empathy_conf_get_bool (conf
, key
, &resource_enabled
))
648 if (!tp_strdiff (key
, EMPATHY_PREFS_LOCATION_RESOURCE_NETWORK
))
649 resource
= GEOCLUE_RESOURCE_NETWORK
;
650 if (!tp_strdiff (key
, EMPATHY_PREFS_LOCATION_RESOURCE_CELL
))
651 resource
= GEOCLUE_RESOURCE_CELL
;
652 if (!tp_strdiff (key
, EMPATHY_PREFS_LOCATION_RESOURCE_GPS
))
653 resource
= GEOCLUE_RESOURCE_GPS
;
655 if (resource_enabled
)
656 priv
->resources
|= resource
;
658 priv
->resources
&= ~resource
;
660 if (priv
->geoclue_is_setup
)
661 update_resources (manager
);
665 accuracy_cb (EmpathyConf
*conf
,
669 EmpathyLocationManager
*manager
= EMPATHY_LOCATION_MANAGER (user_data
);
670 EmpathyLocationManagerPriv
*priv
= GET_PRIV (manager
);
674 DEBUG ("%s changed", key
);
676 if (!empathy_conf_get_bool (conf
, key
, &enabled
))
678 priv
->reduce_accuracy
= enabled
;
680 if (!priv
->geoclue_is_setup
)
683 geoclue_address_get_address_async (priv
->gc_address
,
684 initial_address_cb
, manager
);
685 geoclue_position_get_position_async (priv
->gc_position
,
686 initial_position_cb
, manager
);
690 account_manager_prepared_cb (GObject
*source_object
,
691 GAsyncResult
*result
,
695 TpAccountManager
*account_manager
= TP_ACCOUNT_MANAGER (source_object
);
696 EmpathyLocationManager
*self
= user_data
;
697 GError
*error
= NULL
;
699 if (!tp_account_manager_prepare_finish (account_manager
, result
, &error
))
701 DEBUG ("Failed to prepare account manager: %s", error
->message
);
702 g_error_free (error
);
706 accounts
= tp_account_manager_get_valid_accounts (account_manager
);
707 for (l
= accounts
; l
!= NULL
; l
= l
->next
)
709 TpAccount
*account
= TP_ACCOUNT (l
->data
);
711 empathy_signal_connect_weak (account
, "status-changed",
712 G_CALLBACK (new_connection_cb
), G_OBJECT (self
));
714 g_list_free (accounts
);
718 empathy_location_manager_init (EmpathyLocationManager
*self
)
721 EmpathyLocationManagerPriv
*priv
= G_TYPE_INSTANCE_GET_PRIVATE (self
,
722 EMPATHY_TYPE_LOCATION_MANAGER
, EmpathyLocationManagerPriv
);
725 priv
->geoclue_is_setup
= FALSE
;
726 priv
->location
= g_hash_table_new_full (g_direct_hash
, g_direct_equal
,
727 g_free
, (GDestroyNotify
) tp_g_value_slice_free
);
729 /* Setup account status callbacks */
730 priv
->account_manager
= tp_account_manager_dup ();
732 tp_account_manager_prepare_async (priv
->account_manager
, NULL
,
733 account_manager_prepared_cb
, self
);
735 /* Setup settings status callbacks */
736 conf
= empathy_conf_get ();
737 empathy_conf_notify_add (conf
, EMPATHY_PREFS_LOCATION_PUBLISH
, publish_cb
,
739 empathy_conf_notify_add (conf
, EMPATHY_PREFS_LOCATION_RESOURCE_NETWORK
,
741 empathy_conf_notify_add (conf
, EMPATHY_PREFS_LOCATION_RESOURCE_CELL
,
743 empathy_conf_notify_add (conf
, EMPATHY_PREFS_LOCATION_RESOURCE_GPS
,
745 empathy_conf_notify_add (conf
, EMPATHY_PREFS_LOCATION_REDUCE_ACCURACY
,
748 resource_cb (conf
, EMPATHY_PREFS_LOCATION_RESOURCE_NETWORK
, self
);
749 resource_cb (conf
, EMPATHY_PREFS_LOCATION_RESOURCE_CELL
, self
);
750 resource_cb (conf
, EMPATHY_PREFS_LOCATION_RESOURCE_GPS
, self
);
751 accuracy_cb (conf
, EMPATHY_PREFS_LOCATION_REDUCE_ACCURACY
, self
);
752 publish_cb (conf
, EMPATHY_PREFS_LOCATION_PUBLISH
, self
);
755 EmpathyLocationManager
*
756 empathy_location_manager_dup_singleton (void)
758 return EMPATHY_LOCATION_MANAGER (g_object_new (EMPATHY_TYPE_LOCATION_MANAGER
,