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