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