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