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