1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtPositioning module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "qgeopositioninfosource_android_p.h"
41 #include "jnipositioning.h"
42 //#include <QDebug>
43 #include <QGeoPositionInfo>
44 
45 #define UPDATE_FROM_COLD_START 2*60*1000
46 
47 
QGeoPositionInfoSourceAndroid(QObject * parent)48 QGeoPositionInfoSourceAndroid::QGeoPositionInfoSourceAndroid(QObject *parent) :
49     QGeoPositionInfoSource(parent), updatesRunning(false), m_error(NoError), m_requestTimer(this)
50 {
51     androidClassKeyForUpdate = AndroidPositioning::registerPositionInfoSource(this);
52     androidClassKeyForSingleRequest = AndroidPositioning::registerPositionInfoSource(this);
53 
54     //qDebug() << "androidClassKey: "  << androidClassKeyForUpdate << androidClassKeyForSingleRequest;
55     //by default use all methods
56     setPreferredPositioningMethods(AllPositioningMethods);
57 
58     m_requestTimer.setSingleShot(true);
59     QObject::connect(&m_requestTimer, SIGNAL(timeout()), this, SLOT(requestTimeout()));
60 }
61 
~QGeoPositionInfoSourceAndroid()62 QGeoPositionInfoSourceAndroid::~QGeoPositionInfoSourceAndroid()
63 {
64     stopUpdates();
65 
66     if (m_requestTimer.isActive()) {
67         m_requestTimer.stop();
68         AndroidPositioning::stopUpdates(androidClassKeyForSingleRequest);
69     }
70 
71     AndroidPositioning::unregisterPositionInfoSource(androidClassKeyForUpdate);
72     AndroidPositioning::unregisterPositionInfoSource(androidClassKeyForSingleRequest);
73 }
74 
setUpdateInterval(int msec)75 void QGeoPositionInfoSourceAndroid::setUpdateInterval(int msec)
76 {
77     int previousInterval = updateInterval();
78     msec = (((msec > 0) && (msec < minimumUpdateInterval())) || msec < 0)? minimumUpdateInterval() : msec;
79 
80     if (msec == previousInterval)
81         return;
82 
83     QGeoPositionInfoSource::setUpdateInterval(msec);
84 
85     if (updatesRunning)
86         reconfigureRunningSystem();
87 }
88 
lastKnownPosition(bool fromSatellitePositioningMethodsOnly) const89 QGeoPositionInfo QGeoPositionInfoSourceAndroid::lastKnownPosition(bool fromSatellitePositioningMethodsOnly) const
90 {
91     return AndroidPositioning::lastKnownPosition(fromSatellitePositioningMethodsOnly);
92 }
93 
supportedPositioningMethods() const94 QGeoPositionInfoSource::PositioningMethods QGeoPositionInfoSourceAndroid::supportedPositioningMethods() const
95 {
96     return AndroidPositioning::availableProviders();
97 }
98 
setPreferredPositioningMethods(QGeoPositionInfoSource::PositioningMethods methods)99 void QGeoPositionInfoSourceAndroid::setPreferredPositioningMethods(QGeoPositionInfoSource::PositioningMethods methods)
100 {
101     PositioningMethods previousPreferredPositioningMethods = preferredPositioningMethods();
102     QGeoPositionInfoSource::setPreferredPositioningMethods(methods);
103     if (previousPreferredPositioningMethods == preferredPositioningMethods())
104         return;
105 
106     if (updatesRunning)
107         reconfigureRunningSystem();
108 }
109 
minimumUpdateInterval() const110 int QGeoPositionInfoSourceAndroid::minimumUpdateInterval() const
111 {
112     return 50;
113 }
114 
error() const115 QGeoPositionInfoSource::Error QGeoPositionInfoSourceAndroid::error() const
116 {
117     return m_error;
118 }
119 
setError(Error error)120 void QGeoPositionInfoSourceAndroid::setError(Error error)
121 {
122     // qDebug() << "setError: " << error;
123     if (error != QGeoPositionInfoSource::NoError)
124     {
125         m_error = error;
126         emit QGeoPositionInfoSource::error(m_error);
127     }
128 }
129 
startUpdates()130 void QGeoPositionInfoSourceAndroid::startUpdates()
131 {
132     if (updatesRunning)
133         return;
134 
135     if (preferredPositioningMethods() == 0) {
136         setError(UnknownSourceError);
137         return;
138     }
139 
140     updatesRunning = true;
141     QGeoPositionInfoSource::Error error = AndroidPositioning::startUpdates(androidClassKeyForUpdate);
142     if (error != QGeoPositionInfoSource::NoError)
143         updatesRunning = false;
144 
145     setError(error);
146 }
147 
stopUpdates()148 void QGeoPositionInfoSourceAndroid::stopUpdates()
149 {
150     if (!updatesRunning)
151         return;
152 
153     updatesRunning = false;
154     AndroidPositioning::stopUpdates(androidClassKeyForUpdate);
155 }
156 
requestUpdate(int timeout)157 void QGeoPositionInfoSourceAndroid::requestUpdate(int timeout)
158 {
159     if (m_requestTimer.isActive())
160         return;
161 
162     if (timeout != 0 && timeout < minimumUpdateInterval()) {
163         emit updateTimeout();
164         return;
165     }
166 
167     if (timeout == 0)
168         timeout = UPDATE_FROM_COLD_START;
169 
170     m_requestTimer.start(timeout);
171 
172     // if updates already running with interval equal to timeout
173     // then we wait for next update coming through
174     // assume that a single update will not be quicker than regular updates anyway
175     if (updatesRunning && updateInterval() <= timeout)
176         return;
177 
178     QGeoPositionInfoSource::Error error = AndroidPositioning::requestUpdate(androidClassKeyForSingleRequest);
179     if (error != QGeoPositionInfoSource::NoError)
180         m_requestTimer.stop();
181 
182     setError(error);
183 }
184 
processPositionUpdate(const QGeoPositionInfo & pInfo)185 void QGeoPositionInfoSourceAndroid::processPositionUpdate(const QGeoPositionInfo &pInfo)
186 {
187     //single update request and served as part of regular update
188     if (m_requestTimer.isActive())
189         m_requestTimer.stop();
190 
191     emit positionUpdated(pInfo);
192 }
193 
194 // Might still be called multiple times (once for each provider)
processSinglePositionUpdate(const QGeoPositionInfo & pInfo)195 void QGeoPositionInfoSourceAndroid::processSinglePositionUpdate(const QGeoPositionInfo &pInfo)
196 {
197     //timeout but we received a late update -> ignore
198     if (!m_requestTimer.isActive())
199         return;
200 
201     queuedSingleUpdates.append(pInfo);
202 }
203 
locationProviderDisabled()204 void QGeoPositionInfoSourceAndroid::locationProviderDisabled()
205 {
206     setError(QGeoPositionInfoSource::ClosedError);
207 }
208 
locationProvidersChanged()209 void QGeoPositionInfoSourceAndroid::locationProvidersChanged()
210 {
211     emit supportedPositioningMethodsChanged();
212 }
213 
requestTimeout()214 void QGeoPositionInfoSourceAndroid::requestTimeout()
215 {
216     AndroidPositioning::stopUpdates(androidClassKeyForSingleRequest);
217     //no queued update to process -> timeout
218     const int count = queuedSingleUpdates.count();
219 
220     if (!count) {
221         emit updateTimeout();
222         return;
223     }
224 
225     //pick best
226     QGeoPositionInfo best = queuedSingleUpdates[0];
227     for (int i = 1; i < count; i++) {
228         const QGeoPositionInfo info = queuedSingleUpdates[i];
229 
230         //anything newer by 20s is always better
231         const qint64 timeDelta = best.timestamp().secsTo(info.timestamp());
232         if (abs(timeDelta) > 20) {
233             if (timeDelta > 0)
234                 best = info;
235             continue;
236         }
237 
238         //compare accuracy
239         if (info.hasAttribute(QGeoPositionInfo::HorizontalAccuracy) &&
240                 best.hasAttribute(QGeoPositionInfo::HorizontalAccuracy))
241         {
242             best = info.attribute(QGeoPositionInfo::HorizontalAccuracy) <
243                     best.attribute(QGeoPositionInfo::HorizontalAccuracy) ? info : best;
244             continue;
245         }
246 
247         //prefer info with accuracy information
248         if (info.hasAttribute(QGeoPositionInfo::HorizontalAccuracy))
249             best = info;
250     }
251 
252     queuedSingleUpdates.clear();
253     emit positionUpdated(best);
254 }
255 
256 /*
257   Updates the system assuming that updateInterval
258   and/or preferredPositioningMethod have changed.
259  */
reconfigureRunningSystem()260 void QGeoPositionInfoSourceAndroid::reconfigureRunningSystem()
261 {
262     if (!updatesRunning)
263         return;
264 
265     stopUpdates();
266     startUpdates();
267 }
268