gclue-locator.c 19.8 KB
Newer Older
Zeeshan Ali's avatar
Zeeshan Ali committed
1
/* vim: set et ts=8 sw=8: */
Zeeshan Ali's avatar
Zeeshan Ali committed
2 3
/* gclue-locator.c
 *
4
 * Copyright 2013 Red Hat, Inc.
Zeeshan Ali's avatar
Zeeshan Ali committed
5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
 *
 * Geoclue is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2 of the License, or (at your option)
 * any later version.
 *
 * Geoclue is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along
 * with Geoclue; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 * Authors: Zeeshan Ali (Khattak) <zeeshanak@gnome.org>
 */

23 24
#include "config.h"

Zeeshan Ali's avatar
Zeeshan Ali committed
25 26 27
#include <glib/gi18n.h>

#include "gclue-locator.h"
28

29
#include "gclue-wifi.h"
Ankit's avatar
Ankit committed
30
#include "gclue-config.h"
31 32

#if GCLUE_USE_3G_SOURCE
33
#include "gclue-3g.h"
34 35
#endif

Zeeshan Ali's avatar
Zeeshan Ali committed
36 37 38 39
#if GCLUE_USE_CDMA_SOURCE
#include "gclue-cdma.h"
#endif

40
#if GCLUE_USE_MODEM_GPS_SOURCE
41
#include "gclue-modem-gps.h"
42
#endif
Zeeshan Ali's avatar
Zeeshan Ali committed
43

Ankit's avatar
Ankit committed
44 45 46 47
#if GCLUE_USE_NMEA_SOURCE
#include "gclue-nmea-source.h"
#endif

48 49 50
/* This class is like a master location source that hides all individual
 * location sources from rest of the code
 */
Zeeshan Ali's avatar
Zeeshan Ali committed
51

52 53 54 55 56
static gboolean
gclue_locator_start (GClueLocationSource *source);
static gboolean
gclue_locator_stop (GClueLocationSource *source);

Zeeshan Ali's avatar
Zeeshan Ali committed
57 58
struct _GClueLocatorPrivate
{
59
        GList *sources;
60
        GList *active_sources;
Zeeshan Ali's avatar
Zeeshan Ali committed
61

62
        GClueAccuracyLevel accuracy_level;
63 64

        guint time_threshold;
Zeeshan Ali's avatar
Zeeshan Ali committed
65 66
};

67 68 69 70 71
G_DEFINE_TYPE_WITH_CODE (GClueLocator,
                         gclue_locator,
                         GCLUE_TYPE_LOCATION_SOURCE,
                         G_ADD_PRIVATE (GClueLocator))

Zeeshan Ali's avatar
Zeeshan Ali committed
72 73 74
enum
{
        PROP_0,
75
        PROP_ACCURACY_LEVEL,
Zeeshan Ali's avatar
Zeeshan Ali committed
76 77 78 79 80
        LAST_PROP
};

static GParamSpec *gParamSpecs[LAST_PROP];

81
static void
Ankit's avatar
Ankit committed
82 83
set_location (GClueLocator  *locator,
              GClueLocation *location)
84
{
Ankit's avatar
Ankit committed
85
        GClueLocation *cur_location;
86

87 88
        cur_location = gclue_location_source_get_location
                        (GCLUE_LOCATION_SOURCE (locator));
89 90 91

        g_debug ("New location available");

92
        if (cur_location != NULL) {
93 94
            if (gclue_location_get_timestamp (location) <
                gclue_location_get_timestamp (cur_location)) {
95 96 97 98
                    g_debug ("New location older than current, ignoring.");
                    return;
            }

99 100 101 102 103
            if (gclue_location_get_distance_from (location, cur_location)
                * 1000 <
                gclue_location_get_accuracy (location) &&
                gclue_location_get_accuracy (location) >
                gclue_location_get_accuracy (cur_location)) {
104 105 106 107 108 109 110
                    /* We only take the new location if either the previous one
                     * lies outside its accuracy circle or its more or as
                     * accurate as previous one.
                     */
                    g_debug ("Ignoring less accurate new location");
                    return;
            }
111 112
        }

113 114 115 116
        gclue_location_source_set_location (GCLUE_LOCATION_SOURCE (locator),
                                            location);
}

117 118 119 120 121 122 123 124 125 126 127 128
static gint
compare_accuracy_level (GClueLocationSource *src_a,
                        GClueLocationSource *src_b)
{
        GClueAccuracyLevel level_a, level_b;

        level_a = gclue_location_source_get_available_accuracy_level (src_a);
        level_b = gclue_location_source_get_available_accuracy_level (src_b);

        return (level_b - level_a);
}

