1/*
2 * Copyright (C) 2014 Samsung Electronics. All rights reserved.
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) && USE(GEOCLUE2)
30
31#include <wtf/gobject/GUniquePtr.h>
32#include <wtf/gobject/GlibUtilities.h>
33#include <wtf/text/CString.h>
34
35const char* gGeoclueBusName = "org.freedesktop.GeoClue2";
36const char* gGeoclueManagerPath = "/org/freedesktop/GeoClue2/Manager";
37
38using namespace WebCore;
39
40typedef enum {
41    GeoclueAccuracyLevelCountry = 1,
42    GeoclueAccuracyLevelCity = 4,
43    GeoclueAccuracyLevelStreet = 6,
44    GeoclueAccuracyLevelExact = 8,
45} GeoclueAccuracyLevel;
46
47GeolocationProviderGeoclue::GeolocationProviderGeoclue(GeolocationProviderGeoclueClient* client)
48    : m_client(client)
49    , m_latitude(0)
50    , m_longitude(0)
51    , m_altitude(0)
52    , m_accuracy(0)
53    , m_altitudeAccuracy(0)
54    , m_timestamp(0)
55    , m_enableHighAccuracy(false)
56    , m_isUpdating(false)
57{
58    ASSERT(m_client);
59}
60
61GeolocationProviderGeoclue::~GeolocationProviderGeoclue()
62{
63    stopUpdating();
64}
65
66void GeolocationProviderGeoclue::startUpdating()
67{
68    m_isUpdating = true;
69
70    if (!m_managerProxy) {
71        geoclue_manager_proxy_new_for_bus(G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, gGeoclueBusName, gGeoclueManagerPath, nullptr,
72            reinterpret_cast<GAsyncReadyCallback>(createGeoclueManagerProxyCallback), this);
73        return;
74    }
75
76    geoclue_manager_call_get_client(m_managerProxy.get(), nullptr, reinterpret_cast<GAsyncReadyCallback>(getGeoclueClientCallback), this);
77}
78
79void GeolocationProviderGeoclue::stopUpdating()
80{
81    if (m_clientProxy) {
82        geoclue_client_call_stop(m_clientProxy.get(), nullptr, nullptr, nullptr);
83        g_signal_handlers_disconnect_by_func(m_clientProxy.get(), reinterpret_cast<gpointer>(locationUpdatedCallback), this);
84        m_clientProxy = nullptr;
85    }
86    m_isUpdating = false;
87}
88
89void GeolocationProviderGeoclue::setEnableHighAccuracy(bool enable)
90{
91    if (m_enableHighAccuracy == enable)
92        return;
93
94    m_enableHighAccuracy = enable;
95
96    // If we're already updating we should report the new requirements in order
97    // to change to a more suitable provider if needed. If not, return.
98    if (!m_isUpdating)
99        return;
100
101    updateClientRequirements();
102}
103
104void GeolocationProviderGeoclue::createGeoclueManagerProxyCallback(GObject*, GAsyncResult* result, GeolocationProviderGeoclue* provider)
105{
106    GUniqueOutPtr<GError> error;
107    provider->m_managerProxy = adoptGRef(geoclue_manager_proxy_new_for_bus_finish(result, &error.outPtr()));
108    if (error) {
109        provider->errorOccurred(error->message);
110        return;
111    }
112
113    geoclue_manager_call_get_client(provider->m_managerProxy.get(), nullptr, reinterpret_cast<GAsyncReadyCallback>(getGeoclueClientCallback), provider);
114}
115
116void GeolocationProviderGeoclue::getGeoclueClientCallback(GObject* sourceObject, GAsyncResult* result, GeolocationProviderGeoclue* provider)
117{
118    GUniqueOutPtr<GError> error;
119    GUniqueOutPtr<gchar> path;
120    if (!geoclue_manager_call_get_client_finish(GEOCLUE_MANAGER(sourceObject), &path.outPtr(), result, &error.outPtr())) {
121        provider->errorOccurred(error->message);
122        return;
123    }
124
125    geoclue_client_proxy_new_for_bus(G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, gGeoclueBusName, path.get(), nullptr,
126        reinterpret_cast<GAsyncReadyCallback>(createGeoclueClientProxyCallback), provider);
127}
128
129void GeolocationProviderGeoclue::createGeoclueClientProxyCallback(GObject*, GAsyncResult* result, GeolocationProviderGeoclue* provider)
130{
131    GUniqueOutPtr<GError> error;
132    provider->m_clientProxy = adoptGRef(geoclue_client_proxy_new_for_bus_finish(result, &error.outPtr()));
133    if (error) {
134        provider->errorOccurred(error->message);
135        return;
136    }
137
138    // Geoclue2 requires the client to provide a desktop ID for security
139    // reasons, which should identify the application requesting the location.
140    // FIXME: We provide the program name as the desktop ID for now but, in an ideal world,
141    // we should provide a proper "application ID" (normally the name of a .desktop file).
142    // https://bugs.webkit.org/show_bug.cgi?id=129879
143    geoclue_client_set_desktop_id(provider->m_clientProxy.get(), g_get_prgname());
144
145    provider->startGeoclueClient();
146}
147
148void GeolocationProviderGeoclue::startClientCallback(GObject* sourceObject, GAsyncResult* result, GeolocationProviderGeoclue* provider)
149{
150    GUniqueOutPtr<GError> error;
151    if (!geoclue_client_call_start_finish(GEOCLUE_CLIENT(sourceObject), result, &error.outPtr()))
152        static_cast<GeolocationProviderGeoclue*>(provider)->errorOccurred(error->message);
153}
154
155void GeolocationProviderGeoclue::locationUpdatedCallback(GeoclueClient*, const gchar*, const gchar* newPath, GeolocationProviderGeoclue* provider)
156{
157    geoclue_location_proxy_new_for_bus(G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, gGeoclueBusName, newPath, nullptr,
158        reinterpret_cast<GAsyncReadyCallback>(createLocationProxyCallback), provider);
159}
160
161void GeolocationProviderGeoclue::createLocationProxyCallback(GObject*, GAsyncResult* result, GeolocationProviderGeoclue* provider)
162{
163    GUniqueOutPtr<GError> error;
164    GRefPtr<GeoclueLocation> locationProxy = adoptGRef(geoclue_location_proxy_new_for_bus_finish(result, &error.outPtr()));
165    if (error) {
166        provider->errorOccurred(error->message);
167        return;
168    }
169    provider->updateLocation(locationProxy.get());
170}
171
172void GeolocationProviderGeoclue::startGeoclueClient()
173{
174    // Set the requirement for the client.
175    updateClientRequirements();
176
177    g_signal_connect(m_clientProxy.get(), "location-updated", G_CALLBACK(locationUpdatedCallback), this);
178    geoclue_client_call_start(m_clientProxy.get(), nullptr, reinterpret_cast<GAsyncReadyCallback>(startClientCallback), this);
179}
180
181void GeolocationProviderGeoclue::updateLocation(GeoclueLocation* locationProxy)
182{
183    GTimeVal timeValue;
184    g_get_current_time(&timeValue);
185    m_timestamp = timeValue.tv_sec;
186    m_latitude = geoclue_location_get_latitude(locationProxy);
187    m_longitude = geoclue_location_get_longitude(locationProxy);
188    m_accuracy = geoclue_location_get_accuracy(locationProxy);
189    m_client->notifyPositionChanged(m_timestamp, m_latitude, m_longitude, m_altitude, m_accuracy, m_altitudeAccuracy);
190}
191
192void GeolocationProviderGeoclue::errorOccurred(const char* message)
193{
194    m_isUpdating = false;
195    m_client->notifyErrorOccurred(message);
196}
197
198void GeolocationProviderGeoclue::updateClientRequirements()
199{
200    if (!m_clientProxy)
201        return;
202
203    GeoclueAccuracyLevel accuracyLevel = m_enableHighAccuracy ? GeoclueAccuracyLevelExact : GeoclueAccuracyLevelCity;
204    geoclue_client_set_requested_accuracy_level(m_clientProxy.get(), accuracyLevel);
205}
206
207#endif // ENABLE(GEOLOCATION) && USE(GEOCLUE2)
208