1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 Jolla Ltd.
4 ** Contact: Aaron McCarthy <aaron.mccarthy@jollamobile.com>
5 ** Copyright (C) 2016 The Qt Company Ltd.
6 ** Contact: https://www.qt.io/licensing/
7 **
8 ** This file is part of the QtPositioning module of the Qt Toolkit.
9 **
10 ** $QT_BEGIN_LICENSE:LGPL$
11 ** Commercial License Usage
12 ** Licensees holding valid commercial Qt licenses may use this file in
13 ** accordance with the commercial license agreement provided with the
14 ** Software or, alternatively, in accordance with the terms contained in
15 ** a written agreement between you and The Qt Company. For licensing terms
16 ** and conditions see https://www.qt.io/terms-conditions. For further
17 ** information use the contact form at https://www.qt.io/contact-us.
18 **
19 ** GNU Lesser General Public License Usage
20 ** Alternatively, this file may be used under the terms of the GNU Lesser
21 ** General Public License version 3 as published by the Free Software
22 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
23 ** packaging of this file. Please review the following information to
24 ** ensure the GNU Lesser General Public License version 3 requirements
25 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
26 **
27 ** GNU General Public License Usage
28 ** Alternatively, this file may be used under the terms of the GNU
29 ** General Public License version 2.0 or (at your option) the GNU General
30 ** Public license version 3 or any later version approved by the KDE Free
31 ** Qt Foundation. The licenses are as published by the Free Software
32 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
33 ** included in the packaging of this file. Please review the following
34 ** information to ensure the GNU General Public License requirements will
35 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
36 ** https://www.gnu.org/licenses/gpl-3.0.html.
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41
42 #include "qgeopositioninfosource_geocluemaster.h"
43
44 #include <geoclue_interface.h>
45 #include <position_interface.h>
46 #include <velocity_interface.h>
47
48 #include <QtCore/QDateTime>
49 #include <QtCore/QFile>
50 #include <QtCore/QSaveFile>
51 #include <QtCore/QStandardPaths>
52 #include <QtCore/QVariantMap>
53 #include <QtCore/QtNumeric>
54 #include <QtCore/QLoggingCategory>
55 #include <QtDBus/QDBusMetaType>
56
57 #ifndef QT_NO_DATASTREAM
58 #include <QtCore/QDataStream>
59 #endif
60
61 Q_DECLARE_LOGGING_CATEGORY(lcPositioningGeoclue)
62
63 #define MINIMUM_UPDATE_INTERVAL 1000
64 #define UPDATE_TIMEOUT_COLD_START 120000
65
66 QT_BEGIN_NAMESPACE
67
68 namespace
69 {
70
knotsToMetersPerSecond(double knots)71 double knotsToMetersPerSecond(double knots)
72 {
73 return knots * 1852.0 / 3600.0;
74 }
75
76 }
77
QGeoPositionInfoSourceGeoclueMaster(QObject * parent)78 QGeoPositionInfoSourceGeoclueMaster::QGeoPositionInfoSourceGeoclueMaster(QObject *parent)
79 : QGeoPositionInfoSource(parent), m_master(new QGeoclueMaster(this)), m_provider(0), m_pos(0),
80 m_vel(0), m_requestTimer(this), m_lastVelocityIsFresh(false), m_regularUpdateTimedOut(false),
81 m_lastVelocity(qQNaN()), m_lastDirection(qQNaN()), m_lastClimb(qQNaN()), m_lastPositionFromSatellite(false),
82 m_running(false), m_error(NoError)
83 {
84 qDBusRegisterMetaType<Accuracy>();
85
86 #ifndef QT_NO_DATASTREAM
87 // Load the last known location
88 QFile file(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) +
89 QStringLiteral("/qtposition-geoclue"));
90 if (file.open(QIODevice::ReadOnly)) {
91 QDataStream out(&file);
92 out >> m_lastPosition;
93 }
94 #endif
95
96 connect(m_master, SIGNAL(positionProviderChanged(QString,QString,QString,QString)),
97 this, SLOT(positionProviderChanged(QString,QString,QString,QString)));
98
99 m_requestTimer.setSingleShot(true);
100 connect(&m_requestTimer, SIGNAL(timeout()), this, SLOT(requestUpdateTimeout()));
101
102 setPreferredPositioningMethods(AllPositioningMethods);
103 }
104
~QGeoPositionInfoSourceGeoclueMaster()105 QGeoPositionInfoSourceGeoclueMaster::~QGeoPositionInfoSourceGeoclueMaster()
106 {
107 #if !defined(QT_NO_DATASTREAM) && QT_CONFIG(temporaryfile)
108 if (m_lastPosition.isValid()) {
109 QSaveFile file(QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) +
110 QStringLiteral("/qtposition-geoclue"));
111 if (file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
112 QDataStream out(&file);
113 // Only save position and timestamp.
114 out << QGeoPositionInfo(m_lastPosition.coordinate(), m_lastPosition.timestamp());
115 file.commit();
116 }
117 }
118 #endif
119
120 cleanupPositionSource();
121 }
122
positionUpdateFailed()123 void QGeoPositionInfoSourceGeoclueMaster::positionUpdateFailed()
124 {
125 qCDebug(lcPositioningGeoclue) << "position update failed.";
126
127 m_lastVelocityIsFresh = false;
128 if (m_running && !m_regularUpdateTimedOut) {
129 m_regularUpdateTimedOut = true;
130 emit updateTimeout();
131 }
132 }
133
updatePosition(PositionFields fields,int timestamp,double latitude,double longitude,double altitude,Accuracy accuracy)134 void QGeoPositionInfoSourceGeoclueMaster::updatePosition(PositionFields fields, int timestamp,
135 double latitude, double longitude,
136 double altitude, Accuracy accuracy)
137 {
138 if (m_requestTimer.isActive())
139 m_requestTimer.stop();
140
141 QGeoCoordinate coordinate(latitude, longitude);
142 if (fields & Altitude)
143 coordinate.setAltitude(altitude);
144
145 m_lastPosition = QGeoPositionInfo(coordinate, QDateTime::fromSecsSinceEpoch(timestamp));
146
147 m_lastPositionFromSatellite = accuracy.level() == Accuracy::Detailed;
148
149 if (!qIsNaN(accuracy.horizontal()))
150 m_lastPosition.setAttribute(QGeoPositionInfo::HorizontalAccuracy, accuracy.horizontal());
151 if (!qIsNaN(accuracy.vertical()))
152 m_lastPosition.setAttribute(QGeoPositionInfo::VerticalAccuracy, accuracy.vertical());
153
154 if (m_lastVelocityIsFresh) {
155 if (!qIsNaN(m_lastVelocity))
156 m_lastPosition.setAttribute(QGeoPositionInfo::GroundSpeed, m_lastVelocity);
157 if (!qIsNaN(m_lastDirection))
158 m_lastPosition.setAttribute(QGeoPositionInfo::Direction, m_lastDirection);
159 if (!qIsNaN(m_lastClimb))
160 m_lastPosition.setAttribute(QGeoPositionInfo::VerticalSpeed, m_lastClimb);
161 m_lastVelocityIsFresh = false;
162 }
163
164 m_regularUpdateTimedOut = false;
165
166 emit positionUpdated(m_lastPosition);
167
168 qCDebug(lcPositioningGeoclue) << m_lastPosition;
169
170 // Only stop positioning if regular updates not active.
171 if (!m_running) {
172 cleanupPositionSource();
173 m_master->releaseMasterClient();
174 }
175 }
176
velocityUpdateFailed()177 void QGeoPositionInfoSourceGeoclueMaster::velocityUpdateFailed()
178 {
179 qCDebug(lcPositioningGeoclue) << "velocity update failed.";
180
181 // Set the velocitydata non-fresh.
182 m_lastVelocityIsFresh = false;
183 }
184
updateVelocity(VelocityFields fields,int timestamp,double speed,double direction,double climb)185 void QGeoPositionInfoSourceGeoclueMaster::updateVelocity(VelocityFields fields, int timestamp,
186 double speed, double direction,
187 double climb)
188 {
189 Q_UNUSED(timestamp);
190
191 // Store the velocity and mark it as fresh. Simple but hopefully adequate.
192 m_lastVelocity = (fields & Speed) ? knotsToMetersPerSecond(speed) : qQNaN();
193 m_lastDirection = (fields & Direction) ? direction : qQNaN();
194 m_lastClimb = (fields & Climb) ? climb : qQNaN();
195 m_lastVelocityIsFresh = true;
196
197 qCDebug(lcPositioningGeoclue) << m_lastVelocity << m_lastDirection << m_lastClimb;
198 }
199
cleanupPositionSource()200 void QGeoPositionInfoSourceGeoclueMaster::cleanupPositionSource()
201 {
202 qCDebug(lcPositioningGeoclue) << "cleaning up position source";
203
204 if (m_provider)
205 m_provider->RemoveReference();
206 delete m_provider;
207 m_provider = 0;
208 delete m_pos;
209 m_pos = 0;
210 delete m_vel;
211 m_vel = 0;
212 }
213
setOptions()214 void QGeoPositionInfoSourceGeoclueMaster::setOptions()
215 {
216 if (!m_provider)
217 return;
218
219 QVariantMap options;
220 options.insert(QStringLiteral("UpdateInterval"), updateInterval());
221
222 m_provider->SetOptions(options);
223 }
224
setUpdateInterval(int msec)225 void QGeoPositionInfoSourceGeoclueMaster::setUpdateInterval(int msec)
226 {
227 QGeoPositionInfoSource::setUpdateInterval(qMax(minimumUpdateInterval(), msec));
228 setOptions();
229 }
230
setPreferredPositioningMethods(PositioningMethods methods)231 void QGeoPositionInfoSourceGeoclueMaster::setPreferredPositioningMethods(PositioningMethods methods)
232 {
233 PositioningMethods previousPreferredPositioningMethods = preferredPositioningMethods();
234 QGeoPositionInfoSource::setPreferredPositioningMethods(methods);
235 if (previousPreferredPositioningMethods == preferredPositioningMethods())
236 return;
237
238 qCDebug(lcPositioningGeoclue) << "requested to set methods to" << methods
239 << ", and set them to:" << preferredPositioningMethods();
240
241 m_lastVelocityIsFresh = false;
242 m_regularUpdateTimedOut = false;
243
244 // Don't start Geoclue provider until necessary. Don't currently have a master client, no need
245 // no recreate one.
246 if (!m_master->hasMasterClient())
247 return;
248
249 // Free potential previous sources, because new requirements can't be set for the client
250 // (creating a position object after changing requirements seems to fail).
251 cleanupPositionSource();
252 m_master->releaseMasterClient();
253
254 // Restart Geoclue provider with new requirements.
255 configurePositionSource();
256 setOptions();
257 }
258
lastKnownPosition(bool fromSatellitePositioningMethodsOnly) const259 QGeoPositionInfo QGeoPositionInfoSourceGeoclueMaster::lastKnownPosition(bool fromSatellitePositioningMethodsOnly) const
260 {
261 if (fromSatellitePositioningMethodsOnly && !m_lastPositionFromSatellite)
262 return QGeoPositionInfo();
263
264 return m_lastPosition;
265 }
266
supportedPositioningMethods() const267 QGeoPositionInfoSourceGeoclueMaster::PositioningMethods QGeoPositionInfoSourceGeoclueMaster::supportedPositioningMethods() const
268 {
269 return AllPositioningMethods;
270 }
271
startUpdates()272 void QGeoPositionInfoSourceGeoclueMaster::startUpdates()
273 {
274 if (m_running) {
275 qCDebug(lcPositioningGeoclue) << "already running.";
276 return;
277 }
278
279 m_running = true;
280
281 qCDebug(lcPositioningGeoclue) << "starting updates";
282
283 // Start Geoclue provider.
284 if (!m_master->hasMasterClient()) {
285 configurePositionSource();
286 setOptions();
287 }
288
289 // Emit last known position on start.
290 if (m_lastPosition.isValid()) {
291 QMetaObject::invokeMethod(this, "positionUpdated", Qt::QueuedConnection,
292 Q_ARG(QGeoPositionInfo, m_lastPosition));
293 }
294 }
295
minimumUpdateInterval() const296 int QGeoPositionInfoSourceGeoclueMaster::minimumUpdateInterval() const
297 {
298 return MINIMUM_UPDATE_INTERVAL;
299 }
300
stopUpdates()301 void QGeoPositionInfoSourceGeoclueMaster::stopUpdates()
302 {
303 if (!m_running) {
304 qCDebug(lcPositioningGeoclue) << "already stopped.";
305 return;
306 }
307
308 qCDebug(lcPositioningGeoclue) << "stopping updates";
309
310 if (m_pos) {
311 disconnect(m_pos, SIGNAL(PositionChanged(qint32,qint32,double,double,double,Accuracy)),
312 this, SLOT(positionChanged(qint32,qint32,double,double,double,Accuracy)));
313 }
314
315 if (m_vel) {
316 disconnect(m_vel, SIGNAL(VelocityChanged(qint32,qint32,double,double,double)),
317 this, SLOT(velocityChanged(qint32,qint32,double,double,double)));
318 }
319
320 m_running = false;
321
322 // Only stop positioning if single update not requested.
323 if (!m_requestTimer.isActive()) {
324 cleanupPositionSource();
325 m_master->releaseMasterClient();
326 }
327 }
328
requestUpdate(int timeout)329 void QGeoPositionInfoSourceGeoclueMaster::requestUpdate(int timeout)
330 {
331 if (timeout < minimumUpdateInterval() && timeout != 0) {
332 emit updateTimeout();
333 return;
334 }
335 if (m_requestTimer.isActive()) {
336 qCDebug(lcPositioningGeoclue) << "request timer was active, ignoring startUpdates.";
337 return;
338 }
339
340 if (!m_master->hasMasterClient()) {
341 configurePositionSource();
342 setOptions();
343 }
344
345 // Create better logic for timeout value (specs leave it impl dependant).
346 // Especially if there are active updates ongoing, there is no point of waiting
347 // for whole cold start time.
348 m_requestTimer.start(timeout ? timeout : UPDATE_TIMEOUT_COLD_START);
349
350 if (m_pos) {
351 QDBusPendingReply<qint32, qint32, double, double, double, Accuracy> reply = m_pos->GetPosition();
352 QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this);
353 connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)),
354 this, SLOT(getPositionFinished(QDBusPendingCallWatcher*)));
355 }
356 }
357
positionProviderChanged(const QString & name,const QString & description,const QString & service,const QString & path)358 void QGeoPositionInfoSourceGeoclueMaster::positionProviderChanged(const QString &name,
359 const QString &description,
360 const QString &service,
361 const QString &path)
362 {
363 Q_UNUSED(name);
364 Q_UNUSED(description);
365
366 cleanupPositionSource();
367
368 if (service.isEmpty() || path.isEmpty()) {
369 if (!m_regularUpdateTimedOut) {
370 m_regularUpdateTimedOut = true;
371 emit updateTimeout();
372 }
373 return;
374 }
375
376 qCDebug(lcPositioningGeoclue) << "position provider changed to" << name;
377
378 m_provider = new OrgFreedesktopGeoclueInterface(service, path, QDBusConnection::sessionBus());
379 m_provider->AddReference();
380
381 m_pos = new OrgFreedesktopGeocluePositionInterface(service, path, QDBusConnection::sessionBus());
382
383 if (m_running) {
384 connect(m_pos, SIGNAL(PositionChanged(qint32,qint32,double,double,double,Accuracy)),
385 this, SLOT(positionChanged(qint32,qint32,double,double,double,Accuracy)));
386 }
387
388 // Get the current position immediately.
389 QDBusPendingReply<qint32, qint32, double, double, double, Accuracy> reply = m_pos->GetPosition();
390 QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this);
391 connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)),
392 this, SLOT(getPositionFinished(QDBusPendingCallWatcher*)));
393
394 setOptions();
395
396 m_vel = new OrgFreedesktopGeoclueVelocityInterface(service, path, QDBusConnection::sessionBus());
397 if (m_vel->isValid() && m_running) {
398 connect(m_vel, SIGNAL(VelocityChanged(qint32,qint32,double,double,double)),
399 this, SLOT(velocityChanged(qint32,qint32,double,double,double)));
400 }
401 }
402
requestUpdateTimeout()403 void QGeoPositionInfoSourceGeoclueMaster::requestUpdateTimeout()
404 {
405 qCDebug(lcPositioningGeoclue) << "request update timeout occurred.";
406
407 // If we end up here, there has not been valid position update.
408 emit updateTimeout();
409
410 // Only stop positioning if regular updates not active.
411 if (!m_running) {
412 cleanupPositionSource();
413 m_master->releaseMasterClient();
414 }
415 }
416
getPositionFinished(QDBusPendingCallWatcher * watcher)417 void QGeoPositionInfoSourceGeoclueMaster::getPositionFinished(QDBusPendingCallWatcher *watcher)
418 {
419 QDBusPendingReply<qint32, qint32, double, double, double, Accuracy> reply = *watcher;
420 watcher->deleteLater();
421
422 if (reply.isError())
423 return;
424
425 PositionFields fields = static_cast<PositionFields>(reply.argumentAt<0>());
426
427 qCDebug(lcPositioningGeoclue) << "got position update with fields" << int(fields);
428
429 if (fields & Latitude && fields & Longitude) {
430 qint32 timestamp = reply.argumentAt<1>();
431 double latitude = reply.argumentAt<2>();
432 double longitude = reply.argumentAt<3>();
433 double altitude = reply.argumentAt<4>();
434 Accuracy accuracy = reply.argumentAt<5>();
435 updatePosition(fields, timestamp, latitude, longitude, altitude, accuracy);
436 }
437 }
438
positionChanged(qint32 fields,qint32 timestamp,double latitude,double longitude,double altitude,const Accuracy & accuracy)439 void QGeoPositionInfoSourceGeoclueMaster::positionChanged(qint32 fields, qint32 timestamp, double latitude, double longitude, double altitude, const Accuracy &accuracy)
440 {
441 PositionFields pFields = static_cast<PositionFields>(fields);
442
443 qCDebug(lcPositioningGeoclue) << "position changed with fields" << fields;
444
445 if (pFields & Latitude && pFields & Longitude)
446 updatePosition(pFields, timestamp, latitude, longitude, altitude, accuracy);
447 else
448 positionUpdateFailed();
449 }
450
velocityChanged(qint32 fields,qint32 timestamp,double speed,double direction,double climb)451 void QGeoPositionInfoSourceGeoclueMaster::velocityChanged(qint32 fields, qint32 timestamp, double speed, double direction, double climb)
452 {
453 VelocityFields vFields = static_cast<VelocityFields>(fields);
454
455 if (vFields == NoVelocityFields)
456 velocityUpdateFailed();
457 else
458 updateVelocity(vFields, timestamp, speed, direction, climb);
459 }
460
configurePositionSource()461 void QGeoPositionInfoSourceGeoclueMaster::configurePositionSource()
462 {
463 qCDebug(lcPositioningGeoclue);
464
465 bool created = false;
466
467 switch (preferredPositioningMethods()) {
468 case SatellitePositioningMethods:
469 created = m_master->createMasterClient(Accuracy::Detailed, QGeoclueMaster::ResourceGps);
470 break;
471 case NonSatellitePositioningMethods:
472 created = m_master->createMasterClient(Accuracy::None, QGeoclueMaster::ResourceCell | QGeoclueMaster::ResourceNetwork);
473 break;
474 case AllPositioningMethods:
475 created = m_master->createMasterClient(Accuracy::None, QGeoclueMaster::ResourceAll);
476 break;
477 default:
478 qWarning("QGeoPositionInfoSourceGeoclueMaster unknown preferred method.");
479 m_error = UnknownSourceError;
480 emit QGeoPositionInfoSource::error(m_error);
481 return;
482 }
483
484 if (!created) {
485 m_error = UnknownSourceError;
486 emit QGeoPositionInfoSource::error(m_error);
487 }
488 }
489
error() const490 QGeoPositionInfoSource::Error QGeoPositionInfoSourceGeoclueMaster::error() const
491 {
492 return m_error;
493 }
494
495 QT_END_NAMESPACE
496