129 130 131 132 133
static void
refresh_available_accuracy_level (GClueLocator *locator)
{
        GClueAccuracyLevel new, existing;

134 135 136 137 138 139 140 141 142 143 144
        /* Sort the sources according to their accuracy level so that the head
         * of the list will have the highest level. The goal is to start the
         * most accurate source first and when all sources are already active
         * for an app, a second app to get the most accurate location only.
         */
        locator->priv->sources = g_list_sort
                        (locator->priv->sources,
                         (GCompareFunc) compare_accuracy_level);

        new = gclue_location_source_get_available_accuracy_level
                        (GCLUE_LOCATION_SOURCE (locator->priv->sources->data));
145 146 147 148 149 150 151 152 153 154

        existing = gclue_location_source_get_available_accuracy_level
                        (GCLUE_LOCATION_SOURCE (locator));

        if (new != existing)
                g_object_set (G_OBJECT (locator),
                              "available-accuracy-level", new,
                              NULL);
}

155 156 157 158 159 160 161
static void
on_location_changed (GObject    *gobject,
                     GParamSpec *pspec,
                     gpointer    user_data)
{
        GClueLocator *locator = GCLUE_LOCATOR (user_data);
        GClueLocationSource *source = GCLUE_LOCATION_SOURCE (gobject);
Ankit's avatar
Ankit committed
162
        GClueLocation *location;
163 164 165 166 167

        location = gclue_location_source_get_location (source);
        set_location (locator, location);
}

168 169 170 171 172 173 174
static gboolean
is_source_active (GClueLocator        *locator,
                  GClueLocationSource *src)
{
        return (g_list_find (locator->priv->active_sources, src) != NULL);
}

175 176 177 178
static void
start_source (GClueLocator        *locator,
              GClueLocationSource *src)
{
Ankit's avatar
Ankit committed
179
        GClueLocation *location;
180 181 182 183 184 185 186 187 188 189 190

        g_signal_connect (G_OBJECT (src),
                          "notify::location",
                          G_CALLBACK (on_location_changed),
                          locator);

        location = gclue_location_source_get_location (src);
        if (gclue_location_source_get_active (src) && location != NULL)
                set_location (locator, location);

        gclue_location_source_start (src);
191 192
}

193 194 195 196 197
static void
on_avail_accuracy_level_changed (GObject    *gobject,
                                 GParamSpec *pspec,
                                 gpointer    user_data)
{
198
        GClueLocationSource *src = GCLUE_LOCATION_SOURCE (gobject);
199
        GClueLocator *locator = GCLUE_LOCATOR (user_data);
200 201 202
        GClueLocatorPrivate *priv = locator->priv;
        GClueAccuracyLevel level;
        gboolean active;
203 204

        refresh_available_accuracy_level (locator);
205 206 207 208 209 210 211 212 213 214 215

        active = gclue_location_source_get_active
                (GCLUE_LOCATION_SOURCE (locator));
        if (!active)
                return;

        level = gclue_location_source_get_available_accuracy_level (src);
        if (level != GCLUE_ACCURACY_LEVEL_NONE &&
            priv->accuracy_level >= level &&
            !is_source_active (locator, src)) {
                start_source (locator, src);
216 217 218

                priv->active_sources =
                        g_list_append (locator->priv->active_sources, src);
219
        } else if ((level == GCLUE_ACCURACY_LEVEL_NONE ||
220
                    priv->accuracy_level < level) &&
221 222 223 224 225 226 227 228
                   is_source_active (locator, src)) {
                g_signal_handlers_disconnect_by_func (G_OBJECT (src),
                                                      G_CALLBACK (on_location_changed),
                                                      locator);
                gclue_location_source_stop (src);
                priv->active_sources = g_list_remove (priv->active_sources,
                                                      src);
        }
229 230
}

231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259
static void
reset_time_threshold (GClueLocator        *locator,
                      GClueLocationSource *source,
                      guint                value)
{
        GClueMinUINT *threshold;

        threshold = gclue_location_source_get_time_threshold (source);

        gclue_min_uint_add_value (threshold, value, G_OBJECT (locator));
}

