1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 Jolla Ltd, author: Aaron McCarthy <aaron.mccarthy@jollamobile.com>
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 "qgeosatelliteinfosource_geocluemaster.h"
41
42 #include <geoclue_interface.h>
43 #include <satellite_interface.h>
44
45 #include <QtCore/QLoggingCategory>
46 #include <QtDBus/QDBusPendingCallWatcher>
47
Q_DECLARE_LOGGING_CATEGORY(lcPositioningGeoclue)48 Q_DECLARE_LOGGING_CATEGORY(lcPositioningGeoclue)
49
50 #define MINIMUM_UPDATE_INTERVAL 1000
51
52 QT_BEGIN_NAMESPACE
53
54 QGeoSatelliteInfoSourceGeoclueMaster::QGeoSatelliteInfoSourceGeoclueMaster(QObject *parent)
55 : QGeoSatelliteInfoSource(parent), m_master(new QGeoclueMaster(this)), m_provider(0), m_sat(0),
56 m_requestTimer(this), m_error(NoError), m_satellitesChangedConnected(false), m_running(false)
57 {
58 connect(m_master, SIGNAL(positionProviderChanged(QString,QString,QString,QString)),
59 this, SLOT(positionProviderChanged(QString,QString,QString,QString)));
60
61 m_requestTimer.setSingleShot(true);
62 connect(&m_requestTimer, SIGNAL(timeout()), this, SLOT(requestUpdateTimeout()));
63 }
64
~QGeoSatelliteInfoSourceGeoclueMaster()65 QGeoSatelliteInfoSourceGeoclueMaster::~QGeoSatelliteInfoSourceGeoclueMaster()
66 {
67 cleanupSatelliteSource();
68 }
69
minimumUpdateInterval() const70 int QGeoSatelliteInfoSourceGeoclueMaster::minimumUpdateInterval() const
71 {
72 return MINIMUM_UPDATE_INTERVAL;
73 }
74
setUpdateInterval(int msec)75 void QGeoSatelliteInfoSourceGeoclueMaster::setUpdateInterval(int msec)
76 {
77 if (msec < 0 || (msec > 0 && msec < MINIMUM_UPDATE_INTERVAL))
78 msec = MINIMUM_UPDATE_INTERVAL;
79
80 QGeoSatelliteInfoSource::setUpdateInterval(msec);
81 }
82
error() const83 QGeoSatelliteInfoSource::Error QGeoSatelliteInfoSourceGeoclueMaster::error() const
84 {
85 return m_error;
86 }
87
startUpdates()88 void QGeoSatelliteInfoSourceGeoclueMaster::startUpdates()
89 {
90 if (m_running)
91 return;
92
93 m_running = true;
94
95 // Start Geoclue provider.
96 if (!m_master->hasMasterClient())
97 configureSatelliteSource();
98
99 m_requestTimer.start(qMax(updateInterval(), minimumUpdateInterval()));
100 }
101
stopUpdates()102 void QGeoSatelliteInfoSourceGeoclueMaster::stopUpdates()
103 {
104 if (!m_running)
105 return;
106
107 if (m_sat) {
108 disconnect(m_sat, SIGNAL(SatelliteChanged(qint32,qint32,qint32,QList<qint32>,QList<QGeoSatelliteInfo>)),
109 this, SLOT(satelliteChanged(qint32,qint32,qint32,QList<qint32>,QList<QGeoSatelliteInfo>)));
110 }
111
112 m_running = false;
113
114 // Only stop positioning if single update not requested.
115 if (!m_requestTimer.isActive()) {
116 cleanupSatelliteSource();
117 m_master->releaseMasterClient();
118 }
119 }
120
requestUpdate(int timeout)121 void QGeoSatelliteInfoSourceGeoclueMaster::requestUpdate(int timeout)
122 {
123 if (timeout < minimumUpdateInterval() && timeout != 0) {
124 emit requestTimeout();
125 return;
126 }
127
128 if (m_requestTimer.isActive())
129 return;
130
131 if (!m_master->hasMasterClient())
132 configureSatelliteSource();
133
134 m_requestTimer.start(qMax(timeout, minimumUpdateInterval()));
135
136 if (m_sat) {
137 QDBusPendingReply<qint32, qint32, qint32, QList<qint32>, QList<QGeoSatelliteInfo> > reply =
138 m_sat->GetSatellite();
139 QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(reply, this);
140 connect(watcher, SIGNAL(finished(QDBusPendingCallWatcher*)),
141 this, SLOT(getSatelliteFinished(QDBusPendingCallWatcher*)));
142 }
143 }
144
updateSatelliteInfo(int timestamp,int satellitesUsed,int satellitesVisible,const QList<int> & usedPrn,const QList<QGeoSatelliteInfo> & satInfos)145 void QGeoSatelliteInfoSourceGeoclueMaster::updateSatelliteInfo(int timestamp, int satellitesUsed,
146 int satellitesVisible,
147 const QList<int> &usedPrn,
148 const QList<QGeoSatelliteInfo> &satInfos)
149 {
150 Q_UNUSED(timestamp);
151
152 QList<QGeoSatelliteInfo> inUse;
153
154 foreach (const QGeoSatelliteInfo &si, satInfos)
155 if (usedPrn.contains(si.satelliteIdentifier()))
156 inUse.append(si);
157
158 if (satInfos.length() != satellitesVisible) {
159 qWarning("QGeoSatelliteInfoSourceGeoclueMaster number of in view QGeoSatelliteInfos (%d) "
160 "does not match expected number of in view satellites (%d).", satInfos.length(),
161 satellitesVisible);
162 }
163
164 if (inUse.length() != satellitesUsed) {
165 qWarning("QGeoSatelliteInfoSourceGeoclueMaster number of in use QGeoSatelliteInfos (%d) "
166 "does not match expected number of in use satellites (%d).", inUse.length(),
167 satellitesUsed);
168 }
169
170 m_inView = satInfos;
171 emit satellitesInViewUpdated(m_inView);
172
173 m_inUse = inUse;
174 emit satellitesInUseUpdated(m_inUse);
175
176 m_requestTimer.start(qMax(updateInterval(), minimumUpdateInterval()));
177 }
178
requestUpdateTimeout()179 void QGeoSatelliteInfoSourceGeoclueMaster::requestUpdateTimeout()
180 {
181 // If we end up here, there has not been a valid satellite info update.
182 if (m_running) {
183 m_inView.clear();
184 m_inUse.clear();
185 emit satellitesInViewUpdated(m_inView);
186 emit satellitesInUseUpdated(m_inUse);
187 } else {
188 emit requestTimeout();
189
190 // Only stop satellite info if regular updates not active.
191 cleanupSatelliteSource();
192 m_master->releaseMasterClient();
193 }
194 }
195
getSatelliteFinished(QDBusPendingCallWatcher * watcher)196 void QGeoSatelliteInfoSourceGeoclueMaster::getSatelliteFinished(QDBusPendingCallWatcher *watcher)
197 {
198 QDBusPendingReply<qint32, qint32, qint32, QList<qint32>, QList<QGeoSatelliteInfo> > reply = *watcher;
199 watcher->deleteLater();
200
201 if (reply.isError())
202 return;
203
204 m_requestTimer.stop();
205 updateSatelliteInfo(reply.argumentAt<0>(), reply.argumentAt<1>(), reply.argumentAt<2>(),
206 reply.argumentAt<3>(), reply.argumentAt<4>());
207 }
208
satelliteChanged(int timestamp,int satellitesUsed,int satellitesVisible,const QList<int> & usedPrn,const QList<QGeoSatelliteInfo> & satInfos)209 void QGeoSatelliteInfoSourceGeoclueMaster::satelliteChanged(int timestamp, int satellitesUsed, int satellitesVisible, const QList<int> &usedPrn, const QList<QGeoSatelliteInfo> &satInfos)
210 {
211 updateSatelliteInfo(timestamp, satellitesUsed, satellitesVisible, usedPrn, satInfos);
212 }
213
positionProviderChanged(const QString & name,const QString & description,const QString & service,const QString & path)214 void QGeoSatelliteInfoSourceGeoclueMaster::positionProviderChanged(const QString &name,
215 const QString &description,
216 const QString &service,
217 const QString &path)
218 {
219 Q_UNUSED(name);
220 Q_UNUSED(description);
221
222 cleanupSatelliteSource();
223
224 QString providerService;
225 QString providerPath;
226
227 if (service.isEmpty() || path.isEmpty()) {
228 // No valid position provider has been selected. This probably means that the GPS provider
229 // has not yet obtained a position fix. It can still provide satellite information though.
230 if (!m_satellitesChangedConnected) {
231 QDBusConnection conn = QDBusConnection::sessionBus();
232 conn.connect(QString(), QString(), QStringLiteral("org.freedesktop.Geoclue.Satellite"),
233 QStringLiteral("SatelliteChanged"), this,
234 SLOT(satelliteChanged(QDBusMessage)));
235 m_satellitesChangedConnected = true;
236 return;
237 }
238 } else {
239 if (m_satellitesChangedConnected) {
240 QDBusConnection conn = QDBusConnection::sessionBus();
241 conn.disconnect(QString(), QString(),
242 QStringLiteral("org.freedesktop.Geoclue.Satellite"),
243 QStringLiteral("SatelliteChanged"), this,
244 SLOT(satelliteChanged(QDBusMessage)));
245 m_satellitesChangedConnected = false;
246 }
247
248 providerService = service;
249 providerPath = path;
250 }
251
252 if (providerService.isEmpty() || providerPath.isEmpty()) {
253 m_error = AccessError;
254 emit QGeoSatelliteInfoSource::error(m_error);
255 return;
256 }
257
258 m_provider = new OrgFreedesktopGeoclueInterface(providerService, providerPath, QDBusConnection::sessionBus());
259 m_provider->AddReference();
260
261 m_sat = new OrgFreedesktopGeoclueSatelliteInterface(providerService, providerPath, QDBusConnection::sessionBus());
262
263 if (m_running) {
264 connect(m_sat, SIGNAL(SatelliteChanged(qint32,qint32,qint32,QList<qint32>,QList<QGeoSatelliteInfo>)),
265 this, SLOT(satelliteChanged(qint32,qint32,qint32,QList<qint32>,QList<QGeoSatelliteInfo>)));
266 }
267 }
268
satelliteChanged(const QDBusMessage & message)269 void QGeoSatelliteInfoSourceGeoclueMaster::satelliteChanged(const QDBusMessage &message)
270 {
271 QVariantList arguments = message.arguments();
272 if (arguments.length() != 5)
273 return;
274
275 int timestamp = arguments.at(0).toInt();
276 int usedSatellites = arguments.at(1).toInt();
277 int visibleSatellites = arguments.at(2).toInt();
278
279 QDBusArgument dbusArgument = arguments.at(3).value<QDBusArgument>();
280
281 QList<int> usedPrn;
282 dbusArgument >> usedPrn;
283
284 dbusArgument = arguments.at(4).value<QDBusArgument>();
285
286 QList<QGeoSatelliteInfo> satelliteInfos;
287 dbusArgument >> satelliteInfos;
288
289 satelliteChanged(timestamp, usedSatellites, visibleSatellites, usedPrn, satelliteInfos);
290 }
291
configureSatelliteSource()292 void QGeoSatelliteInfoSourceGeoclueMaster::configureSatelliteSource()
293 {
294 if (!m_master->createMasterClient(Accuracy::Detailed, QGeoclueMaster::ResourceGps)) {
295 m_error = UnknownSourceError;
296 emit QGeoSatelliteInfoSource::error(m_error);
297 }
298 }
299
cleanupSatelliteSource()300 void QGeoSatelliteInfoSourceGeoclueMaster::cleanupSatelliteSource()
301 {
302 if (m_provider)
303 m_provider->RemoveReference();
304 delete m_provider;
305 m_provider = 0;
306 delete m_sat;
307 m_sat = 0;
308 }
309
310 QT_END_NAMESPACE
311