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
311/*!
312 \class QGeoPositionInfoSourcePrivate
313 \inmodule QtPositioning
314 \internal
315*/
316QGeoPositionInfoSource *QGeoPositionInfoSourcePrivate::createSourceReal(const QCborMap &meta, const QVariantMap &parameters, QObject *parent)
317{
318 QGeoPositionInfoSource *s = nullptr;
319 auto factory = QGeoPositionInfoSourcePrivate::loadFactory(meta);
320 if (factory)
321 s = factory->positionInfoSource(parent, parameters);
322 if (s)
323 s->d_func()->sourceName = meta.value(QStringLiteral("Provider")).toString();
324
325 return s;
326}
327
328/*!
329 Creates and returns a position source with the given \a parent that
330 reads from the system's default sources of location data, or the plugin
331 with the highest available priority.
332
333 Returns \c nullptr if the system has no default position source, no valid
334 plugins could be found or the user does not have the permission to access
335 the current position.
336*/
337QGeoPositionInfoSource *QGeoPositionInfoSource::createDefaultSource(QObject *parent)
338{
339 return createDefaultSource(QVariantMap(), parent);
340}
341
342/*!
343 Creates and returns a position source with the given \a parent that
344 reads from the system's default sources of location data, or the plugin
345 with the highest available priority.
346
347 Returns \c nullptr if the system has no default position source, no valid plugins
348 could be found or the user does not have the permission to access the current position.
349
350 This method passes \a parameters to the factory to configure the source.
351
352 \since Qt 5.14
353*/
354QGeoPositionInfoSource *QGeoPositionInfoSource::createDefaultSource(const QVariantMap &parameters, QObject *parent)
355{
356 const QList<QCborMap> plugins = QGeoPositionInfoSourcePrivate::pluginsSorted();
357 for (const QCborMap &obj : plugins) {
358 if (obj.value(QStringLiteral("Position")).isBool()
359 && obj.value(QStringLiteral("Position")).toBool()) {
360 QGeoPositionInfoSource *source = QGeoPositionInfoSourcePrivate::createSourceReal(obj, parameters, parent);
361 if (source)
362 return source;
363 }
364 }
365 return nullptr;
366}
367
368/*!
369 Creates and returns a position source with the given \a parent,
370 by loading the plugin named \a sourceName.
371
372 Returns \c nullptr if the plugin cannot be found.
373*/
374QGeoPositionInfoSource *QGeoPositionInfoSource::createSource(const QString &sourceName, QObject *parent)
375{
376 return createSource(sourceName, QVariantMap(), parent);
377}
378
379/*!
380 Creates and returns a position source with the given \a parent,
381 by loading the plugin named \a sourceName.
382
383 Returns \c nullptr if the plugin cannot be found.
384
385 This method passes \a parameters to the factory to configure the source.
386
387 \since Qt 5.14
388*/
389QGeoPositionInfoSource *QGeoPositionInfoSource::createSource(const QString &sourceName, const QVariantMap &parameters, QObject *parent)
390{
391 auto plugins = QGeoPositionInfoSourcePrivate::plugins();
392 if (plugins.contains(sourceName))
393 return QGeoPositionInfoSourcePrivate::createSourceReal(plugins.value(sourceName), parameters, parent);
394 return nullptr;
395}
396
397/*!
398 Returns a list of available source plugins. This includes any default backend
399 plugin for the current platform.
400*/
401QStringList QGeoPositionInfoSource::availableSources()
402{
403 QStringList plugins;
404 const auto meta = QGeoPositionInfoSourcePrivate::plugins();
405 for (auto it = meta.cbegin(), end = meta.cend(); it != end; ++it) {
406 if (it.value().value(QStringLiteral("Position")).isBool()
407 && it.value().value(QStringLiteral("Position")).toBool()) {
408 plugins << it.key();
409 }
410 }
411
412 return plugins;
413}
414
415QGeoPositionInfoSource::QGeoPositionInfoSource(QGeoPositionInfoSourcePrivate &dd, QObject *parent)
416 : QObject(dd, parent)
417{
418 qRegisterMetaType<QGeoPositionInfo>();
419 Q_D(QGeoPositionInfoSource);
420 d->interval.setValueBypassingBindings(0);
421 d->methods.setValueBypassingBindings(NoPositioningMethods);
422}
423
424/*!
425 \fn QGeoPositionInfo QGeoPositionInfoSource::lastKnownPosition(bool fromSatellitePositioningMethodsOnly = false) const = 0;
426
427 Returns an update containing the last known position, or a null update
428 if none is available.
429
430 If \a fromSatellitePositioningMethodsOnly is true, this returns the last
431 known position received from a satellite positioning method; if none
432 is available, a null update is returned.
433*/
434
435/*!
436 \fn virtual PositioningMethods QGeoPositionInfoSource::supportedPositioningMethods() const = 0;
437
438 Returns the positioning methods available to this source. Availability is defined as being usable
439 at the time of calling this function. Therefore user settings like turned off location service or
440 limitations to Satellite-based position providers are reflected by this function. Runtime notifications
441 when the status changes can be obtained via \l supportedPositioningMethodsChanged().
442
443 Not all platforms distinguish the different positioning methods or communicate the current user
444 configuration of the device. The following table provides an overview of the current platform situation:
445
446 \table
447 \header
448 \li Platform
449 \li Brief Description
450 \row
451 \li Android
452 \li Individual provider status and general Location service state are known and communicated
453 when location service is active.
454 \row
455 \li GeoClue
456 \li Hardcoced to always return AllPositioningMethods.
457 \row
458 \li GeoClue2
459 \li Individual providers are not distinguishable but disabled Location services reflected.
460 \row
461 \li iOS
462 \li Hardcoced to always return AllPositioningMethods.
463 \row
464 \li macOS
465 \li Hardcoced to always return AllPositioningMethods.
466 \row
467 \li Windows (UWP)
468 \li Individual providers are not distinguishable but disabled Location services reflected.
469 \endtable
470
471 \sa supportedPositioningMethodsChanged(), setPreferredPositioningMethods()
472*/
473
474
475/*!
476 \property QGeoPositionInfoSource::minimumUpdateInterval
477 \brief This property holds the minimum time (in milliseconds) required to retrieve a position update.
478
479 This is the minimum value accepted by setUpdateInterval() and
480 requestUpdate().
481*/
482
483
484/*!
485 \fn virtual void QGeoPositionInfoSource::startUpdates() = 0;
486
487 Starts emitting updates at regular intervals as specified by setUpdateInterval().
488
489 If setUpdateInterval() has not been called, the source will emit updates
490 as soon as they become available.
491
492 An errorOccurred() signal with the \l {QGeoPositionInfoSource::}
493 {UpdateTimeoutError} will be emitted if this QGeoPositionInfoSource subclass
494 determines that it will not be able to provide regular updates. This could
495 happen if a satellite fix is lost or if a hardware error is detected.
496 Position updates will recommence if the data becomes available later on.
497 The \l {QGeoPositionInfoSource::}{UpdateTimeoutError} error will not
498 be emitted again until after the periodic updates resume.
499
500 \note Since Qt6 this method always resets the last error to
501 \l {QGeoPositionInfoSource::}{NoError} before starting the updates.
502
503 \note To understand how to use this method from an Android service, see
504 \l {Qt Positioning on Android}.
505
506 On iOS, starting from version 8, Core Location framework requires additional
507 entries in the application's Info.plist with keys NSLocationAlwaysUsageDescription or
508 NSLocationWhenInUseUsageDescription and a string to be displayed in the authorization prompt.
509 The key NSLocationWhenInUseUsageDescription is used when requesting permission
510 to use location services while the app is in the foreground.
511 The key NSLocationAlwaysUsageDescription is used when requesting permission
512 to use location services whenever the app is running (both the foreground and the background).
513 If both entries are defined, NSLocationWhenInUseUsageDescription has a priority in the
514 foreground mode.
515*/
516
517/*!
518 \fn virtual void QGeoPositionInfoSource::stopUpdates() = 0;
519
520 Stops emitting updates at regular intervals.
521*/
522
523/*!
524 \fn virtual void QGeoPositionInfoSource::requestUpdate(int timeout = 0);
525
526 Attempts to get the current position and emit positionUpdated() with
527 this information. If the current position cannot be found within the given \a timeout
528 (in milliseconds) or if \a timeout is less than the value returned by
529 minimumUpdateInterval(), an errorOccurred() signal with the
530 \l {QGeoPositionInfoSource::}{UpdateTimeoutError} is emitted.
531
532 If the timeout is zero, the timeout defaults to a reasonable timeout
533 period as appropriate for the source.
534
535 This does nothing if another update request is in progress. However
536 it can be called even if startUpdates() has already been called and
537 regular updates are in progress.
538
539 If the source uses multiple positioning methods, it tries to get the
540 current position from the most accurate positioning method within the
541 given timeout.
542
543 \note Since Qt6 this method always resets the last error to
544 \l {QGeoPositionInfoSource::}{NoError} before requesting
545 the position.
546
547 \note To understand how to use this method from an Android service, see
548 \l {Qt Positioning on Android}.
549*/
550
551/*!
552 \fn virtual QGeoPositionInfoSource::Error QGeoPositionInfoSource::error() const;
553
554 Returns the type of error that last occurred.
555
556 \note Since Qt6 the last error is always reset when calling startUpdates()
557 or requestUpdate().
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::errorOccurred(QGeoPositionInfoSource::Error positioningError)
571
572 This signal is emitted after an error occurred. The \a positioningError
573 parameter describes the type of error that occurred.
574*/
575
576/*!
577 \enum QGeoPositionInfoSource::Error
578
579 The Error enumeration represents the errors which can occur.
580
581 \value AccessError The connection setup to the remote positioning backend failed because the
582 application lacked the required privileges.
583 \value ClosedError The remote positioning backend closed the connection, which happens for example in case
584 the user is switching location services to off. As soon as the location service is re-enabled
585 regular updates will resume.
586 \value NoError No error has occurred.
587 \value UnknownSourceError An unidentified error occurred.
588 \value [since 6.2] UpdateTimeoutError If requestUpdate() was called, this
589 error indicates that the current position could not be retrieved within
590 the specified timeout. If startUpdates() was called, this error
591 indicates that this QGeoPositionInfoSource subclass determined that it
592 will not be able to provide further regular updates. In the latter case
593 the error would not be emitted again until after the regular updates
594 resume.
595 */
596
597/*!
598 \fn void QGeoPositionInfoSource::supportedPositioningMethodsChanged()
599
600 This signal is emitted when the supported positioning methods changed. The cause for a change could be
601 a user turning Location services on/off or restricting Location services to certain types (e.g. GPS only).
602 Note that changes to the supported positioning methods cannot be detected on all platforms.
603 \l supportedPositioningMethods() provides an overview of the current platform support.
604
605 \since Qt 5.12
606*/
607
608QT_END_NAMESPACE
609
610#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 } }))