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