static void
on_time_threshold_changed (GObject    *gobject,
                           GParamSpec *pspec,
                           gpointer    user_data)
{
        GClueMinUINT *threshold = GCLUE_MIN_UINT (gobject);
        GClueLocator *locator = GCLUE_LOCATOR (user_data);
        guint value = gclue_min_uint_get_value (threshold);
        GList *node;

        for (node = locator->priv->sources; node != NULL; node = node->next) {
                reset_time_threshold (locator,
                                      GCLUE_LOCATION_SOURCE (node->data),
                                      value);
        }
}

Zeeshan Ali's avatar
Zeeshan Ali committed
260 261 262 263 264 265 266 267 268
static void
gclue_locator_get_property (GObject    *object,
                            guint       prop_id,
                            GValue     *value,
                            GParamSpec *pspec)
{
        GClueLocator *locator = GCLUE_LOCATOR (object);

        switch (prop_id) {
269 270 271 272
        case PROP_ACCURACY_LEVEL:
                g_value_set_enum (value, locator->priv->accuracy_level);
                break;

273 274 275 276 277 278 279 280 281 282 283
        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
        }
}

static void
gclue_locator_set_property (GObject      *object,
                            guint         prop_id,
                            const GValue *value,
                            GParamSpec   *pspec)
{
284 285 286 287 288 289 290 291 292 293
        GClueLocator *locator = GCLUE_LOCATOR (object);

        switch (prop_id) {
        case PROP_ACCURACY_LEVEL:
                locator->priv->accuracy_level = g_value_get_enum (value);
                break;

        default:
                G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
        }
Zeeshan Ali's avatar
Zeeshan Ali committed
294 295
}

296 297 298 299 300
static void
gclue_locator_finalize (GObject *gsource)
{
        GClueLocator *locator = GCLUE_LOCATOR (gsource);
        GClueLocatorPrivate *priv = locator->priv;
301
        GList *node;
302
        GClueMinUINT *threshold;
303

304 305
        G_OBJECT_CLASS (gclue_locator_parent_class)->finalize (gsource);

306 307 308 309 310 311 312 313
        threshold = gclue_location_source_get_time_threshold
                        (GCLUE_LOCATION_SOURCE (locator));
        g_signal_handlers_disconnect_by_func
                (G_OBJECT (threshold),
                 G_CALLBACK (on_time_threshold_changed),
                 locator);

        for (node = locator->priv->sources; node != NULL; node = node->next) {
314 315 316 317
                g_signal_handlers_disconnect_by_func
                        (G_OBJECT (node->data),
                         G_CALLBACK (on_avail_accuracy_level_changed),
                         locator);
318
        }
319 320 321 322 323 324 325
        for (node = locator->priv->active_sources; node != NULL; node = node->next) {
                g_signal_handlers_disconnect_by_func
                        (G_OBJECT (node->data),
                         G_CALLBACK (on_location_changed),
                         locator);
                gclue_location_source_stop (GCLUE_LOCATION_SOURCE (node->data));
        }
326 327
        g_list_free_full (priv->sources, g_object_unref);
        priv->sources = NULL;
328
        priv->active_sources = NULL;
329 330
}

