diff options
author | Alex Wilson <alex.wilson@nokia.com> | 2012-03-07 16:38:50 +1000 |
---|---|---|
committer | Qt by Nokia <qt-info@nokia.com> | 2012-03-12 03:25:51 +0100 |
commit | 369fa99b132f8c1425da8a41f1436b8d86aaa50e (patch) | |
tree | a13d675390d1590df27c70a4ee47b6490cc58800 /src/plugins/position/geoclue/qgeopositioninfosource_geocluemaster.cpp | |
parent | c435861528161ee931287034f41023c11dfb7ba3 (diff) |
Update positioning sources to use new plugin style
Notably, also splits the default sources that were previously compiled
into the library, out into their own plugins. This follows a similar
pattern to the geoservices change.
We also drop the "plugin whitelisting" feature in favour of a simple
Priority value in the plugin JSON -- the whitelist provides no
additional security over this solution on any of our platforms.
Task-number: QTBUG-24331
Change-Id: I62a9c940157ad2e33a9a575fa09633b98656b276
Reviewed-by: Alex <alex.blasche@nokia.com>
Diffstat (limited to 'src/plugins/position/geoclue/qgeopositioninfosource_geocluemaster.cpp')
-rw-r--r-- | src/plugins/position/geoclue/qgeopositioninfosource_geocluemaster.cpp | 556 |
1 files changed, 556 insertions, 0 deletions
diff --git a/src/plugins/position/geoclue/qgeopositioninfosource_geocluemaster.cpp b/src/plugins/position/geoclue/qgeopositioninfosource_geocluemaster.cpp new file mode 100644 index 00000000..4da6c5e5 --- /dev/null +++ b/src/plugins/position/geoclue/qgeopositioninfosource_geocluemaster.cpp @@ -0,0 +1,556 @@ +/**************************************************************************** +** +** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/ +** +** This file is part of the QtLocation module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 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 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtCore> + +#ifdef Q_LOCATION_GEOCLUE_DEBUG +#include <QDebug> +#endif + +#include "qgeopositioninfosource_geocluemaster_p.h" +#include <gconf/gconf-client.h> + +QT_BEGIN_NAMESPACE + +#define MINIMUM_UPDATE_INTERVAL 1000 +#define UPDATE_TIMEOUT_COLD_START 120000 + +// Callback for position-changed -signal +static void position_changed (GeocluePosition *position, + GeocluePositionFields fields, + int timestamp, + double latitude, + double longitude, + double altitude, + GeoclueAccuracy *accuracy, + gpointer userdata) // Ptr to this +{ + Q_UNUSED(position); + if (!(fields & GEOCLUE_POSITION_FIELDS_LATITUDE && + fields & GEOCLUE_POSITION_FIELDS_LONGITUDE)) { + static_cast<QGeoPositionInfoSourceGeoclueMaster*>(userdata)->regularUpdateFailed(); + } else { + static_cast<QGeoPositionInfoSourceGeoclueMaster*>(userdata)->regularUpdateSucceeded( + fields, timestamp, latitude, longitude, altitude, accuracy); + } +} + +// Callback for velocity-changed -signal +static void velocity_changed (GeoclueVelocity *velocity, + GeoclueVelocityFields fields, + int timestamp, + double speed, + double direction, + double climb, + gpointer userdata) // Ptr to this +{ + Q_UNUSED(velocity) + Q_UNUSED(timestamp) + Q_UNUSED(direction) + Q_UNUSED(climb) + if (!(fields & GEOCLUE_VELOCITY_FIELDS_SPEED)) { + static_cast<QGeoPositionInfoSourceGeoclueMaster*>(userdata)->velocityUpdateFailed(); + return; + } + static_cast<QGeoPositionInfoSourceGeoclueMaster*>(userdata)->velocityUpdateSucceeded(speed); +} + +// Callback for single async update +static void position_callback (GeocluePosition *pos, + GeocluePositionFields fields, + int timestamp, + double latitude, + double longitude, + double altitude, + GeoclueAccuracy *accuracy, + GError *error, + gpointer userdata) +{ + Q_UNUSED(pos); + Q_UNUSED(accuracy); + + if (error) + g_error_free (error); + if (!(fields & GEOCLUE_POSITION_FIELDS_LATITUDE && + fields & GEOCLUE_POSITION_FIELDS_LONGITUDE)) { + static_cast<QGeoPositionInfoSourceGeoclueMaster*>(userdata)->singleUpdateFailed(); + + } else { + static_cast<QGeoPositionInfoSourceGeoclueMaster*>(userdata)->singleUpdateSucceeded( + fields, timestamp, latitude, longitude, altitude, accuracy); + } +} + +QGeoPositionInfoSourceGeoclueMaster::QGeoPositionInfoSourceGeoclueMaster(QObject *parent) + : QGeoPositionInfoSource(parent), m_updateInterval(0), m_preferredResources(GEOCLUE_RESOURCE_ALL), + m_preferredAccuracy(GEOCLUE_ACCURACY_LEVEL_NONE), + m_client(0), m_pos(0), m_vel(0), m_lastPositionIsFresh(false), m_lastVelocityIsFresh(false), + m_lastVelocity(0), m_lastPositionFromSatellite(false), m_methods(AllPositioningMethods) +{ + m_requestTimer.setSingleShot(true); + QObject::connect(&m_requestTimer, SIGNAL(timeout()), this, SLOT(requestUpdateTimeout())); + QObject::connect(&m_updateTimer, SIGNAL(timeout()), this, SLOT(startUpdatesTimeout())); +} + +QGeoPositionInfoSourceGeoclueMaster::~QGeoPositionInfoSourceGeoclueMaster() +{ + if (m_pos) + g_object_unref (m_pos); + if (m_vel) + g_object_unref(m_vel); + if (m_client) + g_object_unref (m_client); +} + +void QGeoPositionInfoSourceGeoclueMaster::velocityUpdateFailed() +{ +#ifdef Q_LOCATION_GEOCLUE_DEBUG + qDebug() << "QGeoPositionInfoSourceGeoclueMaster velocity update failed."; +#endif + // Set the velocitydata non-fresh. + m_lastVelocityIsFresh = false; +} + +void QGeoPositionInfoSourceGeoclueMaster::velocityUpdateSucceeded(double speed) +{ +#ifdef Q_LOCATION_GEOCLUE_DEBUG + qDebug() << "QGeoPositionInfoSourceGeoclueMaster velocity update succeeded, speed: " << speed; +#endif + // Store the velocity and mark it as fresh. Simple but hopefully adequate. + m_lastVelocity = speed * 0.514444; // convert knots to m/s + m_lastVelocityIsFresh = true; +} + +void QGeoPositionInfoSourceGeoclueMaster::singleUpdateFailed() +{ +#ifdef Q_LOCATION_GEOCLUE_DEBUG + qDebug() << "QGeoPositionInfoSourceGeoclueMaster single update failed (requestUpdate)"; +#endif + if (m_requestTimer.isActive()) + m_requestTimer.stop(); + // Send timeout even if time wasn't up yet, because we are not trying again + emit updateTimeout(); +} + +void QGeoPositionInfoSourceGeoclueMaster::singleUpdateSucceeded(GeocluePositionFields fields, + int timestamp, + double latitude, + double longitude, + double altitude, + GeoclueAccuracy *accuracy) +{ + QGeoPositionInfo info = geoclueToPositionInfo(fields, timestamp, latitude, longitude, altitude, accuracy); + m_lastPosition = info; + if (m_requestTimer.isActive()) + m_requestTimer.stop(); + if (m_lastVelocityIsFresh) + info.setAttribute(QGeoPositionInfo::GroundSpeed, m_lastVelocity); // assume groundspeed +#ifdef Q_LOCATION_GEOCLUE_DEBUG + qDebug() << "QGeoPositionInfoSourceGeoclueMaster single update succeeded: "; + qDebug() << "Lat, lon, alt, speed:" << info.coordinate().latitude() << info.coordinate().longitude() << info.coordinate().altitude() << info.attribute(QGeoPositionInfo::GroundSpeed); +#endif + emit positionUpdated(info); +} + +void QGeoPositionInfoSourceGeoclueMaster::regularUpdateFailed() +{ +#ifdef Q_LOCATION_GEOCLUE_DEBUG + qDebug() << "QGeoPositionInfoSourceGeoclueMaster regular update failed."; +#endif + // Emit timeout and keep on listening in case error condition clears. + // Currently this is emitted each time an error occurs, and thereby it assumes + // that there does not come many erroneous updates from position source. + // This assumption may be invalid. + m_lastVelocityIsFresh = false; + m_lastPositionIsFresh = false; + if (m_updateTimer.isActive()) + emit updateTimeout(); +} + +void QGeoPositionInfoSourceGeoclueMaster::regularUpdateSucceeded(GeocluePositionFields fields, + int timestamp, + double latitude, + double longitude, + double altitude, + GeoclueAccuracy *accuracy) +{ + m_lastPosition = geoclueToPositionInfo(fields, timestamp, latitude, longitude, altitude, accuracy); + m_lastPositionIsFresh = true; + if (m_lastVelocityIsFresh) { + m_lastPosition.setAttribute(QGeoPositionInfo::GroundSpeed, m_lastVelocity); // assume groundspeed + m_lastVelocityIsFresh = false; + } + // If a non-intervalled startUpdates has been issued, send an update. + if (!m_updateTimer.isActive()) { + m_lastPositionIsFresh = false; + emit positionUpdated(m_lastPosition); + } +#ifdef Q_LOCATION_GEOCLUE_DEBUG + qDebug() << "QGeoPositionInfoSourceGeoclueMaster regular update succeeded: "; + qDebug() << "Lat, lon, alt, speed:" << m_lastPosition.coordinate().latitude() << m_lastPosition.coordinate().longitude() << m_lastPosition.coordinate().altitude() << m_lastPosition.attribute(QGeoPositionInfo::GroundSpeed); +#endif +} + +bool QGeoPositionInfoSourceGeoclueMaster::tryGPS() +{ + // Check if the gconf value is set properly + GConfClient* client; + gchar* device_name; + client = gconf_client_get_default(); + device_name = gconf_client_get_string(client, "/apps/geoclue/master/org.freedesktop.Geoclue.GPSDevice", NULL); + QString deviceName(QString::fromAscii(device_name)); + g_object_unref(client); + g_free(device_name); + + if (deviceName.isEmpty()) { + return false; + } else { + // Check if the device exists (does nothing if a bluetooth address) + if (deviceName.trimmed().at(0) == '/' && QFile::exists(deviceName.trimmed())) { + return true; + } + return false; + } +} + +int QGeoPositionInfoSourceGeoclueMaster::init() +{ + g_type_init (); + // Check if there is sense to try GPS + if (tryGPS()) { + m_preferredResources = GEOCLUE_RESOURCE_GPS; + m_preferredAccuracy = GEOCLUE_ACCURACY_LEVEL_DETAILED; + if (configurePositionSource() != -1) { + return 0; + } else { + // If not successful, try to get any resource + m_preferredResources = GEOCLUE_RESOURCE_ALL; + m_preferredAccuracy = GEOCLUE_ACCURACY_LEVEL_NONE; + return configurePositionSource(); + } + } else { + return configurePositionSource(); + } +} + +int QGeoPositionInfoSourceGeoclueMaster::configurePositionSource() +{ + GeoclueMaster *master(0); + GError *error = 0; + // Free potential previous sources, because new requirements can't be set for the client + // (creating a position object after changing requirements seems to fail). + if (m_client) { + g_object_unref (m_client); + m_client = 0; + } + if (m_pos) { + g_object_unref(m_pos); + m_pos = 0; + } + if (m_vel) { + g_object_unref(m_vel); + m_vel = 0; + } + + master = geoclue_master_get_default (); + if (!master) { + qCritical ("QGeoPositionInfoSourceGeoclueMaster error creating GeoclueMaster"); + return -1; + } + + m_client = geoclue_master_create_client (master, NULL, &error); + g_object_unref (master); + + if (!m_client) { + qCritical ("QGeoPositionInfoSourceGeoclueMaster error creating GeoclueMasterClient."); + if (error) { + qCritical (error->message); + g_error_free (error); + } + return -1; + } + + if (!geoclue_master_client_set_requirements (m_client, + m_preferredAccuracy, // min_accuracy + 0, // min_time + TRUE, // require_updates (signals) + m_preferredResources, + &error)){ + qCritical ("QGeoPositionInfoSourceGeoclueMaster geoclue set_requirements failed."); + if (error) { + qCritical (error->message); + g_error_free (error); + } + g_object_unref (m_client); + m_client = 0; + return -1; + } + m_pos = geoclue_master_client_create_position (m_client, NULL); + if (!m_pos) { + qCritical("QGeoPositionInfoSourceGeoclueMaster failed to get a position object"); + g_object_unref (m_client); + m_client = 0; + return -1; + } + // Succeeding velocity is not mandatory. Master does not provide abstraction + // for velocity provider, hence request Gypsy provider directly. + m_vel = geoclue_velocity_new("org.freedesktop.Geoclue.Providers.Gypsy", + "/org/freedesktop/Geoclue/Providers/Gypsy"); +#ifdef Q_LOCATION_GEOCLUE_DEBUG + if (m_vel == NULL) + qDebug("QGeoPositionInfoSourceGeoclueMaster velocity provider (Gypsy) not available."); +#endif + return 0; +} + +void QGeoPositionInfoSourceGeoclueMaster::setUpdateInterval(int msec) +{ + msec = (((msec > 0) && (msec < minimumUpdateInterval())) || msec < 0)? minimumUpdateInterval() : msec; + QGeoPositionInfoSource::setUpdateInterval(msec); + m_updateInterval = msec; + // If update timer is running, set the new interval + if (m_updateTimer.isActive()) { + m_updateTimer.setInterval(msec); + } +} + +void QGeoPositionInfoSourceGeoclueMaster::setPreferredPositioningMethods(PositioningMethods methods) +{ + if (methods == m_methods) + return; + m_methods = methods; + + switch (methods) { + case SatellitePositioningMethods: + m_preferredResources = GEOCLUE_RESOURCE_GPS; + m_preferredAccuracy = GEOCLUE_ACCURACY_LEVEL_DETAILED; + break; + case NonSatellitePositioningMethods: + m_preferredResources = (GeoclueResourceFlags)(GEOCLUE_RESOURCE_CELL | GEOCLUE_RESOURCE_NETWORK); + m_preferredAccuracy = GEOCLUE_ACCURACY_LEVEL_NONE; + break; + case AllPositioningMethods: + m_preferredResources = GEOCLUE_RESOURCE_ALL; + m_preferredAccuracy = GEOCLUE_ACCURACY_LEVEL_NONE; + break; + default: + qWarning("GeoPositionInfoSourceGeoClueMaster unknown preferred method."); + return; + } + QGeoPositionInfoSource::setPreferredPositioningMethods(methods); +#ifdef Q_LOCATION_GEOCLUE_DEBUG + qDebug() << "QGeoPositionInfoSourceGeoclueMaster requested to set methods to, and set them to: " << methods << m_preferredResources; +#endif + m_lastPositionIsFresh = false; + m_lastVelocityIsFresh = false; + int status = configurePositionSource(); + + // If updates ongoing, connect to the new objects + if (m_updateTimer.isActive()) { + if (status != -1) { + g_signal_connect (G_OBJECT (m_pos), "position-changed", + G_CALLBACK (position_changed),this); + if (m_vel) { + g_signal_connect (G_OBJECT (m_vel), "velocity-changed", + G_CALLBACK (velocity_changed),this); + } + } else { + // Changing source failed and there was active reques + m_updateTimer.stop(); + emit updateTimeout(); + } + } + // If a request ongoing, ask it from new object + if (m_requestTimer.isActive()) { + if ( status != -1) { + geoclue_position_get_position_async (m_pos, + (GeocluePositionCallback)position_callback, + this); + } else { + // Changing source failed and there was active reques + m_requestTimer.stop(); + emit updateTimeout(); + } + } +} + +QGeoPositionInfo QGeoPositionInfoSourceGeoclueMaster::lastKnownPosition(bool fromSatellitePositioningMethodsOnly) const +{ + if (fromSatellitePositioningMethodsOnly) { + if (m_lastPositionFromSatellite) + return m_lastPosition; + else + return QGeoPositionInfo(); + } + return m_lastPosition; +} + +QGeoPositionInfoSourceGeoclueMaster::PositioningMethods QGeoPositionInfoSourceGeoclueMaster::supportedPositioningMethods() const +{ + // There is no really knowing which methods the GeoClue master supports. + return AllPositioningMethods; +} + +void QGeoPositionInfoSourceGeoclueMaster::startUpdates() +{ + if (m_updateTimer.isActive()) { +#ifdef Q_LOCATION_GEOCLUE_DEBUG + qDebug() << "QGeoPositionInfoSourceGeoclueMaster timer was active, ignoring startUpdates: " << m_updateInterval; +#endif + return; + } + if (!m_pos) { + // May happen if source has been changed unsuccesfully + emit updateTimeout(); + return; + } + if (m_updateInterval > 0) { +#ifdef Q_LOCATION_GEOCLUE_DEBUG + qDebug() << "QGeoPositionInfoSourceGeoclueMaster startUpdates with interval: " << m_updateInterval; +#endif + m_updateTimer.start(m_updateInterval); + } + g_signal_connect (G_OBJECT (m_pos), "position-changed", + G_CALLBACK (position_changed),this); + if (m_vel) { + g_signal_connect (G_OBJECT (m_vel), "velocity-changed", + G_CALLBACK (velocity_changed),this); + } +} + +int QGeoPositionInfoSourceGeoclueMaster::minimumUpdateInterval() const { + return MINIMUM_UPDATE_INTERVAL; +} + +void QGeoPositionInfoSourceGeoclueMaster::stopUpdates() +{ + if (m_updateTimer.isActive()) + m_updateTimer.stop(); + if (m_pos) { + g_signal_handlers_disconnect_by_func(G_OBJECT(m_pos), (void*)position_changed, this); + } if (m_vel) { + g_signal_handlers_disconnect_by_func(G_OBJECT(m_vel), (void*)velocity_changed, this); + } +} + +void QGeoPositionInfoSourceGeoclueMaster::requestUpdate(int timeout) +{ + if ((timeout < minimumUpdateInterval() && timeout != 0) || !m_pos) { + emit updateTimeout(); + return; + } + if (m_requestTimer.isActive()) { +#ifdef Q_LOCATION_GEOCLUE_DEBUG + qDebug() << "QGeoPositionInfoSourceGeoclueMaster request timer was active, ignoring startUpdates."; +#endif + return; + } + // Create better logic for timeout value (specs leave it impl dependant). + // Especially if there are active updates ongoing, there is no point of waiting + // for whole cold start time. + if (timeout == 0) + m_requestTimer.start(UPDATE_TIMEOUT_COLD_START); + else + m_requestTimer.start(timeout); + geoclue_position_get_position_async (m_pos, (GeocluePositionCallback)position_callback,this); +} + +void QGeoPositionInfoSourceGeoclueMaster::requestUpdateTimeout() +{ +#ifdef Q_LOCATION_GEOCLUE_DEBUG + qDebug() << "QGeoPositionInfoSourceGeoclueMaster requestUpdate timeout occurred."; +#endif + // If we end up here, there has not been valid position update. + emit updateTimeout(); +} + +void QGeoPositionInfoSourceGeoclueMaster::startUpdatesTimeout() +{ +#ifdef Q_LOCATION_GEOCLUE_DEBUG + qDebug() << "QGeoPositionInfoSourceGeoclueMaster startUpdates timeout occurred."; +#endif + // Check if there are position updates since last positionUpdated(). + // Do not however send timeout, that's reserved for signaling errors. + if (m_lastPositionIsFresh) { + emit positionUpdated(m_lastPosition); + m_lastPositionIsFresh = false; + m_lastVelocityIsFresh = false; + } +} + +// Helper function to convert data into a QGeoPositionInfo +QGeoPositionInfo QGeoPositionInfoSourceGeoclueMaster::geoclueToPositionInfo( + GeocluePositionFields fields, + int timestamp, + double latitude, + double longitude, + double altitude, + GeoclueAccuracy* accuracy) +{ + QGeoCoordinate coordinate(latitude, longitude); + QDateTime dateTime = QDateTime(); + dateTime.setTime_t(timestamp); + if (fields & GEOCLUE_POSITION_FIELDS_ALTITUDE) { + coordinate.setAltitude(altitude); + } + QGeoPositionInfo info(coordinate, dateTime); + if (accuracy) { + double horizontalAccuracy; + double verticalAccuracy; + GeoclueAccuracyLevel accuracyLevel; + geoclue_accuracy_get_details(accuracy, &accuracyLevel, &horizontalAccuracy, &verticalAccuracy); +#ifdef Q_LOCATION_GEOCLUE_DEBUG + qDebug() << "QGeoPositionInfoSourceGeoclueMaster::accuracy horizontal vertical level: " << horizontalAccuracy << verticalAccuracy << accuracyLevel; +#endif + if (accuracyLevel & GEOCLUE_ACCURACY_LEVEL_DETAILED) + m_lastPositionFromSatellite = true; + else + m_lastPositionFromSatellite = false; + if (accuracyLevel != GEOCLUE_ACCURACY_LEVEL_NONE) { + info.setAttribute(QGeoPositionInfo::HorizontalAccuracy, horizontalAccuracy); + info.setAttribute(QGeoPositionInfo::VerticalAccuracy, verticalAccuracy); + } + } + return info; +} + +#include "moc_qgeopositioninfosource_geocluemaster_p.cpp" +QT_END_NAMESPACE |