Revision control

Copy as Markdown

// Copyright (C) 2015 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
// Qt-Security score:significant reason:default
#include "qgeopositioninfosource_winrt_p.h"
#include <QtCore/qcoreapplication.h>
#include <QtCore/qdatetime.h>
#include <QtCore/private/qfunctions_winrt_p.h>
#include <QtCore/qloggingcategory.h>
#include <QtCore/qmutex.h>
#include <QtCore/qtimezone.h>
#include <functional>
#include <windows.system.h>
#include <windows.devices.geolocation.h>
#include <windows.foundation.h>
#include <windows.foundation.collections.h>
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<Geolocator *, PositionChangedEventArgs *> GeoLocatorPositionHandler;
typedef ITypedEventHandler<Geolocator *, StatusChangedEventArgs *> GeoLocatorStatusHandler;
typedef IAsyncOperationCompletedHandler<Geoposition*> PositionHandler;
typedef IAsyncOperationCompletedHandler<GeolocationAccessStatus> AccessHandler;
Q_DECLARE_LOGGING_CATEGORY(lcPositioningWinRT)
QT_BEGIN_NAMESPACE
static inline HRESULT await(const ComPtr<IAsyncOperation<GeolocationAccessStatus>> &asyncOp,
GeolocationAccessStatus *result)
{
ComPtr<IAsyncInfo> 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);
}
enum class InitializationState {
Uninitialized,
Initializing,
Initialized
};
class QGeoPositionInfoSourceWinRTPrivate {
public:
ComPtr<IGeolocator> locator;
mutable ComPtr<IGeolocatorStatics> statics;
QTimer periodicTimer;
QTimer singleUpdateTimer;
QGeoPositionInfo lastPosition;
QGeoPositionInfoSource::Error positionError = QGeoPositionInfoSource::NoError;
EventRegistrationToken statusToken;
EventRegistrationToken positionToken;
QMutex mutex;
bool updatesOngoing = false;
int minimumUpdateInterval = -1;
int updateInterval = -1;
InitializationState initState = InitializationState::Uninitialized;
PositionStatus positionStatus = PositionStatus_NotInitialized;
};
QGeoPositionInfoSourceWinRT::QGeoPositionInfoSourceWinRT(QObject *parent)
: QGeoPositionInfoSource(parent)
, d_ptr(new QGeoPositionInfoSourceWinRTPrivate)
{
qRegisterMetaType<QGeoPositionInfoSource::Error>();
qCDebug(lcPositioningWinRT) << __FUNCTION__;
CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED);
Q_D(QGeoPositionInfoSourceWinRT);
d->positionError = QGeoPositionInfoSource::NoError;
d->updatesOngoing = false;
d->positionToken.value = 0;
d->statusToken.value = 0;
}
QGeoPositionInfoSourceWinRT::~QGeoPositionInfoSourceWinRT()
{
qCDebug(lcPositioningWinRT) << __FUNCTION__;
CoUninitialize();
}
int QGeoPositionInfoSourceWinRT::init()
{
Q_D(QGeoPositionInfoSourceWinRT);
Q_ASSERT(d->initState != InitializationState::Initializing);
if (d->initState == InitializationState::Initialized)
return 0;
qCDebug(lcPositioningWinRT) << __FUNCTION__;
d->initState = InitializationState::Initializing;
if (!requestAccess()) {
d->initState = InitializationState::Uninitialized;
setError(QGeoPositionInfoSource::AccessError);
qWarning ("Location access failed.");
return -1;
}
HRESULT hr = [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.");
if (d->minimumUpdateInterval == -1) {
UINT32 interval;
hr = d->locator->get_ReportInterval(&interval);
RETURN_HR_IF_FAILED("Could not retrieve report interval.");
d->minimumUpdateInterval = static_cast<int>(interval);
}
if (d->updateInterval == -1)
d->updateInterval = d->minimumUpdateInterval;
setUpdateInterval(d->updateInterval);
return hr;
}();
if (FAILED(hr)) {
d->initState = InitializationState::Uninitialized;
setError(QGeoPositionInfoSource::UnknownSourceError);
return -1;
}
d->periodicTimer.setSingleShot(true);
connect(&d->periodicTimer, &QTimer::timeout, this, &QGeoPositionInfoSourceWinRT::virtualPositionUpdate);
d->singleUpdateTimer.setSingleShot(true);
connect(&d->singleUpdateTimer, &QTimer::timeout, this, &QGeoPositionInfoSourceWinRT::singleUpdateTimeOut);
QGeoPositionInfoSource::PositioningMethods preferredMethods = preferredPositioningMethods();
if (preferredMethods == QGeoPositionInfoSource::NoPositioningMethods)
preferredMethods = QGeoPositionInfoSource::AllPositioningMethods;
setPreferredPositioningMethods(preferredMethods);
connect(this, &QGeoPositionInfoSourceWinRT::nativePositionUpdate, this, &QGeoPositionInfoSourceWinRT::updateSynchronized);
d->initState = InitializationState::Initialized;
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
{
return requestAccess() ? QGeoPositionInfoSource::AllPositioningMethods
: QGeoPositionInfoSource::NoPositioningMethods;
}
void QGeoPositionInfoSourceWinRT::setPreferredPositioningMethods(QGeoPositionInfoSource::PositioningMethods methods)
{
qCDebug(lcPositioningWinRT) << __FUNCTION__ << methods;
Q_D(QGeoPositionInfoSourceWinRT);
PositioningMethods previousPreferredPositioningMethods = preferredPositioningMethods();
QGeoPositionInfoSource::setPreferredPositioningMethods(methods);
if (previousPreferredPositioningMethods == preferredPositioningMethods()
|| d->initState == InitializationState::Uninitialized) {
return;
}
const bool needsRestart = d->positionToken.value != 0 || d->statusToken.value != 0;
if (needsRestart)
stopHandler();
PositionAccuracy acc = methods & PositioningMethod::SatellitePositioningMethods ?
PositionAccuracy::PositionAccuracy_High :
PositionAccuracy::PositionAccuracy_Default;
HRESULT hr = [d, acc]() {
return d->locator->put_DesiredAccuracy(acc);
}();
RETURN_VOID_IF_FAILED("Could not set positioning accuracy.");
if (needsRestart)
startHandler();
}
void QGeoPositionInfoSourceWinRT::setUpdateInterval(int msec)
{
qCDebug(lcPositioningWinRT) << __FUNCTION__ << msec;
Q_D(QGeoPositionInfoSourceWinRT);
if (d->initState == InitializationState::Uninitialized) {
d->updateInterval = msec;
return;
}
// minimumUpdateInterval is initialized to the lowest possible update interval in init().
// Passing 0 will cause an error on Windows 10.
if (msec < minimumUpdateInterval())
msec = minimumUpdateInterval();
const bool needsRestart = d->positionToken.value != 0 || d->statusToken.value != 0;
if (needsRestart)
stopHandler();
HRESULT hr = d->locator->put_ReportInterval(static_cast<UINT32>(msec));
if (FAILED(hr)) {
setError(QGeoPositionInfoSource::UnknownSourceError);
qErrnoWarning(hr, "Failed to set update interval");
return;
}
d->updateInterval = msec;
d->periodicTimer.setInterval(d->updateInterval);
QGeoPositionInfoSource::setUpdateInterval(d->updateInterval);
if (needsRestart)
startHandler();
}
int QGeoPositionInfoSourceWinRT::minimumUpdateInterval() const
{
Q_D(const QGeoPositionInfoSourceWinRT);
return d->minimumUpdateInterval == -1 ? 1000 : d->minimumUpdateInterval;
}
void QGeoPositionInfoSourceWinRT::startUpdates()
{
qCDebug(lcPositioningWinRT) << __FUNCTION__;
Q_D(QGeoPositionInfoSourceWinRT);
setError(QGeoPositionInfoSource::NoError);
if (init() < 0)
return;
if (d->updatesOngoing)
return;
if (!startHandler())
return;
d->updatesOngoing = true;
d->periodicTimer.start();
}
void QGeoPositionInfoSourceWinRT::stopUpdates()
{
qCDebug(lcPositioningWinRT) << __FUNCTION__;
Q_D(QGeoPositionInfoSourceWinRT);
if (init() < 0)
return;
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()) {
setError(QGeoPositionInfoSource::AccessError);
return false;
}
HRESULT hr = [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<IAsyncOperation<Geoposition*>> op;
hr = d->locator->GetGeopositionAsync(&op);
RETURN_HR_IF_FAILED("Could not start position operation");
hr = d->locator->add_PositionChanged(Callback<GeoLocatorPositionHandler>(this,
&QGeoPositionInfoSourceWinRT::onPositionChanged).Get(),
&d->positionToken);
RETURN_HR_IF_FAILED("Could not add position handler");
hr = d->locator->add_StatusChanged(Callback<GeoLocatorStatusHandler>(this,
&QGeoPositionInfoSourceWinRT::onStatusChanged).Get(),
&d->statusToken);
RETURN_HR_IF_FAILED("Could not add status 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;
d->locator->remove_PositionChanged(d->positionToken);
d->locator->remove_StatusChanged(d->statusToken);
d->positionToken.value = 0;
d->statusToken.value = 0;
}
void QGeoPositionInfoSourceWinRT::requestUpdate(int timeout)
{
qCDebug(lcPositioningWinRT) << __FUNCTION__ << timeout;
Q_D(QGeoPositionInfoSourceWinRT);
if (init() < 0)
return;
setError(QGeoPositionInfoSource::NoError);
if (timeout != 0 && timeout < minimumUpdateInterval()) {
d->positionError = QGeoPositionInfoSource::UpdateTimeoutError;
emit QGeoPositionInfoSource::errorOccurred(d->positionError);
return;
}
if (timeout == 0)
timeout = 2*60*1000; // Maximum time for cold start (see Android)
if (startHandler())
d->singleUpdateTimer.start(timeout);
}
void QGeoPositionInfoSourceWinRT::virtualPositionUpdate()
{
qCDebug(lcPositioningWinRT) << __FUNCTION__;
Q_D(QGeoPositionInfoSourceWinRT);
QMutexLocker locker(&d->mutex);
// 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()) {
d->positionError = QGeoPositionInfoSource::UpdateTimeoutError;
emit QGeoPositionInfoSource::errorOccurred(d->positionError);
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;
// If the last encountered error was "Access denied", it is possible that the location service
// has been enabled by now so that we are clear again.
if ((d->positionError == QGeoPositionInfoSource::AccessError
|| d->positionError == QGeoPositionInfoSource::UnknownSourceError) && requestAccess())
return QGeoPositionInfoSource::NoError;
return d->positionError;
}
void QGeoPositionInfoSourceWinRT::setError(QGeoPositionInfoSource::Error positionError)
{
Q_D(QGeoPositionInfoSourceWinRT);
if (positionError == d->positionError)
return;
qCDebug(lcPositioningWinRT) << __FUNCTION__ << positionError;
d->positionError = positionError;
if (positionError != QGeoPositionInfoSource::NoError)
emit QGeoPositionInfoSource::errorOccurred(positionError);
}
void QGeoPositionInfoSourceWinRT::reactOnError(QGeoPositionInfoSource::Error positionError)
{
setError(positionError);
stopUpdates();
}
HRESULT QGeoPositionInfoSourceWinRT::onPositionChanged(IGeolocator *locator, IPositionChangedEventArgs *args)
{
qCDebug(lcPositioningWinRT) << __FUNCTION__;
Q_UNUSED(locator);
HRESULT hr;
ComPtr<IGeoposition> position;
hr = args->get_Position(&position);
RETURN_HR_IF_FAILED("Could not access position object.");
QGeoPositionInfo currentInfo;
ComPtr<IGeocoordinate> coord;
hr = position->get_Coordinate(&coord);
if (FAILED(hr))
qErrnoWarning(hr, "Could not access coordinate");
ComPtr<IGeocoordinateWithPoint> pointCoordinate;
hr = coord.As(&pointCoordinate);
if (FAILED(hr))
qErrnoWarning(hr, "Could not cast coordinate.");
ComPtr<IGeopoint> 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<IGeoshape> 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<double> *altAccuracy;
hr = coord->get_AltitudeAccuracy(&altAccuracy);
if (SUCCEEDED(hr) && altAccuracy) {
double value;
hr = altAccuracy->get_Value(&value);
currentInfo.setAttribute(QGeoPositionInfo::VerticalAccuracy, value);
}
IReference<double> *speed;
hr = coord->get_Speed(&speed);
if (SUCCEEDED(hr) && speed) {
double value;
hr = speed->get_Value(&value);
currentInfo.setAttribute(QGeoPositionInfo::GroundSpeed, value);
}
IReference<double> *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<int>(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),
QTimeZone::UTC));
}
}
emit nativePositionUpdate(currentInfo);
return S_OK;
}
static inline bool isDisabledStatus(PositionStatus status)
{
return status == PositionStatus_NoData || status == PositionStatus_Disabled
|| status == PositionStatus_NotAvailable;
}
HRESULT QGeoPositionInfoSourceWinRT::onStatusChanged(IGeolocator *, IStatusChangedEventArgs *args)
{
Q_D(QGeoPositionInfoSourceWinRT);
const PositionStatus oldStatus = d->positionStatus;
HRESULT hr = args->get_Status(&d->positionStatus);
RETURN_HR_IF_FAILED("Could not obtain position status");
qCDebug(lcPositioningWinRT) << __FUNCTION__ << d->positionStatus;
QGeoPositionInfoSource::Error error = QGeoPositionInfoSource::NoError;
switch (d->positionStatus) {
case PositionStatus::PositionStatus_NotAvailable:
error = QGeoPositionInfoSource::UnknownSourceError;
break;
case PositionStatus::PositionStatus_Disabled:
error = QGeoPositionInfoSource::AccessError;
break;
case PositionStatus::PositionStatus_NoData:
error = QGeoPositionInfoSource::ClosedError;
break;
}
if (error != QGeoPositionInfoSource::NoError) {
QMetaObject::invokeMethod(this, "reactOnError", Qt::QueuedConnection,
Q_ARG(QGeoPositionInfoSource::Error,
QGeoPositionInfoSource::UnknownSourceError));
}
if (isDisabledStatus(oldStatus) != isDisabledStatus(d->positionStatus))
emit supportedPositioningMethodsChanged();
return S_OK;
}
bool QGeoPositionInfoSourceWinRT::requestAccess() const
{
Q_D(const QGeoPositionInfoSourceWinRT);
qCDebug(lcPositioningWinRT) << __FUNCTION__;
GeolocationAccessStatus accessStatus;
ComPtr<IAsyncOperation<GeolocationAccessStatus>> op;
HRESULT hr = [&op, d]() {
HRESULT hr;
if (!d->statics) {
hr = RoGetActivationFactory(HString::MakeReference(RuntimeClass_Windows_Devices_Geolocation_Geolocator).Get(),
IID_PPV_ARGS(&d->statics));
RETURN_HR_IF_FAILED("Could not access Geolocation Statics.");
}
hr = d->statics->RequestAccessAsync(&op);
return hr;
}();
if (FAILED(hr)) {
qCDebug(lcPositioningWinRT) << __FUNCTION__ << "Requesting access from Xaml thread failed";
return false;
}
await(op, &accessStatus);
return accessStatus == GeolocationAccessStatus_Allowed;
}
QT_END_NAMESPACE