331 332 333 334
static void
gclue_locator_constructed (GObject *object)
{
        GClueLocator *locator = GCLUE_LOCATOR (object);
335
        GClueLocationSource *submit_source = NULL;
Ankit's avatar
Ankit committed
336
        GClueConfig *gconfig = gclue_config_get_singleton ();
337
        GClueWifi *wifi;
338
        GList *node;
339
        GClueMinUINT *threshold;
340

341 342
        G_OBJECT_CLASS (gclue_locator_parent_class)->constructed (object);

343
#if GCLUE_USE_3G_SOURCE
344 345 346 347 348
        if (gclue_config_get_enable_3g_source (gconfig)) {
                GClue3G *source = gclue_3g_get_singleton ();
                locator->priv->sources = g_list_append (locator->priv->sources,
                                                        source);
        }
Zeeshan Ali's avatar
Zeeshan Ali committed
349 350
#endif
#if GCLUE_USE_CDMA_SOURCE
351 352 353 354 355
        if (gclue_config_get_enable_cdma_source (gconfig)) {
                GClueCDMA *cdma = gclue_cdma_get_singleton ();
                locator->priv->sources = g_list_append (locator->priv->sources,
                                                        cdma);
        }
356
#endif
357 358 359 360 361 362
        if (gclue_config_get_enable_wifi_source (gconfig))
                wifi = gclue_wifi_get_singleton (locator->priv->accuracy_level);
        else
                /* City-level accuracy will give us GeoIP-only source */
                wifi = gclue_wifi_get_singleton (GCLUE_ACCURACY_LEVEL_CITY);
        locator->priv->sources = g_list_append (locator->priv->sources, wifi);
363
#if GCLUE_USE_MODEM_GPS_SOURCE
364 365 366 367 368 369
        if (gclue_config_get_enable_modem_gps_source (gconfig)) {
                GClueModemGPS *gps = gclue_modem_gps_get_singleton ();
                locator->priv->sources = g_list_append (locator->priv->sources,
                                                        gps);
                submit_source = GCLUE_LOCATION_SOURCE (gps);
        }
370
#endif
Ankit's avatar
Ankit committed
371
#if GCLUE_USE_NMEA_SOURCE
Ankit's avatar
Ankit committed
372
        if (gclue_config_get_enable_nmea_source (gconfig)) {
Ankit's avatar
Ankit committed
373 374 375 376 377
                GClueNMEASource *nmea = gclue_nmea_source_get_singleton ();
                locator->priv->sources = g_list_append (locator->priv->sources,
                                                        nmea);
        }
#endif
378

379
        for (node = locator->priv->sources; node != NULL; node = node->next) {
380 381 382 383 384
                g_signal_connect (G_OBJECT (node->data),
                                  "notify::available-accuracy-level",
                                  G_CALLBACK (on_avail_accuracy_level_changed),
                                  locator);

385
                if (submit_source != NULL && GCLUE_IS_WEB_SOURCE (node->data))
386
                        gclue_web_source_set_submit_source
387
                                (GCLUE_WEB_SOURCE (node->data), submit_source);
388
        }
389 390 391 392 393 394 395

        threshold = gclue_location_source_get_time_threshold
                        (GCLUE_LOCATION_SOURCE (locator));
        g_signal_connect (G_OBJECT (threshold),
                          "notify::value",
                          G_CALLBACK (on_time_threshold_changed),
                          locator);
396
        refresh_available_accuracy_level (locator);
397 398
}

Zeeshan Ali's avatar
Zeeshan Ali committed
399 400 401
static void
gclue_locator_class_init (GClueLocatorClass *klass)
{
402
        GClueLocationSourceClass *source_class = GCLUE_LOCATION_SOURCE_CLASS (klass);
Zeeshan Ali's avatar
Zeeshan Ali committed
403 404
        GObjectClass *object_class;

405 406 407
        source_class->start = gclue_locator_start;
        source_class->stop = gclue_locator_stop;

Zeeshan Ali's avatar
Zeeshan Ali committed
408 409
        object_class = G_OBJECT_CLASS (klass);
        object_class->get_property = gclue_locator_get_property;
410
        object_class->set_property = gclue_locator_set_property;
411
        object_class->finalize = gclue_locator_finalize;
412
        object_class->constructed = gclue_locator_constructed;
Zeeshan Ali's avatar
Zeeshan Ali committed
413

414 415 416 417 418
        gParamSpecs[PROP_ACCURACY_LEVEL] = g_param_spec_enum ("accuracy-level",
                                                              "AccuracyLevel",
                                                              "Accuracy level",
                                                              GCLUE_TYPE_ACCURACY_LEVEL,
                                                              GCLUE_ACCURACY_LEVEL_CITY,
419 420
                                                              G_PARAM_READWRITE |
                                                              G_PARAM_CONSTRUCT_ONLY);
421 422 423
        g_object_class_install_property (object_class,
                                         PROP_ACCURACY_LEVEL,
                                         gParamSpecs[PROP_ACCURACY_LEVEL]);
Zeeshan Ali's avatar
Zeeshan Ali committed
424 425 426 427 428
}

static void
gclue_locator_init (GClueLocator *locator)
{
429 430 431 432
        locator->priv =
                G_TYPE_INSTANCE_GET_PRIVATE (locator,
                                            GCLUE_TYPE_LOCATOR,
                                            GClueLocatorPrivate);
Zeeshan Ali's avatar
Zeeshan Ali committed
433 434
}

