1 /* vim: set et ts=8 sw=8: */
2 /*
3  * Copyright 2014 Red Hat, Inc.
4  *
5  * Geoclue is free software; you can redistribute it and/or modify it under
6  * the terms of the GNU General Public License as published by the Free
7  * Software Foundation; either version 2 of the License, or (at your option)
8  * any later version.
9  *
10  * Geoclue is distributed in the hope that it will be useful, but WITHOUT ANY
11  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
13  * details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with Geoclue; if not, write to the Free Software Foundation, Inc.,
17  * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18  *
19  * Authors: Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
20  */
21 
22 #include <glib.h>
23 #include "gclue-location-source.h"
24 #include "gclue-compass.h"
25 
26 /**
27  * SECTION:gclue-location-source
28  * @short_description: GeoIP client
29  * @include: gclue-glib/gclue-location-source.h
30  *
31  * The interface all geolocation sources must implement.
32  **/
33 
34 static gboolean
35 start_source (GClueLocationSource *source);
36 static gboolean
37 stop_source (GClueLocationSource *source);
38 
39 struct _GClueLocationSourcePrivate
40 {
41         GClueLocation *location;
42 
43         guint active_counter;
44         GClueMinUINT *time_threshold;
45 
46         GClueAccuracyLevel avail_accuracy_level;
47 
48         gboolean compute_movement;
49         gboolean scramble_location;
50 
51         GClueCompass *compass;
52 
53         guint heading_changed_id;
54 };
55 
56 G_DEFINE_ABSTRACT_TYPE_WITH_CODE (GClueLocationSource,
57                                   gclue_location_source,
58                                   G_TYPE_OBJECT,
59                                   G_ADD_PRIVATE (GClueLocationSource))
60 
61 enum
62 {
63         PROP_0,
64         PROP_LOCATION,
65         PROP_ACTIVE,
66         PROP_TIME_THRESHOLD,
67         PROP_AVAILABLE_ACCURACY_LEVEL,
68         PROP_COMPUTE_MOVEMENT,
69         PROP_SCRAMBLE_LOCATION,
70         LAST_PROP
71 };
72 
73 static GParamSpec *gParamSpecs[LAST_PROP];
74 
75 static gboolean
set_heading_from_compass(GClueLocationSource * source,GClueLocation * location)76 set_heading_from_compass (GClueLocationSource *source,
77                           GClueLocation       *location)
78 {
79         GClueLocationSourcePrivate *priv = source->priv;
80         gdouble heading, curr_heading;
81 
82         if (priv->compass == NULL)
83                 return FALSE;
84 
85         heading = gclue_compass_get_heading (priv->compass);
86         curr_heading = gclue_location_get_heading (location);
87 
88         if (heading == GCLUE_LOCATION_HEADING_UNKNOWN  ||
89             heading == curr_heading)
90                 return FALSE;
91 
92         g_debug ("%s got new heading %f", G_OBJECT_TYPE_NAME (source), heading);
93         /* We trust heading from compass more than any other source so we always
94          * override existing heading
95          */
96         gclue_location_set_heading (location, heading);
97 
98         return TRUE;
99 }
100 
101 static void
on_compass_heading_changed(GObject * gobject,GParamSpec * pspec,gpointer user_data)102 on_compass_heading_changed (GObject    *gobject,
103                             GParamSpec *pspec,
104                             gpointer    user_data)
105 {
106         GClueLocationSource* source = GCLUE_LOCATION_SOURCE (user_data);
107 
108         if (source->priv->location == NULL)
109                 return;
110 
111         if (set_heading_from_compass (source, source->priv->location))
112                 g_object_notify (G_OBJECT (source), "location");
113 }
114 
115 static void
gclue_location_source_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)116 gclue_location_source_get_property (GObject    *object,
117                                     guint       prop_id,
118                                     GValue     *value,
119                                     GParamSpec *pspec)
120 {
121         GClueLocationSource *source = GCLUE_LOCATION_SOURCE (object);
122 
123         switch (prop_id) {
124         case PROP_LOCATION:
125                 g_value_set_object (value, source->priv->location);
126                 break;
127 
128         case PROP_ACTIVE:
129                 g_value_set_boolean (value,
130                                      gclue_location_source_get_active (source));
131                 break;
132 
133         case PROP_TIME_THRESHOLD:
134                 g_value_set_object (value, source->priv->time_threshold);
135                 break;
136 
137         case PROP_AVAILABLE_ACCURACY_LEVEL:
138                 g_value_set_enum (value, source->priv->avail_accuracy_level);
139                 break;
140 
141         case PROP_COMPUTE_MOVEMENT:
142                 g_value_set_boolean (value, source->priv->compute_movement);
143                 break;
144 
145         case PROP_SCRAMBLE_LOCATION:
146                 g_value_set_boolean (value, source->priv->scramble_location);
147                 break;
148 
149         default:
150                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
151         }
152 }
153 
154 static void
gclue_location_source_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)155 gclue_location_source_set_property (GObject      *object,
156                                     guint         prop_id,
157                                     const GValue *value,
158                                     GParamSpec   *pspec)
159 {
160         GClueLocationSource *source = GCLUE_LOCATION_SOURCE (object);
161 
162         switch (prop_id) {
163         case PROP_LOCATION:
164         {
165                 GClueLocation *location = g_value_get_object (value);
166 
167                 gclue_location_source_set_location (source, location);
168                 break;
169         }
170 
171         case PROP_AVAILABLE_ACCURACY_LEVEL:
172                 source->priv->avail_accuracy_level = g_value_get_enum (value);
173                 break;
174 
175         case PROP_COMPUTE_MOVEMENT:
176                 source->priv->compute_movement = g_value_get_boolean (value);
177                 break;
178 
179         case PROP_SCRAMBLE_LOCATION:
180                 source->priv->scramble_location = g_value_get_boolean (value);
181                 break;
182 
183         default:
184                 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
185         }
186 }
187 
188 static void
gclue_location_source_finalize(GObject * object)189 gclue_location_source_finalize (GObject *object)
190 {
191         GClueLocationSourcePrivate *priv = GCLUE_LOCATION_SOURCE (object)->priv;
192 
193         gclue_location_source_stop (GCLUE_LOCATION_SOURCE (object));
194         g_clear_object (&priv->location);
195         g_clear_object (&priv->time_threshold);
196 
197         G_OBJECT_CLASS (gclue_location_source_parent_class)->finalize (object);
198 }
199 
200 static void
gclue_location_source_class_init(GClueLocationSourceClass * klass)201 gclue_location_source_class_init (GClueLocationSourceClass *klass)
202 {
203         GObjectClass *object_class;
204 
205         klass->start = start_source;
206         klass->stop = stop_source;
207 
208         object_class = G_OBJECT_CLASS (klass);
209         object_class->get_property = gclue_location_source_get_property;
210         object_class->set_property = gclue_location_source_set_property;
211         object_class->finalize = gclue_location_source_finalize;
212 
213         gParamSpecs[PROP_LOCATION] = g_param_spec_object ("location",
214                                                           "Location",
215                                                           "Location",
216                                                           GCLUE_TYPE_LOCATION,
217                                                           G_PARAM_READWRITE);
218         g_object_class_install_property (object_class,
219                                          PROP_LOCATION,
220                                          gParamSpecs[PROP_LOCATION]);
221 
222         gParamSpecs[PROP_ACTIVE] = g_param_spec_boolean ("active",
223                                                          "Active",
224                                                          "Active",
225                                                          FALSE,
226                                                          G_PARAM_READABLE);
227         g_object_class_install_property (object_class,
228                                          PROP_ACTIVE,
229                                          gParamSpecs[PROP_ACTIVE]);
230 
231         gParamSpecs[PROP_TIME_THRESHOLD] =
232                 g_param_spec_object ("time-threshold",
233                                      "TimeThreshold",
234                                      "TimeThreshold",
235                                      GCLUE_TYPE_MIN_UINT,
236                                      G_PARAM_READABLE);
237         g_object_class_install_property (object_class,
238                                          PROP_TIME_THRESHOLD,
239                                          gParamSpecs[PROP_TIME_THRESHOLD]);
240 
241         gParamSpecs[PROP_AVAILABLE_ACCURACY_LEVEL] =
242                 g_param_spec_enum ("available-accuracy-level",
243                                    "AvailableAccuracyLevel",
244                                    "Available accuracy level",
245                                    GCLUE_TYPE_ACCURACY_LEVEL,
246                                    0,
247                                    G_PARAM_READWRITE);
248         g_object_class_install_property (object_class,
249                                          PROP_AVAILABLE_ACCURACY_LEVEL,
250                                          gParamSpecs[PROP_AVAILABLE_ACCURACY_LEVEL]);
251 
252         gParamSpecs[PROP_COMPUTE_MOVEMENT] =
253                 g_param_spec_boolean ("compute-movement",
254                                       "ComputeMovement",
255                                       "Whether or not, speed and heading should "
256                                       "be automatically computed (or fetched "
257                                       "from hardware) and set on new locations.",
258                                       TRUE,
259                                       G_PARAM_READWRITE);
260         g_object_class_install_property (object_class,
261                                          PROP_COMPUTE_MOVEMENT,
262                                          gParamSpecs[PROP_COMPUTE_MOVEMENT]);
263 
264         gParamSpecs[PROP_SCRAMBLE_LOCATION] =
265                 g_param_spec_boolean ("scramble-location",
266                                       "ScrambleLocation",
267                                       "Enable location scrambling",
268                                       FALSE,
269                                       G_PARAM_READWRITE |
270                                       G_PARAM_CONSTRUCT_ONLY);
271         g_object_class_install_property (object_class,
272                                          PROP_SCRAMBLE_LOCATION,
273                                          gParamSpecs[PROP_SCRAMBLE_LOCATION]);
274 }
275 
276 static void
gclue_location_source_init(GClueLocationSource * source)277 gclue_location_source_init (GClueLocationSource *source)
278 {
279         source->priv =
280                 G_TYPE_INSTANCE_GET_PRIVATE (source,
281                                              GCLUE_TYPE_LOCATION_SOURCE,
282                                              GClueLocationSourcePrivate);
283         source->priv->compute_movement = TRUE;
284         source->priv->time_threshold = gclue_min_uint_new ();
285 }
286 
287 static gboolean
start_source(GClueLocationSource * source)288 start_source (GClueLocationSource *source)
289 {
290         source->priv->active_counter++;
291         if (source->priv->active_counter > 1) {
292                 g_debug ("%s already active, not starting.",
293                          G_OBJECT_TYPE_NAME (source));
294                 return TRUE;
295         }
296 
297         if (source->priv->compute_movement) {
298                 source->priv->compass = gclue_compass_get_singleton ();
299                 source->priv->heading_changed_id = g_signal_connect
300                         (G_OBJECT (source->priv->compass),
301                          "notify::heading",
302                          G_CALLBACK (on_compass_heading_changed),
303                          source);
304         }
305 
306         g_object_notify (G_OBJECT (source), "active");
307         g_debug ("%s now active", G_OBJECT_TYPE_NAME (source));
308         return TRUE;
309 }
310 
311 static gboolean
stop_source(GClueLocationSource * source)312 stop_source (GClueLocationSource *source)
313 {
314         if (source->priv->active_counter == 0) {
315                 g_debug ("%s already inactive, not stopping.",
316                          G_OBJECT_TYPE_NAME (source));
317                 return TRUE;
318         }
319 
320         source->priv->active_counter--;
321         if (source->priv->active_counter > 0) {
322                 g_debug ("%s still in use, not stopping.",
323                          G_OBJECT_TYPE_NAME (source));
324                 return FALSE;
325         }
326 
327         if (source->priv->compass) {
328                 g_signal_handler_disconnect (source->priv->compass,
329                                              source->priv->heading_changed_id);
330                 g_clear_object (&source->priv->compass);
331         }
332 
333         g_object_notify (G_OBJECT (source), "active");
334         g_debug ("%s now inactive", G_OBJECT_TYPE_NAME (source));
335 
336         return TRUE;
337 }
338 
339 /**
340  * gclue_location_source_start:
341  * @source: a #GClueLocationSource
342  *
343  * Start searching for location and keep an eye on location changes.
344  **/
345 void
gclue_location_source_start(GClueLocationSource * source)346 gclue_location_source_start (GClueLocationSource *source)
347 {
348         g_return_if_fail (GCLUE_IS_LOCATION_SOURCE (source));
349 
350         GCLUE_LOCATION_SOURCE_GET_CLASS (source)->start (source);
351 }
352 
353 /**
354  * gclue_location_source_stop:
355  * @source: a #GClueLocationSource
356  *
357  * Stop searching for location and no need to keep an eye on location changes
358  * anymore.
359  **/
360 void
gclue_location_source_stop(GClueLocationSource * source)361 gclue_location_source_stop (GClueLocationSource *source)
362 {
363         g_return_if_fail (GCLUE_IS_LOCATION_SOURCE (source));
364 
365         GCLUE_LOCATION_SOURCE_GET_CLASS (source)->stop (source);
366 }
367 
368 /**
369  * gclue_location_source_get_location:
370  * @source: a #GClueLocationSource
371  *
372  * Returns: (transfer none): The location, or NULL if unknown.
373  **/
374 GClueLocation *
gclue_location_source_get_location(GClueLocationSource * source)375 gclue_location_source_get_location (GClueLocationSource *source)
376 {
377         g_return_val_if_fail (GCLUE_IS_LOCATION_SOURCE (source), NULL);
378 
379         return source->priv->location;
380 }
381 
382 /* 1 km in latitude is always .00899928005759539236 degrees */
383 #define LATITUDE_IN_KM .00899928005759539236
384 
385 /**
386  * gclue_location_source_set_location:
387  * @source: a #GClueLocationSource
388  *
389  * Set the current location to @location. Its meant to be only used by
390  * subclasses.
391  **/
392 void
gclue_location_source_set_location(GClueLocationSource * source,GClueLocation * location)393 gclue_location_source_set_location (GClueLocationSource *source,
394                                     GClueLocation       *location)
395 {
396         GClueLocationSourcePrivate *priv = source->priv;
397         GClueLocation *cur_location;
398         gdouble speed, heading;
399 
400         cur_location = priv->location;
401         priv->location = gclue_location_duplicate (location);
402 
403         if (priv->scramble_location) {
404                 gdouble latitude, distance, accuracy;
405 
406                 latitude = gclue_location_get_latitude (priv->location);
407                 accuracy = gclue_location_get_accuracy (priv->location);
408 
409                 /* Randomization is needed to stop apps from calculationg the
410                  * actual location.
411                  */
412                 distance = (gdouble) g_random_int_range (1, 3);
413 
414                 if (g_random_boolean ())
415                         latitude += distance * LATITUDE_IN_KM;
416                 else
417                         latitude -= distance * LATITUDE_IN_KM;
418                 accuracy += 3000;
419 
420                 g_object_set (G_OBJECT (priv->location),
421                               "latitude", latitude,
422                               "accuracy", accuracy,
423                               NULL);
424                 g_debug ("location scrambled");
425         }
426 
427         speed = gclue_location_get_speed (location);
428         if (speed == GCLUE_LOCATION_SPEED_UNKNOWN) {
429                 if (cur_location != NULL && priv->compute_movement) {
430                         guint64 cur_timestamp, timestamp;
431 
432                         timestamp = gclue_location_get_timestamp (location);
433                         cur_timestamp = gclue_location_get_timestamp
434                                         (cur_location);
435 
436                         if (timestamp != cur_timestamp)
437                                 gclue_location_set_speed_from_prev_location
438                                         (priv->location, cur_location);
439                 }
440         } else {
441                 gclue_location_set_speed (priv->location, speed);
442         }
443 
444         set_heading_from_compass (source, location);
445         heading = gclue_location_get_heading (location);
446         if (heading == GCLUE_LOCATION_HEADING_UNKNOWN) {
447                 if (cur_location != NULL && priv->compute_movement)
448                         gclue_location_set_heading_from_prev_location
449                                 (priv->location, cur_location);
450         } else {
451                 gclue_location_set_heading (priv->location, heading);
452         }
453 
454         g_object_notify (G_OBJECT (source), "location");
455         g_clear_object (&cur_location);
456 }
457 
458 /**
459  * gclue_location_source_get_active:
460  * @source: a #GClueLocationSource
461  *
462  * Returns: TRUE if source is active, FALSE otherwise.
463  **/
464 gboolean
gclue_location_source_get_active(GClueLocationSource * source)465 gclue_location_source_get_active (GClueLocationSource *source)
466 {
467         g_return_val_if_fail (GCLUE_IS_LOCATION_SOURCE (source), FALSE);
468 
469         return (source->priv->active_counter > 0);
470 }
471 
472 /**
473  * gclue_location_source_get_available_accuracy_level:
474  * @source: a #GClueLocationSource
475  *
476  * Returns: The currently available accuracy level.
477  **/
478 GClueAccuracyLevel
gclue_location_source_get_available_accuracy_level(GClueLocationSource * source)479 gclue_location_source_get_available_accuracy_level (GClueLocationSource *source)
480 {
481         g_return_val_if_fail (GCLUE_IS_LOCATION_SOURCE (source), 0);
482 
483         return source->priv->avail_accuracy_level;
484 }
485 
486 /**
487  * gclue_location_source_get_compute_movement
488  * @source: a #GClueLocationSource
489  *
490  * Returns: %TRUE if speed and heading will be automatically computed (or
491  * fetched from hardware) and set on new locations, %FALSE otherwise.
492  **/
493 gboolean
gclue_location_source_get_compute_movement(GClueLocationSource * source)494 gclue_location_source_get_compute_movement (GClueLocationSource *source)
495 {
496         g_return_val_if_fail (GCLUE_IS_LOCATION_SOURCE (source), FALSE);
497 
498         return source->priv->compute_movement;
499 }
500 
501 /**
502  * gclue_location_source_set_compute_movement
503  * @source: a #GClueLocationSource
504  * @compute: a #gboolean
505  *
506  * Use this to specify whether or not you want @source to automatically compute
507  * (or fetch from hardware) and set speed and heading on new locations.
508  **/
509 void
gclue_location_source_set_compute_movement(GClueLocationSource * source,gboolean compute)510 gclue_location_source_set_compute_movement (GClueLocationSource *source,
511                                             gboolean             compute)
512 {
513         g_return_if_fail (GCLUE_IS_LOCATION_SOURCE (source));
514 
515         source->priv->compute_movement = compute;
516 }
517 
518 /**
519  * gclue_location_source_get_time_threshold
520  * @source: a #GClueLocationSource
521  *
522  * Returns: (transfer none): The current time-threshold object, or NULL if
523  * @source is not a valid #GClueLocationSource instance.
524  **/
525 GClueMinUINT *
gclue_location_source_get_time_threshold(GClueLocationSource * source)526 gclue_location_source_get_time_threshold (GClueLocationSource *source)
527 {
528         g_return_val_if_fail (GCLUE_IS_LOCATION_SOURCE (source), NULL);
529 
530         return source->priv->time_threshold;
531 }
532