/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtWebEngine module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** 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 https://www.qt.io/terms-conditions. For further ** information use the contact form at https://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.LGPL3 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-3.0.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 (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "location_provider_qt.h" #include #include "type_conversion.h" #include #include #include #include #include "base/bind.h" #include "base/memory/weak_ptr.h" #include "base/message_loop/message_loop.h" #include "content/public/browser/browser_thread.h" #include "device/geolocation/geolocation_provider.h" #include "device/geolocation/geolocation_provider_impl.h" namespace QtWebEngineCore { using content::BrowserThread; class QtPositioningHelper : public QObject { Q_OBJECT public: QtPositioningHelper(LocationProviderQt *provider); ~QtPositioningHelper(); Q_INVOKABLE void start(bool highAccuracy); Q_INVOKABLE void stop(); Q_INVOKABLE void refresh(); private Q_SLOTS: void updatePosition(const QGeoPositionInfo &); void error(QGeoPositionInfoSource::Error positioningError); void timeout(); private: LocationProviderQt *m_locationProvider; QGeoPositionInfoSource *m_positionInfoSource; base::WeakPtrFactory m_locationProviderFactory; void postToLocationProvider(const base::Closure &task); friend class LocationProviderQt; }; QtPositioningHelper::QtPositioningHelper(LocationProviderQt *provider) : m_locationProvider(provider) , m_positionInfoSource(0) , m_locationProviderFactory(provider) { Q_ASSERT(provider); } QtPositioningHelper::~QtPositioningHelper() { } static bool isHighAccuracySource(const QGeoPositionInfoSource *source) { return source->supportedPositioningMethods().testFlag( QGeoPositionInfoSource::SatellitePositioningMethods); } void QtPositioningHelper::start(bool highAccuracy) { DCHECK_CURRENTLY_ON(BrowserThread::UI); if (!m_positionInfoSource) m_positionInfoSource = QGeoPositionInfoSource::createDefaultSource(this); if (!m_positionInfoSource) { qWarning("Failed to initialize location provider: The system either has no default " "position source, no valid plugins could be found or the user does not have " "the right permissions."); error(QGeoPositionInfoSource::UnknownSourceError); return; } // Find high accuracy source if the default source is not already one. if (highAccuracy && !isHighAccuracySource(m_positionInfoSource)) { const QStringList availableSources = QGeoPositionInfoSource::availableSources(); for (const QString &name : availableSources) { if (name == m_positionInfoSource->sourceName()) continue; QGeoPositionInfoSource *source = QGeoPositionInfoSource::createSource(name, this); if (source && isHighAccuracySource(source)) { delete m_positionInfoSource; m_positionInfoSource = source; break; } delete source; } m_positionInfoSource->setPreferredPositioningMethods( QGeoPositionInfoSource::SatellitePositioningMethods); } connect(m_positionInfoSource, &QGeoPositionInfoSource::positionUpdated, this, &QtPositioningHelper::updatePosition); // disambiguate the error getter and the signal in QGeoPositionInfoSource. connect(m_positionInfoSource, static_cast(&QGeoPositionInfoSource::error) , this, &QtPositioningHelper::error); connect(m_positionInfoSource, &QGeoPositionInfoSource::updateTimeout, this, &QtPositioningHelper::timeout); m_positionInfoSource->startUpdates(); return; } void QtPositioningHelper::stop() { DCHECK_CURRENTLY_ON(BrowserThread::UI); if (!m_positionInfoSource) return; m_positionInfoSource->stopUpdates(); } void QtPositioningHelper::refresh() { DCHECK_CURRENTLY_ON(BrowserThread::UI); if (!m_positionInfoSource) return; m_positionInfoSource->stopUpdates(); } void QtPositioningHelper::updatePosition(const QGeoPositionInfo &pos) { if (!pos.isValid()) return; Q_ASSERT(m_positionInfoSource->error() == QGeoPositionInfoSource::NoError); device::mojom::Geoposition newPos; newPos.error_code = device::mojom::Geoposition::ErrorCode::NONE; newPos.error_message.clear(); newPos.timestamp = toTime(pos.timestamp()); newPos.latitude = pos.coordinate().latitude(); newPos.longitude = pos.coordinate().longitude(); const double altitude = pos.coordinate().altitude(); if (!qIsNaN(altitude)) newPos.altitude = altitude; // Chromium's geoposition needs a valid (as in >=0.) accuracy field. // try and get an accuracy estimate from QGeoPositionInfo. // If we don't have any accuracy info, 100m seems a pesimistic enough default. if (!pos.hasAttribute(QGeoPositionInfo::VerticalAccuracy) && !pos.hasAttribute(QGeoPositionInfo::HorizontalAccuracy)) newPos.accuracy = 100; else { const double vAccuracy = pos.hasAttribute(QGeoPositionInfo::VerticalAccuracy) ? pos.attribute(QGeoPositionInfo::VerticalAccuracy) : 0; const double hAccuracy = pos.hasAttribute(QGeoPositionInfo::HorizontalAccuracy) ? pos.attribute(QGeoPositionInfo::HorizontalAccuracy) : 0; newPos.accuracy = sqrt(vAccuracy * vAccuracy + hAccuracy * hAccuracy); } // And now the "nice to have" fields (-1 means invalid). newPos.speed = pos.hasAttribute(QGeoPositionInfo::GroundSpeed) ? pos.attribute(QGeoPositionInfo::GroundSpeed) : -1; newPos.heading = pos.hasAttribute(QGeoPositionInfo::Direction) ? pos.attribute(QGeoPositionInfo::Direction) : -1; if (m_locationProvider) postToLocationProvider(base::Bind(&LocationProviderQt::updatePosition, m_locationProviderFactory.GetWeakPtr(), newPos)); } void QtPositioningHelper::error(QGeoPositionInfoSource::Error positioningError) { Q_ASSERT(positioningError != QGeoPositionInfoSource::NoError); device::mojom::Geoposition newPos; switch (positioningError) { case QGeoPositionInfoSource::AccessError: newPos.error_code = device::mojom::Geoposition::ErrorCode::PERMISSION_DENIED; break; case QGeoPositionInfoSource::ClosedError: case QGeoPositionInfoSource::UnknownSourceError: // position unavailable is as good as it gets in Geoposition default: newPos.error_code = device::mojom::Geoposition::ErrorCode::POSITION_UNAVAILABLE; break; } if (m_locationProvider) postToLocationProvider(base::Bind(&LocationProviderQt::updatePosition, m_locationProviderFactory.GetWeakPtr(), newPos)); } void QtPositioningHelper::timeout() { device::mojom::Geoposition newPos; // content::Geoposition::ERROR_CODE_TIMEOUT is not handled properly in the renderer process, and the timeout // argument used in JS never comes all the way to the browser process. // Let's just treat it like any other error where the position is unavailable. newPos.error_code = device::mojom::Geoposition::ErrorCode::POSITION_UNAVAILABLE; if (m_locationProvider) postToLocationProvider(base::Bind(&LocationProviderQt::updatePosition, m_locationProviderFactory.GetWeakPtr(), newPos)); } inline void QtPositioningHelper::postToLocationProvider(const base::Closure &task) { static_cast(device::GeolocationProvider::GetInstance())->task_runner()->PostTask(FROM_HERE, task); } LocationProviderQt::LocationProviderQt() : m_positioningHelper(0) { } LocationProviderQt::~LocationProviderQt() { if (m_positioningHelper) { m_positioningHelper->m_locationProvider = 0; m_positioningHelper->m_locationProviderFactory.InvalidateWeakPtrs(); m_positioningHelper->deleteLater(); } } void LocationProviderQt::StartProvider(bool highAccuracy) { QThread *guiThread = qApp->thread(); if (!m_positioningHelper) { m_positioningHelper = new QtPositioningHelper(this); m_positioningHelper->moveToThread(guiThread); } QMetaObject::invokeMethod(m_positioningHelper, "start", Qt::QueuedConnection, Q_ARG(bool, highAccuracy)); } void LocationProviderQt::StopProvider() { if (m_positioningHelper) QMetaObject::invokeMethod(m_positioningHelper, "stop", Qt::QueuedConnection); } void LocationProviderQt::OnPermissionGranted() { if (m_positioningHelper) QMetaObject::invokeMethod(m_positioningHelper, "refresh", Qt::QueuedConnection); } void LocationProviderQt::SetUpdateCallback(const LocationProviderUpdateCallback& callback) { m_callback = callback; } void LocationProviderQt::updatePosition(const device::mojom::Geoposition &position) { m_lastKnownPosition = position; m_callback.Run(this, position); } } // namespace QtWebEngineCore #include "location_provider_qt.moc"