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 #include <qgeopositioninfosource.h>
40 #include "qgeopositioninfosource_p.h"
41 #include "qgeopositioninfosourcefactory.h"
42 
43 #include <QFile>
44 #include <QPluginLoader>
45 #include <QStringList>
46 #include <QJsonObject>
47 #include <QCryptographicHash>
48 #include <QtCore/private/qfactoryloader_p.h>
49 
50 #include <algorithm>
51 
52 QT_BEGIN_NAMESPACE
53 
54 Q_GLOBAL_STATIC_WITH_ARGS(QFactoryLoader, loader,
55         ("org.qt-project.qt.position.sourcefactory/5.0",
56          QLatin1String("/position")))
57 
58 /*!
59     \class QGeoPositionInfoSource
60     \inmodule QtPositioning
61     \ingroup QtPositioning-positioning
62     \since 5.2
63 
64     \brief The QGeoPositionInfoSource class is an abstract base class for the distribution of positional updates.
65 
66     The static function QGeoPositionInfoSource::createDefaultSource() creates a default
67     position source that is appropriate for the platform, if one is available.
68     Otherwise, QGeoPositionInfoSource will check for available plugins that
69     implement the QGeoPositionInfoSourceFactory interface.
70 
71     Users of a QGeoPositionInfoSource subclass can request the current position using
72     requestUpdate(), or start and stop regular position updates using
73     startUpdates() and stopUpdates(). When an update is available,
74     positionUpdated() is emitted. The last known position can be accessed with
75     lastKnownPosition().
76 
77     If regular position updates are required, setUpdateInterval() can be used
78     to specify how often these updates should be emitted. If no interval is
79     specified, updates are simply provided whenever they are available.
80     For example:
81 
82     \code
83         // Emit updates every 10 seconds if available
84         QGeoPositionInfoSource *source = QGeoPositionInfoSource::createDefaultSource(0);
85         if (source)
86             source->setUpdateInterval(10000);
87     \endcode
88 
89     To remove an update interval that was previously set, call
90     setUpdateInterval() with a value of 0.
91 
92     Note that the position source may have a minimum value requirement for
93     update intervals, as returned by minimumUpdateInterval().
94 */
95 
96 /*!
97     \enum QGeoPositionInfoSource::PositioningMethod
98     Defines the types of positioning methods.
99 
100     \value NoPositioningMethods None of the positioning methods.
101     \value SatellitePositioningMethods Satellite-based positioning methods such as GPS or GLONASS.
102     \value NonSatellitePositioningMethods Other positioning methods such as 3GPP cell identifier or WiFi based positioning.
103     \value AllPositioningMethods Satellite-based positioning methods as soon as available. Otherwise non-satellite based methods.
104 */
105 
get(const QGeoPositionInfoSource & source)106 QGeoPositionInfoSourcePrivate *QGeoPositionInfoSourcePrivate::get(const QGeoPositionInfoSource &source)
107 {
108     return source.d;
109 }
110 
~QGeoPositionInfoSourcePrivate()111 QGeoPositionInfoSourcePrivate::~QGeoPositionInfoSourcePrivate()
112 {
113 
114 }
115 
loadMeta()116 void QGeoPositionInfoSourcePrivate::loadMeta()
117 {
118     metaData = plugins().value(providerName);
119 }
120 
loadPlugin()121 void QGeoPositionInfoSourcePrivate::loadPlugin()
122 {
123     int idx = int(metaData.value(QStringLiteral("index")).toDouble());
124     if (idx < 0)
125         return;
126     QObject *instance = loader()->instance(idx);
127     if (!instance)
128         return;
129     factoryV2 = qobject_cast<QGeoPositionInfoSourceFactoryV2 *>(instance);
130     if (!factoryV2)
131         factory = qobject_cast<QGeoPositionInfoSourceFactory *>(instance);
132     else
133         factory = factoryV2;
134 }
135 
setBackendProperty(const QString &,const QVariant &)136 bool QGeoPositionInfoSourcePrivate::setBackendProperty(const QString &/*name*/, const QVariant & /*value*/)
137 {
138     return false;
139 }
140 
backendProperty(const QString &) const141 QVariant QGeoPositionInfoSourcePrivate::backendProperty(const QString &/*name*/) const
142 {
143     return QVariant();
144 }
145 
plugins(bool reload)146 QMultiHash<QString, QJsonObject> QGeoPositionInfoSourcePrivate::plugins(bool reload)
147 {
148     static QMultiHash<QString, QJsonObject> plugins;
149     static bool alreadyDiscovered = false;
150 
151     if (reload == true)
152         alreadyDiscovered = false;
153 
154     if (!alreadyDiscovered) {
155         loadPluginMetadata(plugins);
156         alreadyDiscovered = true;
157     }
158     return plugins;
159 }
160 
pluginComparator(const QJsonObject & p1,const QJsonObject & p2)161 static bool pluginComparator(const QJsonObject &p1, const QJsonObject &p2)
162 {
163     const QString prio = QStringLiteral("Priority");
164     if (p1.contains(prio) && !p2.contains(prio))
165         return true;
166     if (!p1.contains(prio) && p2.contains(prio))
167         return false;
168     if (p1.value(prio).isDouble() && !p2.value(prio).isDouble())
169         return true;
170     if (!p1.value(prio).isDouble() && p2.value(prio).isDouble())
171         return false;
172     return (p1.value(prio).toDouble() > p2.value(prio).toDouble());
173 }
174 
pluginsSorted()175 QList<QJsonObject> QGeoPositionInfoSourcePrivate::pluginsSorted()
176 {
177     QList<QJsonObject> list = plugins().values();
178     std::stable_sort(list.begin(), list.end(), pluginComparator);
179     return list;
180 }
181 
loadPluginMetadata(QMultiHash<QString,QJsonObject> & plugins)182 void QGeoPositionInfoSourcePrivate::loadPluginMetadata(QMultiHash<QString, QJsonObject> &plugins)
183 {
184     QFactoryLoader *l = loader();
185     QList<QJsonObject> meta = l->metaData();
186     for (int i = 0; i < meta.size(); ++i) {
187         QJsonObject obj = meta.at(i).value(QStringLiteral("MetaData")).toObject();
188         const QString testableKey = QStringLiteral("Testable");
189         if (obj.contains(testableKey) && !obj.value(testableKey).toBool()) {
190             static bool inTest = qEnvironmentVariableIsSet("QT_QTESTLIB_RUNNING");
191             if (inTest)
192                 continue;
193         }
194         obj.insert(QStringLiteral("index"), i);
195         plugins.insert(obj.value(QStringLiteral("Provider")).toString(), obj);
196     }
197 }
198 
199 /*!
200     Creates a position source with the specified \a parent.
201 */
202 
QGeoPositionInfoSource(QObject * parent)203 QGeoPositionInfoSource::QGeoPositionInfoSource(QObject *parent)
204         : QObject(parent),
205         d(new QGeoPositionInfoSourcePrivate)
206 {
207     qRegisterMetaType<QGeoPositionInfo>();
208     d->interval = 0;
209     d->methods = {};
210 }
211 
212 /*!
213     Destroys the position source.
214 */
~QGeoPositionInfoSource()215 QGeoPositionInfoSource::~QGeoPositionInfoSource()
216 {
217     delete d;
218 }
219 
220 /*!
221     \property QGeoPositionInfoSource::sourceName
222     \brief This property holds the unique name of the position source
223            implementation in use.
224 
225     This is the same name that can be passed to createSource() in order to
226     create a new instance of a particular position source implementation.
227 */
sourceName() const228 QString QGeoPositionInfoSource::sourceName() const
229 {
230     return d->metaData.value(QStringLiteral("Provider")).toString();
231 }
232 
233 /*!
234     Sets the backend-specific property named \a name to \a value.
235     Returns \c true on success, \c false otherwise.
236     Backend-specific properties can be used to configure the positioning subsystem behavior
237     at runtime.
238     Supported backend-specific properties are listed and described in
239     \l {Qt Positioning plugins#Default plugins}.
240 
241     \sa backendProperty
242     \since Qt 5.14
243 */
setBackendProperty(const QString & name,const QVariant & value)244 bool QGeoPositionInfoSource::setBackendProperty(const QString &name, const QVariant &value)
245 {
246     return d->setBackendProperty(name, value);
247 }
248 
249 /*!
250     Returns the value of the backend-specific property named \a name, if present.
251     Otherwise, the returned value will be invalid.
252     Supported backend-specific properties are listed and described in
253     \l {Qt Positioning plugins#Default plugins}.
254 
255     \sa setBackendProperty
256     \since Qt 5.14
257 */
backendProperty(const QString & name) const258 QVariant QGeoPositionInfoSource::backendProperty(const QString &name) const
259 {
260     return d->backendProperty(name);
261 }
262 
263 /*!
264     \property QGeoPositionInfoSource::updateInterval
265     \brief This property holds the requested interval in milliseconds between each update.
266 
267     If the update interval is not set (or is set to 0) the
268     source will provide updates as often as necessary.
269 
270     If the update interval is set, the source will provide updates at an
271     interval as close to the requested interval as possible. If the requested
272     interval is less than the minimumUpdateInterval(),
273     the minimum interval is used instead.
274 
275     Changes to the update interval will happen as soon as is practical, however the
276     time the change takes may vary between implementations.  Whether or not the elapsed
277     time from the previous interval is counted as part of the new interval is also
278     implementation dependent.
279 
280     The default value for this property is 0.
281 
282     Note: Subclass implementations must call the base implementation of
283     setUpdateInterval() so that updateInterval() returns the correct value.
284 */
setUpdateInterval(int msec)285 void QGeoPositionInfoSource::setUpdateInterval(int msec)
286 {
287     d->interval = msec;
288 }
289 
updateInterval() const290 int QGeoPositionInfoSource::updateInterval() const
291 {
292     return d->interval;
293 }
294 
295 /*!
296     Sets the preferred positioning methods for this source to \a methods.
297 
298     If \a methods includes a method that is not supported by the source, the
299     unsupported method will be ignored.
300 
301     If \a methods does not include a single method available/supported by the source, the
302     preferred methods will be set to the set of methods which the source has available.
303     If the source has no method availabe (e.g. because its Location service is turned off
304     or it does not offer a Location service), the passed \a methods are accepted as they are.
305 
306     \b {Note:} When reimplementing this method, subclasses must call the
307     base method implementation to ensure preferredPositioningMethods() returns the correct value.
308 
309     \sa supportedPositioningMethods()
310 */
setPreferredPositioningMethods(PositioningMethods methods)311 void QGeoPositionInfoSource::setPreferredPositioningMethods(PositioningMethods methods)
312 {
313     if (supportedPositioningMethods() != QGeoPositionInfoSource::NoPositioningMethods) {
314         d->methods = methods & supportedPositioningMethods();
315         if (d->methods == 0) {
316             d->methods = supportedPositioningMethods();
317         }
318     } else { // avoid that turned of Location service blocks any changes to d->methods
319         d->methods = methods;
320     }
321 }
322 
323 /*!
324     Returns the positioning methods set by setPreferredPositioningMethods().
325 */
preferredPositioningMethods() const326 QGeoPositionInfoSource::PositioningMethods QGeoPositionInfoSource::preferredPositioningMethods() const
327 {
328     return d->methods;
329 }
330 
createSource_real(const QJsonObject & meta,const QVariantMap & parameters,QObject * parent)331 static QGeoPositionInfoSource* createSource_real(const QJsonObject &meta, const QVariantMap &parameters, QObject *parent)
332 {
333     QGeoPositionInfoSourcePrivate d;
334     d.metaData = meta;
335     d.loadPlugin();
336     QGeoPositionInfoSource *s = nullptr;
337     if (!parameters.isEmpty() && d.factoryV2)
338         s = d.factoryV2->positionInfoSourceWithParameters(parent, parameters);
339     else if (d.factory)
340         s = d.factory->positionInfoSource(parent);
341     if (s)
342         QGeoPositionInfoSourcePrivate::get(*s)->metaData = d.metaData;
343 
344     return s;
345 }
346 
347 /*!
348     Creates and returns a position source with the given \a parent that
349     reads from the system's default sources of location data, or the plugin
350     with the highest available priority.
351 
352     Returns 0 if the system has no default position source, no valid plugins
353     could be found or the user does not have the permission to access the current position.
354 */
createDefaultSource(QObject * parent)355 QGeoPositionInfoSource *QGeoPositionInfoSource::createDefaultSource(QObject *parent)
356 {
357     return createDefaultSource(QVariantMap(), parent);
358 }
359 
360 /*!
361     Creates and returns a position source with the given \a parent that
362     reads from the system's default sources of location data, or the plugin
363     with the highest available priority.
364 
365     Returns nullptr if the system has no default position source, no valid plugins
366     could be found or the user does not have the permission to access the current position.
367 
368     This method passes \a parameters to the factory to configure the source.
369 
370     \since Qt 5.14
371 */
createDefaultSource(const QVariantMap & parameters,QObject * parent)372 QGeoPositionInfoSource *QGeoPositionInfoSource::createDefaultSource(const QVariantMap &parameters, QObject *parent)
373 {
374     QList<QJsonObject> plugins = QGeoPositionInfoSourcePrivate::pluginsSorted();
375     foreach (const QJsonObject &obj, plugins) {
376         if (obj.value(QStringLiteral("Position")).isBool()
377                 && obj.value(QStringLiteral("Position")).toBool()) {
378             QGeoPositionInfoSource *source = createSource_real(obj, parameters, parent);
379             if (source)
380                 return source;
381         }
382     }
383     return nullptr;
384 }
385 
386 /*!
387     Creates and returns a position source with the given \a parent,
388     by loading the plugin named \a sourceName.
389 
390     Returns 0 if the plugin cannot be found.
391 */
createSource(const QString & sourceName,QObject * parent)392 QGeoPositionInfoSource *QGeoPositionInfoSource::createSource(const QString &sourceName, QObject *parent)
393 {
394     return createSource(sourceName, QVariantMap(), parent);
395 }
396 
397 /*!
398     Creates and returns a position source with the given \a parent,
399     by loading the plugin named \a sourceName.
400 
401     Returns nullptr if the plugin cannot be found.
402 
403     This method passes \a parameters to the factory to configure the source.
404 
405     \since Qt 5.14
406 */
createSource(const QString & sourceName,const QVariantMap & parameters,QObject * parent)407 QGeoPositionInfoSource *QGeoPositionInfoSource::createSource(const QString &sourceName, const QVariantMap &parameters, QObject *parent)
408 {
409     QHash<QString, QJsonObject> plugins = QGeoPositionInfoSourcePrivate::plugins();
410     if (plugins.contains(sourceName))
411         return createSource_real(plugins.value(sourceName), parameters, parent);
412     return nullptr;
413 }
414 
415 /*!
416     Returns a list of available source plugins. This includes any default backend
417     plugin for the current platform.
418 */
availableSources()419 QStringList QGeoPositionInfoSource::availableSources()
420 {
421     QStringList plugins;
422     const QHash<QString, QJsonObject> meta = QGeoPositionInfoSourcePrivate::plugins();
423     for (auto it = meta.cbegin(), end = meta.cend(); it != end; ++it) {
424         if (it.value().value(QStringLiteral("Position")).isBool()
425                 && it.value().value(QStringLiteral("Position")).toBool()) {
426             plugins << it.key();
427         }
428     }
429 
430     return plugins;
431 }
432 
QGeoPositionInfoSource(QGeoPositionInfoSourcePrivate & dd,QObject * parent)433 QGeoPositionInfoSource::QGeoPositionInfoSource(QGeoPositionInfoSourcePrivate &dd, QObject *parent)
434 :   QObject(parent),
435     d(&dd)
436 {
437     qRegisterMetaType<QGeoPositionInfo>();
438     d->interval = 0;
439     d->methods = NoPositioningMethods;
440 }
441 
442 /*!
443     \fn QGeoPositionInfo QGeoPositionInfoSource::lastKnownPosition(bool fromSatellitePositioningMethodsOnly = false) const = 0;
444 
445     Returns an update containing the last known position, or a null update
446     if none is available.
447 
448     If \a fromSatellitePositioningMethodsOnly is true, this returns the last
449     known position received from a satellite positioning method; if none
450     is available, a null update is returned.
451 */
452 
453 /*!
454     \fn virtual PositioningMethods QGeoPositionInfoSource::supportedPositioningMethods() const = 0;
455 
456     Returns the positioning methods available to this source. Availability is defined as being usable
457     at the time of calling this function. Therefore user settings like turned off location service or
458     limitations to Satellite-based position providers are reflected by this function. Runtime notifications
459     when the status changes can be obtained via \l supportedPositioningMethodsChanged().
460 
461     Not all platforms distinguish the different positioning methods or communicate the current user
462     configuration of the device. The following table provides an overview of the current platform situation:
463 
464     \table
465     \header
466         \li Platform
467         \li Brief Description
468     \row
469         \li Android
470         \li Individual provider status and general Location service state are known and communicated
471             when location service is active.
472     \row
473         \li GeoClue
474         \li Hardcoced to always return AllPositioningMethods.
475     \row
476         \li GeoClue2
477         \li Individual providers are not distinguishable but disabled Location services reflected.
478     \row
479         \li iOS/tvOS
480         \li Hardcoced to always return AllPositioningMethods.
481     \row
482         \li macOS
483         \li Hardcoced to always return AllPositioningMethods.
484     \row
485         \li Windows (UWP)
486         \li Individual providers are not distinguishable but disabled Location services reflected.
487     \endtable
488 
489     \sa supportedPositioningMethodsChanged(), setPreferredPositioningMethods()
490 */
491 
492 
493 /*!
494     \property QGeoPositionInfoSource::minimumUpdateInterval
495     \brief This property holds the minimum time (in milliseconds) required to retrieve a position update.
496 
497     This is the minimum value accepted by setUpdateInterval() and
498     requestUpdate().
499 */
500 
501 
502 /*!
503     \fn virtual void QGeoPositionInfoSource::startUpdates() = 0;
504 
505     Starts emitting updates at regular intervals as specified by setUpdateInterval().
506 
507     If setUpdateInterval() has not been called, the source will emit updates
508     as soon as they become available.
509 
510     An updateTimeout() signal will be emitted if this QGeoPositionInfoSource subclass determines
511     that it will not be able to provide regular updates.  This could happen if a satellite fix is
512     lost or if a hardware error is detected.  Position updates will recommence if the data becomes
513     available later on.  The updateTimeout() signal will not be emitted again until after the
514     periodic updates resume.
515 
516     On iOS, starting from version 8, Core Location framework requires additional
517     entries in the application's Info.plist with keys NSLocationAlwaysUsageDescription or
518     NSLocationWhenInUseUsageDescription and a string to be displayed in the authorization prompt.
519     The key NSLocationWhenInUseUsageDescription is used when requesting permission
520     to use location services while the app is in the foreground.
521     The key NSLocationAlwaysUsageDescription is used when requesting permission
522     to use location services whenever the app is running (both the foreground and the background).
523     If both entries are defined, NSLocationWhenInUseUsageDescription has a priority in the
524     foreground mode.
525 */
526 
527 /*!
528     \fn virtual void QGeoPositionInfoSource::stopUpdates() = 0;
529 
530     Stops emitting updates at regular intervals.
531 */
532 
533 /*!
534     \fn virtual void QGeoPositionInfoSource::requestUpdate(int timeout = 0);
535 
536     Attempts to get the current position and emit positionUpdated() with
537     this information. If the current position cannot be found within the given \a timeout
538     (in milliseconds) or if \a timeout is less than the value returned by
539     minimumUpdateInterval(), updateTimeout() is emitted.
540 
541     If the timeout is zero, the timeout defaults to a reasonable timeout
542     period as appropriate for the source.
543 
544     This does nothing if another update request is in progress. However
545     it can be called even if startUpdates() has already been called and
546     regular updates are in progress.
547 
548     If the source uses multiple positioning methods, it tries to get the
549     current position from the most accurate positioning method within the
550     given timeout.
551 */
552 
553 /*!
554      \fn virtual QGeoPositionInfoSource::Error QGeoPositionInfoSource::error() const;
555 
556      Returns the type of error that last occurred.
557 
558 */
559 
560 /*!
561     \fn void QGeoPositionInfoSource::positionUpdated(const QGeoPositionInfo &update);
562 
563     If startUpdates() or requestUpdate() is called, this signal is emitted
564     when an update becomes available.
565 
566     The \a update value holds the value of the new update.
567 */
568 
569 /*!
570     \fn void QGeoPositionInfoSource::updateTimeout();
571 
572     If requestUpdate() was called, this signal will be emitted if the current position could not
573     be retrieved within the specified timeout.
574 
575     If startUpdates() has been called, this signal will be emitted if this QGeoPositionInfoSource
576     subclass determines that it will not be able to provide further regular updates.  This signal
577     will not be emitted again until after the regular updates resume.
578 
579     While the triggering of this signal may be considered an error condition, it does not
580     imply the emission of the \c error() signal. Only the emission of \c updateTimeout() is required
581     to indicate a timeout.
582 */
583 
584 /*!
585     \fn void QGeoPositionInfoSource::error(QGeoPositionInfoSource::Error positioningError)
586 
587     This signal is emitted after an error occurred. The \a positioningError
588     parameter describes the type of error that occurred.
589 
590     This signal is not emitted when an updateTimeout() has occurred.
591 
592 */
593 
594 /*!
595     \enum QGeoPositionInfoSource::Error
596 
597     The Error enumeration represents the errors which can occur.
598 
599     \value AccessError The connection setup to the remote positioning backend failed because the
600         application lacked the required privileges.
601     \value ClosedError  The remote positioning backend closed the connection, which happens for example in case
602         the user is switching location services to off. As soon as the location service is re-enabled
603         regular updates will resume.
604     \value NoError No error has occurred.
605     \value UnknownSourceError An unidentified error occurred.
606  */
607 
608 /*!
609     \fn void QGeoPositionInfoSource::supportedPositioningMethodsChanged()
610 
611     This signal is emitted when the supported positioning methods changed. The cause for a change could be
612     a user turning Location services on/off or restricting Location services to certain types (e.g. GPS only).
613     Note that changes to the supported positioning methods cannot be detected on all platforms.
614     \l supportedPositioningMethods() provides an overview of the current platform support.
615 
616     \since Qt 5.12
617 */
618 
619 QT_END_NAMESPACE
620