1/*
2 * Copyright 2012, The Android Open Source Project
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 *  * Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 *  * 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 THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "GeolocationClientImpl.h"
28
29#include <Frame.h>
30#include <Page.h>
31#include <GeolocationController.h>
32#include <GeolocationError.h>
33#include <GeolocationPosition.h>
34#include <WebViewCore.h>
35#if PLATFORM(ANDROID)
36// Required for sim-eng build
37#include <math.h>
38#endif
39#include <wtf/CurrentTime.h>
40
41using WebCore::Geolocation;
42using WebCore::GeolocationError;
43using WebCore::GeolocationPosition;
44using WebCore::Timer;
45
46using namespace std;
47
48namespace {
49
50bool isPositionMovement(GeolocationPosition* position1, GeolocationPosition* position2)
51{
52    // For the small distances in which we are likely concerned, it's reasonable
53    // to approximate the distance between the two positions as the sum of the
54    // differences in latitude and longitude.
55    double delta = fabs(position1->latitude() - position2->latitude()) + fabs(position1->longitude() - position2->longitude());
56    // Approximate conversion from degrees of arc to metres.
57    delta *= 60 * 1852;
58    // The threshold is when the distance between the two positions exceeds the
59    // worse (larger) of the two accuracies.
60    int maxAccuracy = max(position1->accuracy(), position2->accuracy());
61    return delta > maxAccuracy;
62}
63
64bool isPositionMoreAccurate(GeolocationPosition* position1, GeolocationPosition* position2)
65{
66    return position2->accuracy() < position1->accuracy();
67}
68
69bool isPositionMoreTimely(GeolocationPosition* position1)
70{
71    double currentTime = WTF::currentTime();
72    double maximumAge = 10 * 60; // 10 minutes
73    return currentTime - position1->timestamp() > maximumAge;
74}
75
76} // anonymous namespace
77
78namespace android {
79
80GeolocationClientImpl::GeolocationClientImpl(WebViewCore* webViewCore)
81    : m_webViewCore(webViewCore)
82    , m_timer(this, &GeolocationClientImpl::timerFired)
83    , m_isSuspended(false)
84    , m_useGps(false)
85{
86}
87
88GeolocationClientImpl::~GeolocationClientImpl()
89{
90}
91
92void GeolocationClientImpl::geolocationDestroyed()
93{
94    // Lifetime is managed by GeolocationManager.
95}
96
97void GeolocationClientImpl::startUpdating()
98{
99    // This method is called every time a new watch or one-shot position request
100    // is started. If we already have a position or an error, call back
101    // immediately.
102    if (m_lastPosition || m_lastError) {
103        m_timer.startOneShot(0);
104    }
105
106    // Lazilly create the Java object.
107    bool haveJavaBridge = m_javaBridge;
108    if (!haveJavaBridge)
109        m_javaBridge.set(new GeolocationServiceBridge(this, m_webViewCore));
110    ASSERT(m_javaBridge);
111
112    // Set whether to use GPS before we start the implementation.
113    m_javaBridge->setEnableGps(m_useGps);
114
115    // If we're suspended, don't start the service. It will be started when we
116    // get the call to resume().
117    if (!haveJavaBridge && !m_isSuspended)
118        m_javaBridge->start();
119}
120
121void GeolocationClientImpl::stopUpdating()
122{
123    // TODO: It would be good to re-use the Java bridge object.
124    m_javaBridge.clear();
125    m_useGps = false;
126    // Reset last position and error to make sure that we always try to get a
127    // new position from the client when a request is first made.
128    m_lastPosition = 0;
129    m_lastError = 0;
130
131    if (m_timer.isActive())
132        m_timer.stop();
133}
134
135void GeolocationClientImpl::setEnableHighAccuracy(bool enableHighAccuracy)
136{
137    // On Android, high power == GPS.
138    m_useGps = enableHighAccuracy;
139    if (m_javaBridge)
140        m_javaBridge->setEnableGps(m_useGps);
141}
142
143GeolocationPosition* GeolocationClientImpl::lastPosition()
144{
145    return m_lastPosition.get();
146}
147
148void GeolocationClientImpl::requestPermission(Geolocation* geolocation)
149{
150    permissions()->queryPermissionState(geolocation->frame());
151}
152
153void GeolocationClientImpl::cancelPermissionRequest(Geolocation* geolocation)
154{
155    permissions()->cancelPermissionStateQuery(geolocation->frame());
156}
157
158// Note that there is no guarantee that subsequent calls to this method offer a
159// more accurate or updated position.
160void GeolocationClientImpl::newPositionAvailable(PassRefPtr<GeolocationPosition> position)
161{
162    ASSERT(position);
163    if (!m_lastPosition
164        || isPositionMovement(m_lastPosition.get(), position.get())
165        || isPositionMoreAccurate(m_lastPosition.get(), position.get())
166        || isPositionMoreTimely(m_lastPosition.get())) {
167        m_lastPosition = position;
168        // Remove the last error.
169        m_lastError = 0;
170        m_webViewCore->mainFrame()->page()->geolocationController()->positionChanged(m_lastPosition.get());
171    }
172}
173
174void GeolocationClientImpl::newErrorAvailable(PassRefPtr<WebCore::GeolocationError> error)
175{
176    ASSERT(error);
177    // We leave the last position
178    m_lastError = error;
179    m_webViewCore->mainFrame()->page()->geolocationController()->errorOccurred(m_lastError.get());
180}
181
182void GeolocationClientImpl::suspend()
183{
184    m_isSuspended = true;
185    if (m_javaBridge)
186        m_javaBridge->stop();
187}
188
189void GeolocationClientImpl::resume()
190{
191    m_isSuspended = false;
192    if (m_javaBridge)
193        m_javaBridge->start();
194}
195
196void GeolocationClientImpl::resetTemporaryPermissionStates()
197{
198    permissions()->resetTemporaryPermissionStates();
199}
200
201void GeolocationClientImpl::providePermissionState(String origin, bool allow, bool remember)
202{
203    permissions()->providePermissionState(origin, allow, remember);
204}
205
206GeolocationPermissions* GeolocationClientImpl::permissions() const
207{
208    if (!m_permissions)
209        m_permissions = new GeolocationPermissions(m_webViewCore);
210    return m_permissions.get();
211}
212
213void GeolocationClientImpl::timerFired(Timer<GeolocationClientImpl>* timer)
214{
215    ASSERT(&m_timer == timer);
216    ASSERT(m_lastPosition || m_lastError);
217    if (m_lastPosition)
218        m_webViewCore->mainFrame()->page()->geolocationController()->positionChanged(m_lastPosition.get());
219    else
220        m_webViewCore->mainFrame()->page()->geolocationController()->errorOccurred(m_lastError.get());
221}
222
223} // namespace android
224