435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450
static gboolean
gclue_locator_start (GClueLocationSource *source)
{
        GClueLocationSourceClass *base_class;
        GClueLocator *locator;
        GList *node;

        g_return_val_if_fail (GCLUE_IS_LOCATOR (source), FALSE);
        locator = GCLUE_LOCATOR (source);

        base_class = GCLUE_LOCATION_SOURCE_CLASS (gclue_locator_parent_class);
        if (!base_class->start (source))
                return FALSE;

        for (node = locator->priv->sources; node != NULL; node = node->next) {
                GClueLocationSource *src = GCLUE_LOCATION_SOURCE (node->data);
451 452 453 454
                GClueAccuracyLevel level;

                level = gclue_location_source_get_available_accuracy_level (src);
                if (level > locator->priv->accuracy_level ||
455 456 457 458 459 460
                    level == GCLUE_ACCURACY_LEVEL_NONE) {
                        g_debug ("Not starting %s (accuracy level: %u). "
                                 "Requested accuracy level: %u.",
                                 G_OBJECT_TYPE_NAME (src),
                                 level,
                                 locator->priv->accuracy_level);
461
                        continue;
462
                }
463 464 465

                locator->priv->active_sources = g_list_append (locator->priv->active_sources,
                                                               src);
466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486

                start_source (locator, src);
        }

        return TRUE;
}

static gboolean
gclue_locator_stop (GClueLocationSource *source)
{
        GClueLocationSourceClass *base_class;
        GClueLocator *locator;
        GList *node;

        g_return_val_if_fail (GCLUE_IS_LOCATOR (source), FALSE);
        locator = GCLUE_LOCATOR (source);

        base_class = GCLUE_LOCATION_SOURCE_CLASS (gclue_locator_parent_class);
        if (!base_class->stop (source))
                return FALSE;

487
        for (node = locator->priv->active_sources; node != NULL; node = node->next) {
488 489 490 491 492 493
                GClueLocationSource *src = GCLUE_LOCATION_SOURCE (node->data);

                g_signal_handlers_disconnect_by_func (G_OBJECT (src),
                                                      G_CALLBACK (on_location_changed),
                                                      locator);
                gclue_location_source_stop (src);
494
                g_debug ("Requested %s to stop", G_OBJECT_TYPE_NAME (src));
495 496
        }

497 498
        g_list_free (locator->priv->active_sources);
        locator->priv->active_sources = NULL;
499 500 501
        return TRUE;
}

502 503
GClueLocator *
gclue_locator_new (GClueAccuracyLevel level)
504
{
505 506 507 508 509 510 511 512 513 514 515
        GClueAccuracyLevel accuracy_level = level;

        if (accuracy_level == GCLUE_ACCURACY_LEVEL_COUNTRY)
                /* There is no source that provides country-level accuracy.
                 * Since Wifi (as geoip) source is the best we can do, accuracy
                 * really is country-level many times from this source and its
                 * doubtful app (or user) will mind being given slighly more
                 * accurate location, lets just map this to city-level accuracy.
                 */
                accuracy_level = GCLUE_ACCURACY_LEVEL_CITY;

516
        return g_object_new (GCLUE_TYPE_LOCATOR,
517
                             "accuracy-level", accuracy_level,
518
                             "compute-movement", FALSE,
519
                             NULL);
520 521
}

522 523 524 525
GClueAccuracyLevel
gclue_locator_get_accuracy_level (GClueLocator *locator)
{
        g_return_val_if_fail (GCLUE_IS_LOCATOR (locator),
526
                              GCLUE_ACCURACY_LEVEL_NONE);
527 528 529

        return locator->priv->accuracy_level;
}
530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571

/**
 * gclue_locator_get_time_threshold
 * @locator: a #GClueLocator
 *
 * Returns: The current time-threshold in seconds.
 **/
guint
gclue_locator_get_time_threshold (GClueLocator *locator)
{
        GClueMinUINT *threshold;

        g_return_val_if_fail (GCLUE_IS_LOCATOR (locator), 0);

        threshold = gclue_location_source_get_time_threshold
                        (GCLUE_LOCATION_SOURCE (locator));

        return gclue_min_uint_get_value (threshold);
}

/**
 * gclue_locator_set_time_threshold
 * @locator: a #GClueLocator
 * @value: The new threshold value
 *
 * Sets the time-threshold to @value.
 *
 * Unlike other (real) location sources, Locator instances are unique for each
 * client application. Which means we only need just one time-threshold value
 * and hence the reason we have these getter and setters, instead of making use
 * of the #GClueLocationSource:time-threshold property.
 **/
void
gclue_locator_set_time_threshold (GClueLocator *locator,
                                  guint         value)
{
        g_return_if_fail (GCLUE_IS_LOCATOR (locator));

        reset_time_threshold (locator,
                              GCLUE_LOCATION_SOURCE (locator),
                              value);
}