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