1/*
2 * Copyright (C) 2012 Igalia S.L.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "GeolocationProviderGeoclue.h"
28
29#if ENABLE(GEOLOCATION)
30
31#include <wtf/gobject/GOwnPtr.h>
32
33using namespace WebCore;
34
35static void getPositionCallback(GeocluePosition* position, GeocluePositionFields fields, int timestamp, double latitude, double longitude, double altitude, GeoclueAccuracy* accuracy, GError* error, GeolocationProviderGeoclue* provider)
36{
37    if (error) {
38        provider->errorOccurred(error->message);
39        g_error_free(error);
40        return;
41    }
42    provider->positionChanged(position, fields, timestamp, latitude, longitude, altitude, accuracy);
43}
44
45static void positionChangedCallback(GeocluePosition* position, GeocluePositionFields fields, int timestamp, double latitude, double longitude, double altitude, GeoclueAccuracy* accuracy, GeolocationProviderGeoclue* provider)
46{
47    provider->positionChanged(position, fields, timestamp, latitude, longitude, altitude, accuracy);
48}
49
50static void createGeocluePositionCallback(GeoclueMasterClient*, GeocluePosition* position, GError *error, GeolocationProviderGeoclue* provider)
51{
52    if (error) {
53        provider->errorOccurred(error->message);
54        g_error_free(error);
55        return;
56    }
57    provider->initializeGeocluePosition(position);
58}
59
60static void geoclueClientSetRequirementsCallback(GeoclueMasterClient* client, GError* error, GeolocationProviderGeoclue* provider)
61{
62    if (error) {
63        provider->errorOccurred(error->message);
64        g_error_free(error);
65    }
66}
67
68static void createGeoclueClientCallback(GeoclueMaster*, GeoclueMasterClient* client, char*, GError* error, GeolocationProviderGeoclue* provider)
69{
70    if (error) {
71        provider->errorOccurred(error->message);
72        g_error_free(error);
73        return;
74    }
75    provider->initializeGeoclueClient(client);
76}
77
78GeolocationProviderGeoclue::GeolocationProviderGeoclue(GeolocationProviderGeoclueClient* client)
79    : m_client(client)
80    , m_geoclueClient(0)
81    , m_geocluePosition(0)
82    , m_latitude(0)
83    , m_longitude(0)
84    , m_altitude(0)
85    , m_accuracy(0)
86    , m_altitudeAccuracy(0)
87    , m_timestamp(0)
88    , m_enableHighAccuracy(false)
89    , m_isUpdating(false)
90{
91    ASSERT(m_client);
92}
93
94GeolocationProviderGeoclue::~GeolocationProviderGeoclue()
95{
96}
97
98void GeolocationProviderGeoclue::startUpdating()
99{
100    ASSERT(!m_geoclueClient);
101
102    GRefPtr<GeoclueMaster> master = adoptGRef(geoclue_master_get_default());
103    geoclue_master_create_client_async(master.get(), reinterpret_cast<GeoclueCreateClientCallback>(createGeoclueClientCallback), this);
104    m_isUpdating = true;
105}
106
107void GeolocationProviderGeoclue::stopUpdating()
108{
109    m_geocluePosition.clear();
110    m_geoclueClient.clear();
111    m_isUpdating = false;
112}
113
114void GeolocationProviderGeoclue::setEnableHighAccuracy(bool enable)
115{
116    m_enableHighAccuracy = enable;
117
118    // If we're already updating we should report the new requirements in order
119    // to change to a more suitable provider if needed. If not, return.
120    if (!m_isUpdating)
121        return;
122
123    updateClientRequirements();
124}
125
126void GeolocationProviderGeoclue::initializeGeoclueClient(GeoclueMasterClient* geoclueClient)
127{
128    ASSERT(GEOCLUE_IS_MASTER_CLIENT(geoclueClient));
129
130    // Store the GeoclueMasterClient object as created by GeoClue.
131    m_geoclueClient = adoptGRef(geoclueClient);
132
133    // Set the requirement for the client and ask it to create a position associated to it.
134    updateClientRequirements();
135    geoclue_master_client_create_position_async(m_geoclueClient.get(), reinterpret_cast<CreatePositionCallback>(createGeocluePositionCallback), this);
136}
137
138void GeolocationProviderGeoclue::initializeGeocluePosition(GeocluePosition* geocluePosition)
139{
140    ASSERT(GEOCLUE_IS_POSITION(geocluePosition));
141
142    // Stores the GeocluePosition object associated with the GeoclueMasterClient.
143    m_geocluePosition = adoptGRef(geocluePosition);
144
145    // Get the initial position and keep updated of further changes.
146    geoclue_position_get_position_async(m_geocluePosition.get(), reinterpret_cast<GeocluePositionCallback>(getPositionCallback), this);
147    g_signal_connect(m_geocluePosition.get(), "position-changed", G_CALLBACK(positionChangedCallback), this);
148}
149
150void GeolocationProviderGeoclue::updateClientRequirements()
151{
152    if (!m_geoclueClient)
153        return;
154
155    GeoclueAccuracyLevel accuracyLevel = m_enableHighAccuracy ? GEOCLUE_ACCURACY_LEVEL_DETAILED : GEOCLUE_ACCURACY_LEVEL_LOCALITY;
156    geoclue_master_client_set_requirements_async(m_geoclueClient.get(), accuracyLevel, 0, false, GEOCLUE_RESOURCE_ALL, reinterpret_cast<GeoclueSetRequirementsCallback>(geoclueClientSetRequirementsCallback), this);
157}
158
159void GeolocationProviderGeoclue::positionChanged(GeocluePosition* position, GeocluePositionFields fields, int timestamp, double latitude, double longitude, double altitude, GeoclueAccuracy* accuracy)
160{
161    if (!(fields & GEOCLUE_POSITION_FIELDS_LATITUDE && fields & GEOCLUE_POSITION_FIELDS_LONGITUDE)) {
162        errorOccurred("Position could not be determined.");
163        return;
164    }
165
166    m_timestamp = timestamp;
167    m_latitude = latitude;
168    m_longitude = longitude;
169    m_altitude = altitude;
170
171    geoclue_accuracy_get_details(accuracy, 0, &m_accuracy, &m_altitudeAccuracy);
172
173    m_client->notifyPositionChanged(m_timestamp, m_latitude, m_longitude, m_altitude, m_accuracy, m_altitudeAccuracy);
174}
175
176void GeolocationProviderGeoclue::errorOccurred(const char* message)
177{
178    m_client->notifyErrorOccurred(message);
179}
180
181#endif // ENABLE(GEOLOCATION)
182