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_winrt.cpp
Go to the documentation of this file.
1// Copyright (C) 2015 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
5
6#include <QtCore/qcoreapplication.h>
7#include <QtCore/qdatetime.h>
8#include <QtCore/private/qfunctions_winrt_p.h>
9#include <QtCore/qloggingcategory.h>
10#include <QtCore/qmutex.h>
11#include <QtCore/qtimezone.h>
12
13#include <functional>
14#include <windows.system.h>
15#include <windows.devices.geolocation.h>
16#include <windows.foundation.h>
17#include <windows.foundation.collections.h>
18
19using namespace Microsoft::WRL;
20using namespace Microsoft::WRL::Wrappers;
21using namespace ABI::Windows::Devices::Geolocation;
22using namespace ABI::Windows::Foundation;
23using namespace ABI::Windows::Foundation::Collections;
24
29
30Q_DECLARE_LOGGING_CATEGORY(lcPositioningWinRT)
31
32QT_BEGIN_NAMESPACE
33
34static inline HRESULT await(const ComPtr<IAsyncOperation<GeolocationAccessStatus>> &asyncOp,
35 GeolocationAccessStatus *result)
36{
37 ComPtr<IAsyncInfo> asyncInfo;
38 HRESULT hr = asyncOp.As(&asyncInfo);
39 if (FAILED(hr))
40 return hr;
41
42 AsyncStatus status;
43 while (SUCCEEDED(hr = asyncInfo->get_Status(&status)) && status == AsyncStatus::Started)
44 QThread::yieldCurrentThread();
45
46 if (FAILED(hr) || status != AsyncStatus::Completed) {
47 HRESULT ec;
48 hr = asyncInfo->get_ErrorCode(&ec);
49 if (FAILED(hr))
50 return hr;
51 hr = asyncInfo->Close();
52 if (FAILED(hr))
53 return hr;
54 return ec;
55 }
56
57 if (FAILED(hr))
58 return hr;
59
60 return asyncOp->GetResults(result);
61}
62
68
87
88QGeoPositionInfoSourceWinRT::QGeoPositionInfoSourceWinRT(QObject *parent)
89 : QGeoPositionInfoSource(parent)
90 , d_ptr(new QGeoPositionInfoSourceWinRTPrivate)
91{
92 qRegisterMetaType<QGeoPositionInfoSource::Error>();
93 qCDebug(lcPositioningWinRT) << __FUNCTION__;
94 CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
95 Q_D(QGeoPositionInfoSourceWinRT);
96 d->positionError = QGeoPositionInfoSource::NoError;
97 d->updatesOngoing = false;
98 d->positionToken.value = 0;
99 d->statusToken.value = 0;
100}
101
103{
104 qCDebug(lcPositioningWinRT) << __FUNCTION__;
105 CoUninitialize();
106}
107
109{
110 Q_D(QGeoPositionInfoSourceWinRT);
111 Q_ASSERT(d->initState != InitializationState::Initializing);
112 if (d->initState == InitializationState::Initialized)
113 return 0;
114
115 qCDebug(lcPositioningWinRT) << __FUNCTION__;
116 d->initState = InitializationState::Initializing;
117 if (!requestAccess()) {
118 d->initState = InitializationState::Uninitialized;
119 setError(QGeoPositionInfoSource::AccessError);
120 qWarning ("Location access failed.");
121 return -1;
122 }
123 HRESULT hr = [this, d]() {
124 HRESULT hr = RoActivateInstance(HString::MakeReference(RuntimeClass_Windows_Devices_Geolocation_Geolocator).Get(),
125 &d->locator);
126 RETURN_HR_IF_FAILED("Could not initialize native location services.");
127
128 if (d->minimumUpdateInterval == -1) {
129 UINT32 interval;
130 hr = d->locator->get_ReportInterval(&interval);
131 RETURN_HR_IF_FAILED("Could not retrieve report interval.");
132 d->minimumUpdateInterval = static_cast<int>(interval);
133 }
134 if (d->updateInterval == -1)
135 d->updateInterval = d->minimumUpdateInterval;
136 setUpdateInterval(d->updateInterval);
137
138 return hr;
139 }();
140 if (FAILED(hr)) {
141 d->initState = InitializationState::Uninitialized;
142 setError(QGeoPositionInfoSource::UnknownSourceError);
143 return -1;
144 }
145
146 d->periodicTimer.setSingleShot(true);
147 connect(&d->periodicTimer, &QTimer::timeout, this, &QGeoPositionInfoSourceWinRT::virtualPositionUpdate);
148
149 d->singleUpdateTimer.setSingleShot(true);
150 connect(&d->singleUpdateTimer, &QTimer::timeout, this, &QGeoPositionInfoSourceWinRT::singleUpdateTimeOut);
151
152 QGeoPositionInfoSource::PositioningMethods preferredMethods = preferredPositioningMethods();
153 if (preferredMethods == QGeoPositionInfoSource::NoPositioningMethods)
154 preferredMethods = QGeoPositionInfoSource::AllPositioningMethods;
155 setPreferredPositioningMethods(preferredMethods);
156
157 connect(this, &QGeoPositionInfoSourceWinRT::nativePositionUpdate, this, &QGeoPositionInfoSourceWinRT::updateSynchronized);
158 d->initState = InitializationState::Initialized;
159 return 0;
160}
161
162QGeoPositionInfo QGeoPositionInfoSourceWinRT::lastKnownPosition(bool fromSatellitePositioningMethodsOnly) const
163{
164 qCDebug(lcPositioningWinRT) << __FUNCTION__;
165 Q_D(const QGeoPositionInfoSourceWinRT);
166 Q_UNUSED(fromSatellitePositioningMethodsOnly);
167 return d->lastPosition;
168}
169
171{
172 return requestAccess() ? QGeoPositionInfoSource::AllPositioningMethods
173 : QGeoPositionInfoSource::NoPositioningMethods;
174}
175
176void QGeoPositionInfoSourceWinRT::setPreferredPositioningMethods(QGeoPositionInfoSource::PositioningMethods methods)
177{
178 qCDebug(lcPositioningWinRT) << __FUNCTION__ << methods;
179 Q_D(QGeoPositionInfoSourceWinRT);
180
181 PositioningMethods previousPreferredPositioningMethods = preferredPositioningMethods();
182 QGeoPositionInfoSource::setPreferredPositioningMethods(methods);
183 if (previousPreferredPositioningMethods == preferredPositioningMethods()
184 || d->initState == InitializationState::Uninitialized) {
185 return;
186 }
187
188 const bool needsRestart = d->positionToken.value != 0 || d->statusToken.value != 0;
189
190 if (needsRestart)
191 stopHandler();
192
193 PositionAccuracy acc = methods & PositioningMethod::SatellitePositioningMethods ?
194 PositionAccuracy::PositionAccuracy_High :
195 PositionAccuracy::PositionAccuracy_Default;
196 HRESULT hr = [d, acc]() {
197 return d->locator->put_DesiredAccuracy(acc);
198 }();
199 RETURN_VOID_IF_FAILED("Could not set positioning accuracy.");
200
201 if (needsRestart)
202 startHandler();
203}
204
206{
207 qCDebug(lcPositioningWinRT) << __FUNCTION__ << msec;
208 Q_D(QGeoPositionInfoSourceWinRT);
209 if (d->initState == InitializationState::Uninitialized) {
210 d->updateInterval = msec;
211 return;
212 }
213
214 // minimumUpdateInterval is initialized to the lowest possible update interval in init().
215 // Passing 0 will cause an error on Windows 10.
216 // See https://docs.microsoft.com/en-us/uwp/api/windows.devices.geolocation.geolocator.reportinterval
217 if (msec < minimumUpdateInterval())
219
220 const bool needsRestart = d->positionToken.value != 0 || d->statusToken.value != 0;
221
222 if (needsRestart)
223 stopHandler();
224
225 HRESULT hr = d->locator->put_ReportInterval(static_cast<UINT32>(msec));
226 if (FAILED(hr)) {
227 setError(QGeoPositionInfoSource::UnknownSourceError);
228 qErrnoWarning(hr, "Failed to set update interval");
229 return;
230 }
231
232 d->updateInterval = msec;
233 d->periodicTimer.setInterval(d->updateInterval);
234
235 QGeoPositionInfoSource::setUpdateInterval(d->updateInterval);
236
237 if (needsRestart)
238 startHandler();
239}
240
242{
243 Q_D(const QGeoPositionInfoSourceWinRT);
244 return d->minimumUpdateInterval == -1 ? 1000 : d->minimumUpdateInterval;
245}
246
247void QGeoPositionInfoSourceWinRT::startUpdates()
248{
249 qCDebug(lcPositioningWinRT) << __FUNCTION__;
250 Q_D(QGeoPositionInfoSourceWinRT);
251
252 setError(QGeoPositionInfoSource::NoError);
253 if (init() < 0)
254 return;
255
256 if (d->updatesOngoing)
257 return;
258
259 if (!startHandler())
260 return;
261 d->updatesOngoing = true;
262 d->periodicTimer.start();
263}
264
266{
267 qCDebug(lcPositioningWinRT) << __FUNCTION__;
268 Q_D(QGeoPositionInfoSourceWinRT);
269
270 if (init() < 0)
271 return;
272
273 stopHandler();
274 d->updatesOngoing = false;
275 d->periodicTimer.stop();
276}
277
278bool QGeoPositionInfoSourceWinRT::startHandler()
279{
280 qCDebug(lcPositioningWinRT) << __FUNCTION__;
281 Q_D(QGeoPositionInfoSourceWinRT);
282
283 // Check if already attached
284 if (d->positionToken.value != 0)
285 return true;
286
287 if (preferredPositioningMethods() == QGeoPositionInfoSource::NoPositioningMethods) {
288 setError(QGeoPositionInfoSource::UnknownSourceError);
289 return false;
290 }
291
292 if (!requestAccess()) {
293 setError(QGeoPositionInfoSource::AccessError);
294 return false;
295 }
296
297 HRESULT hr = [this, d]() {
298 HRESULT hr;
299
300 // We need to call this at least once on Windows 10 Mobile.
301 // Unfortunately this operation does not have a completion handler
302 // registered. That could have helped in the single update case
303 ComPtr<IAsyncOperation<Geoposition*>> op;
304 hr = d->locator->GetGeopositionAsync(&op);
305 RETURN_HR_IF_FAILED("Could not start position operation");
306
307 hr = d->locator->add_PositionChanged(Callback<GeoLocatorPositionHandler>(this,
308 &QGeoPositionInfoSourceWinRT::onPositionChanged).Get(),
309 &d->positionToken);
310 RETURN_HR_IF_FAILED("Could not add position handler");
311
312 hr = d->locator->add_StatusChanged(Callback<GeoLocatorStatusHandler>(this,
313 &QGeoPositionInfoSourceWinRT::onStatusChanged).Get(),
314 &d->statusToken);
315 RETURN_HR_IF_FAILED("Could not add status handler");
316 return hr;
317 }();
318 if (FAILED(hr)) {
319 setError(QGeoPositionInfoSource::UnknownSourceError);
320 return false;
321 }
322
323 return true;
324}
325
326void QGeoPositionInfoSourceWinRT::stopHandler()
327{
328 qCDebug(lcPositioningWinRT) << __FUNCTION__;
329 Q_D(QGeoPositionInfoSourceWinRT);
330
331 if (!d->positionToken.value)
332 return;
333 d->locator->remove_PositionChanged(d->positionToken);
334 d->locator->remove_StatusChanged(d->statusToken);
335 d->positionToken.value = 0;
336 d->statusToken.value = 0;
337}
338
340{
341 qCDebug(lcPositioningWinRT) << __FUNCTION__ << timeout;
342 Q_D(QGeoPositionInfoSourceWinRT);
343
344 if (init() < 0)
345 return;
346
347 setError(QGeoPositionInfoSource::NoError);
348 if (timeout != 0 && timeout < minimumUpdateInterval()) {
349 d->positionError = QGeoPositionInfoSource::UpdateTimeoutError;
350 emit QGeoPositionInfoSource::errorOccurred(d->positionError);
351 return;
352 }
353
354 if (timeout == 0)
355 timeout = 2*60*1000; // Maximum time for cold start (see Android)
356
357 if (startHandler())
358 d->singleUpdateTimer.start(timeout);
359}
360
361void QGeoPositionInfoSourceWinRT::virtualPositionUpdate()
362{
363 qCDebug(lcPositioningWinRT) << __FUNCTION__;
364 Q_D(QGeoPositionInfoSourceWinRT);
365 QMutexLocker locker(&d->mutex);
366
367 // The operating system did not provide information in time
368 // Hence we send a virtual position update to keep same behavior
369 // between backends.
370 // This only applies to the periodic timer, not for single requests
371 // We can only do this if we received a valid position before
372 if (d->lastPosition.isValid()) {
373 QGeoPositionInfo sent = d->lastPosition;
374 sent.setTimestamp(sent.timestamp().addMSecs(updateInterval()));
375 d->lastPosition = sent;
376 emit positionUpdated(sent);
377 }
378 d->periodicTimer.start();
379}
380
381void QGeoPositionInfoSourceWinRT::singleUpdateTimeOut()
382{
383 Q_D(QGeoPositionInfoSourceWinRT);
384 QMutexLocker locker(&d->mutex);
385
386 if (d->singleUpdateTimer.isActive()) {
387 d->positionError = QGeoPositionInfoSource::UpdateTimeoutError;
388 emit QGeoPositionInfoSource::errorOccurred(d->positionError);
389 if (!d->updatesOngoing)
390 stopHandler();
391 }
392}
393
394void QGeoPositionInfoSourceWinRT::updateSynchronized(QGeoPositionInfo currentInfo)
395{
396 qCDebug(lcPositioningWinRT) << __FUNCTION__ << currentInfo;
397 Q_D(QGeoPositionInfoSourceWinRT);
398 QMutexLocker locker(&d->mutex);
399
400 d->periodicTimer.stop();
401 d->lastPosition = currentInfo;
402
403 if (d->updatesOngoing)
404 d->periodicTimer.start();
405
406 if (d->singleUpdateTimer.isActive()) {
407 d->singleUpdateTimer.stop();
408 if (!d->updatesOngoing)
409 stopHandler();
410 }
411
412 emit positionUpdated(currentInfo);
413}
414
416{
417 Q_D(const QGeoPositionInfoSourceWinRT);
418 qCDebug(lcPositioningWinRT) << __FUNCTION__ << d->positionError;
419
420 // If the last encountered error was "Access denied", it is possible that the location service
421 // has been enabled by now so that we are clear again.
422 if ((d->positionError == QGeoPositionInfoSource::AccessError
423 || d->positionError == QGeoPositionInfoSource::UnknownSourceError) && requestAccess())
424 return QGeoPositionInfoSource::NoError;
425
426 return d->positionError;
427}
428
429void QGeoPositionInfoSourceWinRT::setError(QGeoPositionInfoSource::Error positionError)
430{
431 Q_D(QGeoPositionInfoSourceWinRT);
432
433 if (positionError == d->positionError)
434 return;
435
436 qCDebug(lcPositioningWinRT) << __FUNCTION__ << positionError;
437 d->positionError = positionError;
438 if (positionError != QGeoPositionInfoSource::NoError)
439 emit QGeoPositionInfoSource::errorOccurred(positionError);
440}
441
442void QGeoPositionInfoSourceWinRT::reactOnError(QGeoPositionInfoSource::Error positionError)
443{
444 setError(positionError);
446}
447
448HRESULT QGeoPositionInfoSourceWinRT::onPositionChanged(IGeolocator *locator, IPositionChangedEventArgs *args)
449{
450 qCDebug(lcPositioningWinRT) << __FUNCTION__;
451 Q_UNUSED(locator);
452
453 HRESULT hr;
454 ComPtr<IGeoposition> position;
455 hr = args->get_Position(&position);
456 RETURN_HR_IF_FAILED("Could not access position object.");
457
458 QGeoPositionInfo currentInfo;
459
460 ComPtr<IGeocoordinate> coord;
461 hr = position->get_Coordinate(&coord);
462 if (FAILED(hr))
463 qErrnoWarning(hr, "Could not access coordinate");
464
465 ComPtr<IGeocoordinateWithPoint> pointCoordinate;
466 hr = coord.As(&pointCoordinate);
467 if (FAILED(hr))
468 qErrnoWarning(hr, "Could not cast coordinate.");
469
470 ComPtr<IGeopoint> point;
471 hr = pointCoordinate->get_Point(&point);
472 if (FAILED(hr))
473 qErrnoWarning(hr, "Could not obtain coordinate's point.");
474
475 BasicGeoposition pos;
476 hr = point->get_Position(&pos);
477 if (FAILED(hr))
478 qErrnoWarning(hr, "Could not obtain point's position.");
479
480 DOUBLE lat = pos.Latitude;
481 DOUBLE lon = pos.Longitude;
482 DOUBLE alt = pos.Altitude;
483
484 bool altitudeAvailable = false;
485 ComPtr<IGeoshape> shape;
486 hr = point.As(&shape);
487 if (SUCCEEDED(hr) && shape) {
488 AltitudeReferenceSystem altitudeSystem;
489 hr = shape->get_AltitudeReferenceSystem(&altitudeSystem);
490 if (SUCCEEDED(hr) && altitudeSystem == AltitudeReferenceSystem_Geoid)
491 altitudeAvailable = true;
492 }
493 if (altitudeAvailable)
494 currentInfo.setCoordinate(QGeoCoordinate(lat, lon, alt));
495 else
496 currentInfo.setCoordinate(QGeoCoordinate(lat, lon));
497
498 DOUBLE accuracy;
499 hr = coord->get_Accuracy(&accuracy);
500 if (SUCCEEDED(hr))
501 currentInfo.setAttribute(QGeoPositionInfo::HorizontalAccuracy, accuracy);
502
503 IReference<double> *altAccuracy;
504 hr = coord->get_AltitudeAccuracy(&altAccuracy);
505 if (SUCCEEDED(hr) && altAccuracy) {
506 double value;
507 hr = altAccuracy->get_Value(&value);
508 currentInfo.setAttribute(QGeoPositionInfo::VerticalAccuracy, value);
509 }
510
511 IReference<double> *speed;
512 hr = coord->get_Speed(&speed);
513 if (SUCCEEDED(hr) && speed) {
514 double value;
515 hr = speed->get_Value(&value);
516 currentInfo.setAttribute(QGeoPositionInfo::GroundSpeed, value);
517 }
518
519 IReference<double> *heading;
520 hr = coord->get_Heading(&heading);
521 if (SUCCEEDED(hr) && heading) {
522 double value;
523 hr = heading->get_Value(&value);
524 double mod = 0;
525 value = modf(value, &mod);
526 value += static_cast<int>(mod) % 360;
527 if (value >=0 && value <= 359) // get_Value might return nan/-nan
528 currentInfo.setAttribute(QGeoPositionInfo::Direction, value);
529 }
530
531 DateTime dateTime;
532 hr = coord->get_Timestamp(&dateTime);
533
534 if (dateTime.UniversalTime > 0) {
535 ULARGE_INTEGER uLarge;
536 uLarge.QuadPart = dateTime.UniversalTime;
537 FILETIME fileTime;
538 fileTime.dwHighDateTime = uLarge.HighPart;
539 fileTime.dwLowDateTime = uLarge.LowPart;
540 SYSTEMTIME systemTime;
541 if (FileTimeToSystemTime(&fileTime, &systemTime)) {
542 currentInfo.setTimestamp(QDateTime(QDate(systemTime.wYear, systemTime.wMonth,
543 systemTime.wDay),
544 QTime(systemTime.wHour, systemTime.wMinute,
545 systemTime.wSecond, systemTime.wMilliseconds),
546 QTimeZone::UTC));
547 }
548 }
549
550 emit nativePositionUpdate(currentInfo);
551
552 return S_OK;
553}
554
555static inline bool isDisabledStatus(PositionStatus status)
556{
557 return status == PositionStatus_NoData || status == PositionStatus_Disabled
558 || status == PositionStatus_NotAvailable;
559}
560
561HRESULT QGeoPositionInfoSourceWinRT::onStatusChanged(IGeolocator *, IStatusChangedEventArgs *args)
562{
563 Q_D(QGeoPositionInfoSourceWinRT);
564
565 const PositionStatus oldStatus = d->positionStatus;
566 HRESULT hr = args->get_Status(&d->positionStatus);
567 RETURN_HR_IF_FAILED("Could not obtain position status");
568 qCDebug(lcPositioningWinRT) << __FUNCTION__ << d->positionStatus;
569 QGeoPositionInfoSource::Error error = QGeoPositionInfoSource::NoError;
570 switch (d->positionStatus) {
571 case PositionStatus::PositionStatus_NotAvailable:
572 error = QGeoPositionInfoSource::UnknownSourceError;
573 break;
574 case PositionStatus::PositionStatus_Disabled:
575 error = QGeoPositionInfoSource::AccessError;
576 break;
577 case PositionStatus::PositionStatus_NoData:
578 error = QGeoPositionInfoSource::ClosedError;
579 break;
580 }
581 if (error != QGeoPositionInfoSource::NoError) {
582 QMetaObject::invokeMethod(this, "reactOnError", Qt::QueuedConnection,
583 Q_ARG(QGeoPositionInfoSource::Error,
584 QGeoPositionInfoSource::UnknownSourceError));
585 }
586
587 if (isDisabledStatus(oldStatus) != isDisabledStatus(d->positionStatus))
588 emit supportedPositioningMethodsChanged();
589
590 return S_OK;
591}
592
594{
595 Q_D(const QGeoPositionInfoSourceWinRT);
596 qCDebug(lcPositioningWinRT) << __FUNCTION__;
597 GeolocationAccessStatus accessStatus;
598
599 ComPtr<IAsyncOperation<GeolocationAccessStatus>> op;
600 HRESULT hr = [&op, d]() {
601 HRESULT hr;
602 if (!d->statics) {
603 hr = RoGetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Devices_Geolocation_Geolocator).Get(),
604 IID_PPV_ARGS(&d->statics));
605 RETURN_HR_IF_FAILED("Could not access Geolocation Statics.");
606 }
607
608 hr = d->statics->RequestAccessAsync(&op);
609 return hr;
610 }();
611 if (FAILED(hr)) {
612 qCDebug(lcPositioningWinRT) << __FUNCTION__ << "Requesting access from Xaml thread failed";
613 return false;
614 }
615
616 await(op, &accessStatus);
617 return accessStatus == GeolocationAccessStatus_Allowed;
618}
619
620QT_END_NAMESPACE
HRESULT onStatusChanged(ABI::Windows::Devices::Geolocation::IGeolocator *locator, ABI::Windows::Devices::Geolocation::IStatusChangedEventArgs *args)
QGeoPositionInfo lastKnownPosition(bool fromSatellitePositioningMethodsOnly=false) const override
Returns an update containing the last known position, or a null update if none is available.
Error error() const override
Returns the type of error that last occurred.
HRESULT onPositionChanged(ABI::Windows::Devices::Geolocation::IGeolocator *locator, ABI::Windows::Devices::Geolocation::IPositionChangedEventArgs *args)
void requestUpdate(int timeout=0) override
void setPreferredPositioningMethods(PositioningMethods methods) override
PositioningMethods supportedPositioningMethods() const override
Returns the positioning methods available to this source.
IAsyncOperationCompletedHandler< Geoposition * > PositionHandler
ITypedEventHandler< Geolocator *, StatusChangedEventArgs * > GeoLocatorStatusHandler
ITypedEventHandler< Geolocator *, PositionChangedEventArgs * > GeoLocatorPositionHandler
IAsyncOperationCompletedHandler< GeolocationAccessStatus > AccessHandler
static bool isDisabledStatus(PositionStatus status)