/**************************************************************************** ** ** Copyright (C) 2015 The Qt Company Ltd. ** Contact: http://www.qt.io/licensing/ ** ** This file is part of the QtPositioning module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL3$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see http://www.qt.io/terms-conditions. For further ** information use the contact form at http://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPLv3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or later as published by the Free ** Software Foundation and appearing in the file LICENSE.GPL included in ** the packaging of this file. Please review the following information to ** ensure the GNU General Public License version 2.0 requirements will be ** met: http://www.gnu.org/licenses/gpl-2.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qgeopositioninfosource_winrt_p.h" #include #include #include #include #ifdef Q_OS_WINRT #include #endif #include #include #include #include #include using namespace Microsoft::WRL; using namespace Microsoft::WRL::Wrappers; using namespace ABI::Windows::Devices::Geolocation; using namespace ABI::Windows::Foundation; using namespace ABI::Windows::Foundation::Collections; typedef ITypedEventHandler GeoLocatorPositionHandler; typedef IAsyncOperationCompletedHandler PositionHandler; typedef IAsyncOperationCompletedHandler AccessHandler; Q_DECLARE_LOGGING_CATEGORY(lcPositioningWinRT) QT_BEGIN_NAMESPACE #ifndef Q_OS_WINRT namespace QEventDispatcherWinRT { HRESULT runOnXamlThread(const std::function &delegate, bool waitForRun = true) { Q_UNUSED(waitForRun); return delegate(); } } static inline HRESULT await(const ComPtr> &asyncOp, GeolocationAccessStatus *result) { ComPtr asyncInfo; HRESULT hr = asyncOp.As(&asyncInfo); if (FAILED(hr)) return hr; AsyncStatus status; while (SUCCEEDED(hr = asyncInfo->get_Status(&status)) && status == AsyncStatus::Started) QThread::yieldCurrentThread(); if (FAILED(hr) || status != AsyncStatus::Completed) { HRESULT ec; hr = asyncInfo->get_ErrorCode(&ec); if (FAILED(hr)) return hr; hr = asyncInfo->Close(); if (FAILED(hr)) return hr; return ec; } if (FAILED(hr)) return hr; return asyncOp->GetResults(result); } #endif // !Q_OS_WINRT class QGeoPositionInfoSourceWinRTPrivate { public: ComPtr locator; QTimer periodicTimer; QTimer singleUpdateTimer; QGeoPositionInfo lastPosition; QGeoPositionInfoSource::Error positionError; EventRegistrationToken statusToken; EventRegistrationToken positionToken; QMutex mutex; bool updatesOngoing; int minimumUpdateInterval; PositionStatus nativeStatus() const; }; PositionStatus QGeoPositionInfoSourceWinRTPrivate::nativeStatus() const { #ifdef Q_OS_WINRT qCDebug(lcPositioningWinRT) << __FUNCTION__; PositionStatus status; HRESULT hr = QEventDispatcherWinRT::runOnXamlThread([this, &status]() { return locator->get_LocationStatus(&status); }); if (FAILED(hr)) { qErrnoWarning(hr, "Could not query status"); return PositionStatus_NotAvailable; } return status; #else return PositionStatus_Ready; #endif } QGeoPositionInfoSourceWinRT::QGeoPositionInfoSourceWinRT(QObject *parent) : QGeoPositionInfoSource(parent) , d_ptr(new QGeoPositionInfoSourceWinRTPrivate) { qCDebug(lcPositioningWinRT) << __FUNCTION__; CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); Q_D(QGeoPositionInfoSourceWinRT); d->positionError = QGeoPositionInfoSource::NoError; d->updatesOngoing = false; } QGeoPositionInfoSourceWinRT::~QGeoPositionInfoSourceWinRT() { qCDebug(lcPositioningWinRT) << __FUNCTION__; CoUninitialize(); } int QGeoPositionInfoSourceWinRT::init() { qCDebug(lcPositioningWinRT) << __FUNCTION__; Q_D(QGeoPositionInfoSourceWinRT); if (!requestAccess()) { qWarning ("Location access failed."); return -1; } HRESULT hr = QEventDispatcherWinRT::runOnXamlThread([this, d]() { HRESULT hr = RoActivateInstance(HString::MakeReference(RuntimeClass_Windows_Devices_Geolocation_Geolocator).Get(), &d->locator); RETURN_HR_IF_FAILED("Could not initialize native location services."); UINT32 interval; hr = d->locator->get_ReportInterval(&interval); RETURN_HR_IF_FAILED("Could not retrieve report interval."); d->minimumUpdateInterval = static_cast(interval); setUpdateInterval(d->minimumUpdateInterval); return hr; }); if (FAILED(hr)) { setError(QGeoPositionInfoSource::UnknownSourceError); return -1; } hr = d->locator->put_DesiredAccuracy(PositionAccuracy::PositionAccuracy_Default); if (FAILED(hr)) { setError(QGeoPositionInfoSource::UnknownSourceError); qErrnoWarning(hr, "Could not initialize desired accuracy."); return -1; } d->positionToken.value = 0; d->periodicTimer.setSingleShot(true); connect(&d->periodicTimer, &QTimer::timeout, this, &QGeoPositionInfoSourceWinRT::virtualPositionUpdate); d->singleUpdateTimer.setSingleShot(true); connect(&d->singleUpdateTimer, &QTimer::timeout, this, &QGeoPositionInfoSourceWinRT::singleUpdateTimeOut); setPreferredPositioningMethods(QGeoPositionInfoSource::AllPositioningMethods); connect(this, &QGeoPositionInfoSourceWinRT::nativePositionUpdate, this, &QGeoPositionInfoSourceWinRT::updateSynchronized); return 0; } QGeoPositionInfo QGeoPositionInfoSourceWinRT::lastKnownPosition(bool fromSatellitePositioningMethodsOnly) const { qCDebug(lcPositioningWinRT) << __FUNCTION__; Q_D(const QGeoPositionInfoSourceWinRT); Q_UNUSED(fromSatellitePositioningMethodsOnly) return d->lastPosition; } QGeoPositionInfoSource::PositioningMethods QGeoPositionInfoSourceWinRT::supportedPositioningMethods() const { Q_D(const QGeoPositionInfoSourceWinRT); PositionStatus status = d->nativeStatus(); qCDebug(lcPositioningWinRT) << __FUNCTION__ << status; switch (status) { case PositionStatus::PositionStatus_NoData: case PositionStatus::PositionStatus_Disabled: case PositionStatus::PositionStatus_NotAvailable: return QGeoPositionInfoSource::NoPositioningMethods; } return QGeoPositionInfoSource::AllPositioningMethods; } void QGeoPositionInfoSourceWinRT::setPreferredPositioningMethods(QGeoPositionInfoSource::PositioningMethods methods) { qCDebug(lcPositioningWinRT) << __FUNCTION__ << methods; Q_D(QGeoPositionInfoSourceWinRT); PositioningMethods previousPreferredPositioningMethods = preferredPositioningMethods(); QGeoPositionInfoSource::setPreferredPositioningMethods(methods); if (previousPreferredPositioningMethods == preferredPositioningMethods()) return; const bool needsRestart = d->positionToken.value != 0; if (needsRestart) stopHandler(); PositionAccuracy acc = methods & PositioningMethod::SatellitePositioningMethods ? PositionAccuracy::PositionAccuracy_High : PositionAccuracy::PositionAccuracy_Default; HRESULT hr = QEventDispatcherWinRT::runOnXamlThread([d, acc]() { HRESULT hr = d->locator->put_DesiredAccuracy(acc); return hr; }); RETURN_VOID_IF_FAILED("Could not set positioning accuracy."); if (needsRestart) startHandler(); } void QGeoPositionInfoSourceWinRT::setUpdateInterval(int msec) { qCDebug(lcPositioningWinRT) << __FUNCTION__ << msec; Q_D(QGeoPositionInfoSourceWinRT); // minimumUpdateInterval is initialized to the lowest possible update interval in init(). // Passing 0 will cause an error on Windows 10. // See https://docs.microsoft.com/en-us/uwp/api/windows.devices.geolocation.geolocator.reportinterval if (msec != 0 && msec < minimumUpdateInterval()) msec = minimumUpdateInterval(); const bool needsRestart = d->positionToken.value != 0; if (needsRestart) stopHandler(); HRESULT hr = d->locator->put_ReportInterval(static_cast(msec)); if (FAILED(hr)) { setError(QGeoPositionInfoSource::UnknownSourceError); qErrnoWarning(hr, "Failed to set update interval"); return; } d->periodicTimer.setInterval(qMax(msec, minimumUpdateInterval())); QGeoPositionInfoSource::setUpdateInterval(msec); if (needsRestart) startHandler(); } int QGeoPositionInfoSourceWinRT::minimumUpdateInterval() const { Q_D(const QGeoPositionInfoSourceWinRT); return d->minimumUpdateInterval; } void QGeoPositionInfoSourceWinRT::startUpdates() { qCDebug(lcPositioningWinRT) << __FUNCTION__; Q_D(QGeoPositionInfoSourceWinRT); if (d->updatesOngoing) return; if (!startHandler()) return; d->updatesOngoing = true; d->periodicTimer.start(); } void QGeoPositionInfoSourceWinRT::stopUpdates() { qCDebug(lcPositioningWinRT) << __FUNCTION__; Q_D(QGeoPositionInfoSourceWinRT); stopHandler(); d->updatesOngoing = false; d->periodicTimer.stop(); } bool QGeoPositionInfoSourceWinRT::startHandler() { qCDebug(lcPositioningWinRT) << __FUNCTION__; Q_D(QGeoPositionInfoSourceWinRT); // Check if already attached if (d->positionToken.value != 0) return true; if (preferredPositioningMethods() == QGeoPositionInfoSource::NoPositioningMethods) { setError(QGeoPositionInfoSource::UnknownSourceError); return false; } if (!requestAccess() || !checkNativeState()) return false; HRESULT hr = QEventDispatcherWinRT::runOnXamlThread([this, d]() { HRESULT hr; // We need to call this at least once on Windows 10 Mobile. // Unfortunately this operation does not have a completion handler // registered. That could have helped in the single update case ComPtr> op; hr = d->locator->GetGeopositionAsync(&op); RETURN_HR_IF_FAILED("Could not start position operation"); hr = d->locator->add_PositionChanged(Callback(this, &QGeoPositionInfoSourceWinRT::onPositionChanged).Get(), &d->positionToken); RETURN_HR_IF_FAILED("Could not add position handler"); return hr; }); if (FAILED(hr)) { setError(QGeoPositionInfoSource::UnknownSourceError); return false; } return true; } void QGeoPositionInfoSourceWinRT::stopHandler() { qCDebug(lcPositioningWinRT) << __FUNCTION__; Q_D(QGeoPositionInfoSourceWinRT); if (!d->positionToken.value) return; QEventDispatcherWinRT::runOnXamlThread([d]() { d->locator->remove_PositionChanged(d->positionToken); return S_OK; }); d->positionToken.value = 0; } void QGeoPositionInfoSourceWinRT::requestUpdate(int timeout) { qCDebug(lcPositioningWinRT) << __FUNCTION__; Q_D(QGeoPositionInfoSourceWinRT); if (timeout != 0 && timeout < minimumUpdateInterval()) { emit updateTimeout(); return; } if (timeout == 0) timeout = 2*60*1000; // Maximum time for cold start (see Android) startHandler(); d->singleUpdateTimer.start(timeout); } void QGeoPositionInfoSourceWinRT::virtualPositionUpdate() { qCDebug(lcPositioningWinRT) << __FUNCTION__; Q_D(QGeoPositionInfoSourceWinRT); QMutexLocker locker(&d->mutex); // Need to check if services are still running and ok if (!checkNativeState()) { stopUpdates(); return; } // The operating system did not provide information in time // Hence we send a virtual position update to keep same behavior // between backends. // This only applies to the periodic timer, not for single requests // We can only do this if we received a valid position before if (d->lastPosition.isValid()) { QGeoPositionInfo sent = d->lastPosition; sent.setTimestamp(sent.timestamp().addMSecs(updateInterval())); d->lastPosition = sent; emit positionUpdated(sent); } d->periodicTimer.start(); } void QGeoPositionInfoSourceWinRT::singleUpdateTimeOut() { Q_D(QGeoPositionInfoSourceWinRT); QMutexLocker locker(&d->mutex); if (d->singleUpdateTimer.isActive()) { emit updateTimeout(); if (!d->updatesOngoing) stopHandler(); } } void QGeoPositionInfoSourceWinRT::updateSynchronized(QGeoPositionInfo currentInfo) { qCDebug(lcPositioningWinRT) << __FUNCTION__ << currentInfo; Q_D(QGeoPositionInfoSourceWinRT); QMutexLocker locker(&d->mutex); d->periodicTimer.stop(); d->lastPosition = currentInfo; if (d->updatesOngoing) d->periodicTimer.start(); if (d->singleUpdateTimer.isActive()) { d->singleUpdateTimer.stop(); if (!d->updatesOngoing) stopHandler(); } emit positionUpdated(currentInfo); } QGeoPositionInfoSource::Error QGeoPositionInfoSourceWinRT::error() const { Q_D(const QGeoPositionInfoSourceWinRT); qCDebug(lcPositioningWinRT) << __FUNCTION__ << d->positionError; return d->positionError; } void QGeoPositionInfoSourceWinRT::setError(QGeoPositionInfoSource::Error positionError) { qCDebug(lcPositioningWinRT) << __FUNCTION__ << positionError; Q_D(QGeoPositionInfoSourceWinRT); if (positionError == d->positionError) return; d->positionError = positionError; emit QGeoPositionInfoSource::error(positionError); } bool QGeoPositionInfoSourceWinRT::checkNativeState() { Q_D(QGeoPositionInfoSourceWinRT); qCDebug(lcPositioningWinRT) << __FUNCTION__; PositionStatus status = d->nativeStatus(); bool result = false; switch (status) { case PositionStatus::PositionStatus_NotAvailable: setError(QGeoPositionInfoSource::UnknownSourceError); break; case PositionStatus::PositionStatus_Disabled: case PositionStatus::PositionStatus_NoData: setError(QGeoPositionInfoSource::ClosedError); break; default: setError(QGeoPositionInfoSource::NoError); result = true; break; } return result; } HRESULT QGeoPositionInfoSourceWinRT::onPositionChanged(IGeolocator *locator, IPositionChangedEventArgs *args) { qCDebug(lcPositioningWinRT) << __FUNCTION__; Q_UNUSED(locator); HRESULT hr; ComPtr position; hr = args->get_Position(&position); RETURN_HR_IF_FAILED("Could not access position object."); QGeoPositionInfo currentInfo; ComPtr coord; hr = position->get_Coordinate(&coord); if (FAILED(hr)) qErrnoWarning(hr, "Could not access coordinate"); ComPtr pointCoordinate; hr = coord.As(&pointCoordinate); if (FAILED(hr)) qErrnoWarning(hr, "Could not cast coordinate."); ComPtr point; hr = pointCoordinate->get_Point(&point); if (FAILED(hr)) qErrnoWarning(hr, "Could not obtain coordinate's point."); BasicGeoposition pos; hr = point->get_Position(&pos); if (FAILED(hr)) qErrnoWarning(hr, "Could not obtain point's position."); DOUBLE lat = pos.Latitude; DOUBLE lon = pos.Longitude; DOUBLE alt = pos.Altitude; bool altitudeAvailable = false; ComPtr shape; hr = point.As(&shape); if (SUCCEEDED(hr) && shape) { AltitudeReferenceSystem altitudeSystem; hr = shape->get_AltitudeReferenceSystem(&altitudeSystem); if (SUCCEEDED(hr) && altitudeSystem == AltitudeReferenceSystem_Geoid) altitudeAvailable = true; } if (altitudeAvailable) currentInfo.setCoordinate(QGeoCoordinate(lat, lon, alt)); else currentInfo.setCoordinate(QGeoCoordinate(lat, lon)); DOUBLE accuracy; hr = coord->get_Accuracy(&accuracy); if (SUCCEEDED(hr)) currentInfo.setAttribute(QGeoPositionInfo::HorizontalAccuracy, accuracy); IReference *altAccuracy; hr = coord->get_AltitudeAccuracy(&altAccuracy); if (SUCCEEDED(hr) && altAccuracy) { double value; hr = altAccuracy->get_Value(&value); currentInfo.setAttribute(QGeoPositionInfo::VerticalAccuracy, value); } IReference *speed; hr = coord->get_Speed(&speed); if (SUCCEEDED(hr) && speed) { double value; hr = speed->get_Value(&value); currentInfo.setAttribute(QGeoPositionInfo::GroundSpeed, value); } IReference *heading; hr = coord->get_Heading(&heading); if (SUCCEEDED(hr) && heading) { double value; hr = heading->get_Value(&value); double mod = 0; value = modf(value, &mod); value += static_cast(mod) % 360; if (value >=0 && value <= 359) // get_Value might return nan/-nan currentInfo.setAttribute(QGeoPositionInfo::Direction, value); } DateTime dateTime; hr = coord->get_Timestamp(&dateTime); if (dateTime.UniversalTime > 0) { ULARGE_INTEGER uLarge; uLarge.QuadPart = dateTime.UniversalTime; FILETIME fileTime; fileTime.dwHighDateTime = uLarge.HighPart; fileTime.dwLowDateTime = uLarge.LowPart; SYSTEMTIME systemTime; if (FileTimeToSystemTime(&fileTime, &systemTime)) { currentInfo.setTimestamp(QDateTime(QDate(systemTime.wYear, systemTime.wMonth, systemTime.wDay), QTime(systemTime.wHour, systemTime.wMinute, systemTime.wSecond, systemTime.wMilliseconds), Qt::UTC)); } } emit nativePositionUpdate(currentInfo); return S_OK; } bool QGeoPositionInfoSourceWinRT::requestAccess() const { qCDebug(lcPositioningWinRT) << __FUNCTION__; static GeolocationAccessStatus accessStatus = GeolocationAccessStatus_Unspecified; static ComPtr statics; if (accessStatus == GeolocationAccessStatus_Allowed) return true; else if (accessStatus == GeolocationAccessStatus_Denied) return false; ComPtr> op; HRESULT hr; hr = QEventDispatcherWinRT::runOnXamlThread([&op]() { HRESULT hr; hr = RoGetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Devices_Geolocation_Geolocator).Get(), IID_PPV_ARGS(&statics)); RETURN_HR_IF_FAILED("Could not access Geolocation Statics."); hr = statics->RequestAccessAsync(&op); return hr; }); Q_ASSERT_SUCCEEDED(hr); // We cannot wait inside the XamlThread as that would deadlock #ifdef Q_OS_WINRT QWinRTFunctions::await(op, &accessStatus); #else await(op, &accessStatus); #endif return accessStatus == GeolocationAccessStatus_Allowed; } QT_END_NAMESPACE