summaryrefslogtreecommitdiff
path: root/src/plugins/position/positionpoll
diff options
context:
space:
mode:
authorAlex Blasche <alexander.blasche@digia.com>2013-08-26 09:35:01 +0200
committerThe Qt Project <gerrit-noreply@qt-project.org>2013-09-17 11:38:14 +0200
commit0ed9f7496656fa0ea52d703c7fddff26c2192857 (patch)
treeec0d4d7dc638e8018a8896a0b62cc91840c9c23d /src/plugins/position/positionpoll
parent87ce030b7b7336e561779dc9516d5ae2242a5d5f (diff)
downloadqtlocation-0ed9f7496656fa0ea52d703c7fddff26c2192857.tar.gz
Improve area monitoring API.
1.) QGeoAreaMonitor renamed to QGeoAreaMonitorSource 2.) Add new QGeoAreaMonitorInfo data type encpsulating individual areas to be monitored 3.) Port positionpoll plug-in to new features 4.) Make positionpoll monitor thread safe 4.) Extend and fix the QGeoAreaMonitor unit test 5.) Fix documentation. Task-number: QTBUG-31711 Change-Id: Icfc982de4753d2f43cb4d15c234eb7b7c039a0c4 Reviewed-by: Alex Blasche <alexander.blasche@digia.com>
Diffstat (limited to 'src/plugins/position/positionpoll')
-rw-r--r--src/plugins/position/positionpoll/positionpollfactory.cpp5
-rw-r--r--src/plugins/position/positionpoll/positionpollfactory.h2
-rw-r--r--src/plugins/position/positionpoll/qgeoareamonitor_polling.cpp480
-rw-r--r--src/plugins/position/positionpoll/qgeoareamonitor_polling.h42
4 files changed, 464 insertions, 65 deletions
diff --git a/src/plugins/position/positionpoll/positionpollfactory.cpp b/src/plugins/position/positionpoll/positionpollfactory.cpp
index a3ec6527..0f02bff1 100644
--- a/src/plugins/position/positionpoll/positionpollfactory.cpp
+++ b/src/plugins/position/positionpoll/positionpollfactory.cpp
@@ -54,10 +54,9 @@ QGeoSatelliteInfoSource *PositionPollFactory::satelliteInfoSource(QObject *paren
return 0;
}
-QGeoAreaMonitor *PositionPollFactory::areaMonitor(QObject *parent)
+QGeoAreaMonitorSource *PositionPollFactory::areaMonitor(QObject *parent)
{
- QGeoAreaMonitorPolling *ret = 0;
- ret = new QGeoAreaMonitorPolling(parent);
+ QGeoAreaMonitorPolling *ret = new QGeoAreaMonitorPolling(parent);
if (ret && ret->isValid())
return ret;
delete ret;
diff --git a/src/plugins/position/positionpoll/positionpollfactory.h b/src/plugins/position/positionpoll/positionpollfactory.h
index 73a4d60a..79ad85eb 100644
--- a/src/plugins/position/positionpoll/positionpollfactory.h
+++ b/src/plugins/position/positionpoll/positionpollfactory.h
@@ -54,7 +54,7 @@ class PositionPollFactory : public QObject, public QGeoPositionInfoSourceFactory
public:
QGeoPositionInfoSource *positionInfoSource(QObject *parent);
QGeoSatelliteInfoSource *satelliteInfoSource(QObject *parent);
- QGeoAreaMonitor *areaMonitor(QObject *parent);
+ QGeoAreaMonitorSource *areaMonitor(QObject *parent);
};
#endif // POSITIONPOLLFACTORY_H
diff --git a/src/plugins/position/positionpoll/qgeoareamonitor_polling.cpp b/src/plugins/position/positionpoll/qgeoareamonitor_polling.cpp
index 784c9fad..55d66993 100644
--- a/src/plugins/position/positionpoll/qgeoareamonitor_polling.cpp
+++ b/src/plugins/position/positionpoll/qgeoareamonitor_polling.cpp
@@ -41,94 +41,476 @@
#include "qgeoareamonitor_polling.h"
#include <qgeocoordinate.h>
+#include <qgeorectangle.h>
+#include <qgeocircle.h>
#include <QtCore/qmetaobject.h>
+#include <QtCore/qtimer.h>
+#include <QtCore/qdebug.h>
+#include <QtCore/qmutex.h>
#define UPDATE_INTERVAL_5S 5000
-QGeoAreaMonitorPolling::QGeoAreaMonitorPolling(QObject *parent) : QGeoAreaMonitor(parent)
+typedef QHash<QString, QGeoAreaMonitorInfo> MonitorTable;
+
+
+static QMetaMethod areaEnteredSignal()
{
- insideArea = false;
- location = QGeoPositionInfoSource::createDefaultSource(this);
- if (location) {
- location->setUpdateInterval(UPDATE_INTERVAL_5S);
- connect(location, SIGNAL(positionUpdated(QGeoPositionInfo)),
- this, SLOT(positionUpdated(QGeoPositionInfo)));
- }
+ static QMetaMethod signal = QMetaMethod::fromSignal(&QGeoAreaMonitorPolling::areaEntered);
+ return signal;
}
-QGeoAreaMonitorPolling::~QGeoAreaMonitorPolling()
+static QMetaMethod areaExitedSignal()
{
- if (location)
- location->stopUpdates();
+ static QMetaMethod signal = QMetaMethod::fromSignal(&QGeoAreaMonitorPolling::areaExited);
+ return signal;
+}
+
+static QMetaMethod monitorExpiredSignal()
+{
+ static QMetaMethod signal = QMetaMethod::fromSignal(&QGeoAreaMonitorPolling::monitorExpired);
+ return signal;
}
-void QGeoAreaMonitorPolling::setCenter(const QGeoCoordinate &coordinate)
+class QGeoAreaMonitorPollingPrivate : public QObject
{
- if (coordinate.isValid()) {
- QGeoAreaMonitor::setCenter(coordinate);
+ Q_OBJECT
+public:
+ QGeoAreaMonitorPollingPrivate() : source(0), mutex(QMutex::Recursive)
+ {
+ nextExpiryTimer = new QTimer(this);
+ nextExpiryTimer->setSingleShot(true);
+ connect(nextExpiryTimer, SIGNAL(timeout()),
+ this, SLOT(timeout()));
+ }
+
+ void startMonitoring(const QGeoAreaMonitorInfo &monitor)
+ {
+ QMutexLocker locker(&mutex);
+
+ activeMonitorAreas.insert(monitor.identifier(), monitor);
+ singleShotTrigger.remove(monitor.identifier());
+
+ checkStartStop();
+ setupNextExpiryTimeout();
+ }
+
+ void requestUpdate(const QGeoAreaMonitorInfo &monitor, int signalId)
+ {
+ QMutexLocker locker(&mutex);
+
+ activeMonitorAreas.insert(monitor.identifier(), monitor);
+ singleShotTrigger.insert(monitor.identifier(), signalId);
+
+ checkStartStop();
+ setupNextExpiryTimeout();
+ }
+
+ QGeoAreaMonitorInfo stopMonitoring(const QGeoAreaMonitorInfo &monitor)
+ {
+ QMutexLocker locker(&mutex);
+
+ QGeoAreaMonitorInfo mon = activeMonitorAreas.take(monitor.identifier());
+
checkStartStop();
+ setupNextExpiryTimeout();
+
+ return mon;
}
+
+ void registerClient(QGeoAreaMonitorPolling *client)
+ {
+ QMutexLocker locker(&mutex);
+
+ connect(this, SIGNAL(timeout(QGeoAreaMonitorInfo)),
+ client, SLOT(timeout(QGeoAreaMonitorInfo)));
+
+ connect(this, SIGNAL(positionError(QGeoPositionInfoSource::Error)),
+ client, SLOT(positionError(QGeoPositionInfoSource::Error)));
+
+ connect(this, SIGNAL(areaEventDetected(QGeoAreaMonitorInfo,QGeoPositionInfo,bool)),
+ client, SLOT(processAreaEvent(QGeoAreaMonitorInfo,QGeoPositionInfo,bool)));
+
+ registeredClients.append(client);
+ }
+
+ void deregisterClient(QGeoAreaMonitorPolling *client)
+ {
+ QMutexLocker locker(&mutex);
+
+ registeredClients.removeAll(client);
+ if (registeredClients.isEmpty())
+ checkStartStop();
+ }
+
+ void setPositionSource(QGeoPositionInfoSource *newSource)
+ {
+ QMutexLocker locker(&mutex);
+
+ if (newSource == source)
+ return;
+
+ if (source)
+ delete source;
+
+ source = newSource;
+
+ if (source) {
+ source->setParent(this);
+ source->moveToThread(this->thread());
+ if (source->updateInterval() == 0)
+ source->setUpdateInterval(UPDATE_INTERVAL_5S);
+ disconnect(source, 0, 0, 0); //disconnect all
+ connect(source, SIGNAL(positionUpdated(QGeoPositionInfo)),
+ this, SLOT(positionUpdated(QGeoPositionInfo)));
+ connect(source, SIGNAL(error(QGeoPositionInfoSource::Error)),
+ this, SIGNAL(positionError(QGeoPositionInfoSource::Error)));
+ checkStartStop();
+ }
+ }
+
+ QGeoPositionInfoSource* positionSource() const
+ {
+ QMutexLocker locker(&mutex);
+ return source;
+ }
+
+ MonitorTable activeMonitors() const
+ {
+ QMutexLocker locker(&mutex);
+
+ return activeMonitorAreas;
+ }
+
+ void checkStartStop()
+ {
+ QMutexLocker locker(&mutex);
+
+ bool signalsConnected = false;
+ foreach (const QGeoAreaMonitorPolling *client, registeredClients) {
+ if (client->signalsAreConnected) {
+ signalsConnected = true;
+ break;
+ }
+ }
+
+ if (signalsConnected && !activeMonitorAreas.isEmpty()) {
+ if (source)
+ source->startUpdates();
+ else
+ //translated to InsufficientPositionInfo
+ emit positionError(QGeoPositionInfoSource::ClosedError);
+ } else {
+ if (source)
+ source->stopUpdates();
+ }
+ }
+
+private:
+ void setupNextExpiryTimeout()
+ {
+ nextExpiryTimer->stop();
+ activeExpiry.first = QDateTime();
+ activeExpiry.second = QString();
+
+ foreach (const QGeoAreaMonitorInfo &info, activeMonitors()) {
+ if (info.expiration().isValid()) {
+ if (!activeExpiry.first.isValid()) {
+ activeExpiry.first = info.expiration();
+ activeExpiry.second = info.identifier();
+ continue;
+ }
+ if (info.expiration() < activeExpiry.first) {
+ activeExpiry.first = info.expiration();
+ activeExpiry.second = info.identifier();
+ }
+ }
+ }
+
+ if (activeExpiry.first.isValid())
+ nextExpiryTimer->start(QDateTime::currentDateTime().msecsTo(activeExpiry.first));
+ }
+
+
+ //returns true if areaEntered should be emitted
+ bool processInsideArea(const QString &monitorIdent)
+ {
+ if (!insideArea.contains(monitorIdent)) {
+ if (singleShotTrigger.value(monitorIdent, -1) == areaEnteredSignal().methodIndex()) {
+ //this is the finishing singleshot event
+ singleShotTrigger.remove(monitorIdent);
+ activeMonitorAreas.remove(monitorIdent);
+ setupNextExpiryTimeout();
+ } else {
+ insideArea.insert(monitorIdent);
+ }
+ return true;
+ }
+
+ return false;
+ }
+
+ //returns true if areaExited should be emitted
+ bool processOutsideArea(const QString &monitorIdent)
+ {
+ if (insideArea.contains(monitorIdent)) {
+ if (singleShotTrigger.value(monitorIdent, -1) == areaExitedSignal().methodIndex()) {
+ //this is the finishing singleShot event
+ singleShotTrigger.remove(monitorIdent);
+ activeMonitorAreas.remove(monitorIdent);
+ setupNextExpiryTimeout();
+ } else {
+ insideArea.remove(monitorIdent);
+ }
+ return true;
+ }
+ return false;
+ }
+
+
+
+Q_SIGNALS:
+ void timeout(const QGeoAreaMonitorInfo &info);
+ void positionError(const QGeoPositionInfoSource::Error error);
+ void areaEventDetected(const QGeoAreaMonitorInfo &minfo,
+ const QGeoPositionInfo &pinfo, bool isEnteredEvent);
+private Q_SLOTS:
+ void timeout()
+ {
+ /*
+ * Don't block timer firing even if monitorExpiredSignal is not connected.
+ * This allows us to continue to remove the existing monitors as they expire.
+ **/
+ const QGeoAreaMonitorInfo info = activeMonitorAreas.take(activeExpiry.second);
+ setupNextExpiryTimeout();
+ emit timeout(info);
+
+ }
+
+ void positionUpdated(const QGeoPositionInfo &info)
+ {
+ foreach (const QGeoAreaMonitorInfo &monInfo, activeMonitors()) {
+ const QString identifier = monInfo.identifier();
+ if (monInfo.area().contains(info.coordinate())) {
+ if (processInsideArea(identifier))
+ emit areaEventDetected(monInfo, info, true);
+ } else {
+ if (processOutsideArea(identifier))
+ emit areaEventDetected(monInfo, info, false);
+ }
+ }
+ }
+
+private:
+ QPair<QDateTime, QString> activeExpiry;
+ QHash<QString, int> singleShotTrigger;
+ QTimer* nextExpiryTimer;
+ QSet<QString> insideArea;
+
+ MonitorTable activeMonitorAreas;
+
+ QGeoPositionInfoSource* source;
+ QList<QGeoAreaMonitorPolling*> registeredClients;
+ mutable QMutex mutex;
+};
+
+Q_GLOBAL_STATIC(QGeoAreaMonitorPollingPrivate, pollingPrivate)
+
+
+QGeoAreaMonitorPolling::QGeoAreaMonitorPolling(QObject *parent)
+ : QGeoAreaMonitorSource(parent), signalsAreConnected(false)
+{
+ d = pollingPrivate();
+ lastError = QGeoAreaMonitorSource::UnknownSourceError;
+ d->registerClient(this);
+ //hookup to default source if existing
+ if (!positionInfoSource())
+ setPositionInfoSource(QGeoPositionInfoSource::createDefaultSource(this));
}
-void QGeoAreaMonitorPolling::setRadius(qreal radius)
+QGeoAreaMonitorPolling::~QGeoAreaMonitorPolling()
{
- QGeoAreaMonitor::setRadius(radius);
- checkStartStop();
+ d->deregisterClient(this);
}
-static QMetaMethod areaEnteredSignal()
+QGeoPositionInfoSource* QGeoAreaMonitorPolling::positionInfoSource() const
{
- static QMetaMethod signal = QMetaMethod::fromSignal(&QGeoAreaMonitorPolling::areaEntered);
- return signal;
+ return d->positionSource();
}
-static QMetaMethod areaExitedSignal()
+void QGeoAreaMonitorPolling::setPositionInfoSource(QGeoPositionInfoSource *source)
{
- static QMetaMethod signal = QMetaMethod::fromSignal(&QGeoAreaMonitorPolling::areaExited);
- return signal;
+ d->setPositionSource(source);
}
-void QGeoAreaMonitorPolling::connectNotify(const QMetaMethod &signal)
+QGeoAreaMonitorSource::Error QGeoAreaMonitorPolling::error() const
{
- if (signal == areaEnteredSignal() ||
- signal == areaExitedSignal())
- checkStartStop();
+ return lastError;
}
-void QGeoAreaMonitorPolling::disconnectNotify(const QMetaMethod &signal)
+bool QGeoAreaMonitorPolling::startMonitoring(const QGeoAreaMonitorInfo &monitor)
{
- if (signal == areaEnteredSignal() ||
- signal == areaExitedSignal())
- checkStartStop();
+ if (!monitor.isValid())
+ return false;
+
+ //reject an expiry in the past
+ if (monitor.expiration().isValid() &&
+ (monitor.expiration() < QDateTime::currentDateTime()))
+ return false;
+
+ //don't accept persistent monitor since we don't support it
+ if (monitor.isPersistent())
+ return false;
+
+ //update or insert
+ d->startMonitoring(monitor);
+
+ return true;
+}
+
+int QGeoAreaMonitorPolling::idForSignal(const char *signal)
+{
+ const QByteArray sig = QMetaObject::normalizedSignature(signal + 1);
+ const QMetaObject * const mo = metaObject();
+
+ return mo->indexOfSignal(sig.constData());
+}
+
+bool QGeoAreaMonitorPolling::requestUpdate(const QGeoAreaMonitorInfo &monitor, const char *signal)
+{
+ if (!monitor.isValid())
+ return false;
+ //reject an expiry in the past
+ if (monitor.expiration().isValid() &&
+ (monitor.expiration() < QDateTime::currentDateTime()))
+ return false;
+
+ //don't accept persistent monitor since we don't support it
+ if (monitor.isPersistent())
+ return false;
+
+ if (!signal)
+ return false;
+
+ const int signalId = idForSignal(signal);
+ if (signalId < 0)
+ return false;
+
+ //only accept area entered or exit signal
+ if (signalId != areaEnteredSignal().methodIndex() &&
+ signalId != areaExitedSignal().methodIndex())
+ {
+ return false;
+ }
+
+ d->requestUpdate(monitor, signalId);
+
+ return true;
+}
+
+bool QGeoAreaMonitorPolling::stopMonitoring(const QGeoAreaMonitorInfo &monitor)
+{
+ QGeoAreaMonitorInfo info = d->stopMonitoring(monitor);
+
+ return info.isValid();
+}
+
+QList<QGeoAreaMonitorInfo> QGeoAreaMonitorPolling::activeMonitors() const
+{
+ return d->activeMonitors().values();
+}
+
+QList<QGeoAreaMonitorInfo> QGeoAreaMonitorPolling::activeMonitors(const QGeoShape &region) const
+{
+ QList<QGeoAreaMonitorInfo> results;
+ if (region.isEmpty())
+ return results;
+
+ const MonitorTable list = d->activeMonitors();
+ foreach (const QGeoAreaMonitorInfo &monitor, list) {
+ QGeoCoordinate center;
+ switch (monitor.area().type()) {
+ case QGeoShape::CircleType:
+ {
+ QGeoCircle circle(monitor.area());
+ center = circle.center();
+ break;
+ }
+ case QGeoShape::RectangleType:
+ {
+ QGeoRectangle rectangle(monitor.area());
+ center = rectangle.center();
+ break;
+ }
+ case QGeoShape::UnknownType:
+ {
+ break;
+ }
+ }
+ if (region.contains(center))
+ results.append(monitor);
+ }
+
+ return results;
}
-void QGeoAreaMonitorPolling::checkStartStop()
+QGeoAreaMonitorSource::AreaMonitorFeatures QGeoAreaMonitorPolling::supportedAreaMonitorFeatures() const
{
- if (!location) return;
+ return 0;
+}
- if ((isSignalConnected(areaEnteredSignal()) ||
- isSignalConnected(areaExitedSignal())) &&
- QGeoAreaMonitor::center().isValid() &&
- QGeoAreaMonitor::radius() > qreal(0.0)) {
- location->startUpdates();
- } else {
- location->stopUpdates();
+void QGeoAreaMonitorPolling::connectNotify(const QMetaMethod &/*signal*/)
+{
+ if (!signalsAreConnected &&
+ (isSignalConnected(areaEnteredSignal()) ||
+ isSignalConnected(areaExitedSignal())) )
+ {
+ signalsAreConnected = true;
+ d->checkStartStop();
}
}
-void QGeoAreaMonitorPolling::positionUpdated(const QGeoPositionInfo &info)
+void QGeoAreaMonitorPolling::disconnectNotify(const QMetaMethod &/*signal*/)
{
- double distance = info.coordinate().distanceTo(QGeoAreaMonitor::center());
+ if (!isSignalConnected(areaEnteredSignal()) &&
+ !isSignalConnected(areaExitedSignal()))
+ {
+ signalsAreConnected = false;
+ d->checkStartStop();
+ }
+}
- if (distance <= QGeoAreaMonitor::radius()) {
- if (!insideArea)
- emit areaEntered(info);
- insideArea = true;
- } else if (insideArea) {
- emit areaExited(info);
- insideArea = false;
+void QGeoAreaMonitorPolling::positionError(const QGeoPositionInfoSource::Error error)
+{
+ switch (error) {
+ case QGeoPositionInfoSource::AccessError:
+ lastError = QGeoAreaMonitorSource::AccessError;
+ break;
+ case QGeoPositionInfoSource::UnknownSourceError:
+ lastError = QGeoAreaMonitorSource::UnknownSourceError;
+ break;
+ case QGeoPositionInfoSource::ClosedError:
+ lastError = QGeoAreaMonitorSource::InsufficientPositionInfo;
+ break;
}
+
+ emit QGeoAreaMonitorSource::error(lastError);
+}
+
+void QGeoAreaMonitorPolling::timeout(const QGeoAreaMonitorInfo& monitor)
+{
+ if (isSignalConnected(monitorExpiredSignal()))
+ emit monitorExpired(monitor);
+}
+
+void QGeoAreaMonitorPolling::processAreaEvent(const QGeoAreaMonitorInfo &minfo,
+ const QGeoPositionInfo &pinfo, bool isEnteredEvent)
+{
+ if (isEnteredEvent)
+ emit areaEntered(minfo, pinfo);
+ else
+ emit areaExited(minfo, pinfo);
}
+#include "qgeoareamonitor_polling.moc"
#include "moc_qgeoareamonitor_polling.cpp"
diff --git a/src/plugins/position/positionpoll/qgeoareamonitor_polling.h b/src/plugins/position/positionpoll/qgeoareamonitor_polling.h
index a94e4e40..f25cd685 100644
--- a/src/plugins/position/positionpoll/qgeoareamonitor_polling.h
+++ b/src/plugins/position/positionpoll/qgeoareamonitor_polling.h
@@ -1,4 +1,4 @@
-/****************************************************************************
+/****************************************************************************
**
** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
** Contact: http://www.qt-project.org/legal
@@ -42,37 +42,55 @@
#ifndef QGEOAREAMONITORPOLLING_H
#define QGEOAREAMONITORPOLLING_H
-#include "qgeoareamonitor.h"
-#include "qgeopositioninfosource.h"
+#include <qgeoareamonitorsource.h>
+#include <qgeopositioninfosource.h>
/**
* QGeoAreaMonitorPolling
*
*/
-class QGeoAreaMonitorPolling : public QGeoAreaMonitor
+
+class QGeoAreaMonitorPollingPrivate;
+class QGeoAreaMonitorPolling : public QGeoAreaMonitorSource
{
Q_OBJECT
-
public :
explicit QGeoAreaMonitorPolling(QObject *parent = 0);
~QGeoAreaMonitorPolling();
- void setCenter(const QGeoCoordinate &coordinate);
- void setRadius(qreal radius);
- inline bool isValid() { return location; }
+ void setPositionInfoSource(QGeoPositionInfoSource *source) Q_DECL_OVERRIDE;
+ QGeoPositionInfoSource* positionInfoSource() const Q_DECL_OVERRIDE;
+
+ Error error() const Q_DECL_OVERRIDE;
+
+ bool startMonitoring(const QGeoAreaMonitorInfo &monitor) Q_DECL_OVERRIDE;
+ bool requestUpdate(const QGeoAreaMonitorInfo &monitor,
+ const char *signal) Q_DECL_OVERRIDE;
+ bool stopMonitoring(const QGeoAreaMonitorInfo &monitor) Q_DECL_OVERRIDE;
+
+ QList<QGeoAreaMonitorInfo> activeMonitors() const Q_DECL_OVERRIDE;
+ QList<QGeoAreaMonitorInfo> activeMonitors(const QGeoShape &region) const Q_DECL_OVERRIDE;
+
+ QGeoAreaMonitorSource::AreaMonitorFeatures supportedAreaMonitorFeatures() const Q_DECL_OVERRIDE;
+
+ inline bool isValid() { return positionInfoSource(); }
+
+ bool signalsAreConnected;
private Q_SLOTS:
- void positionUpdated(const QGeoPositionInfo &info);
+ void positionError(QGeoPositionInfoSource::Error error);
+ void timeout(const QGeoAreaMonitorInfo &monitor);
+ void processAreaEvent(const QGeoAreaMonitorInfo &minfo, const QGeoPositionInfo &pinfo, bool isEnteredEvent);
private:
- bool insideArea;
- QGeoPositionInfoSource *location;
+ QGeoAreaMonitorPollingPrivate* d;
+ QGeoAreaMonitorSource::Error lastError;
void connectNotify(const QMetaMethod &signal);
void disconnectNotify(const QMetaMethod &signal);
- void checkStartStop();
+ int idForSignal(const char *signal);
};
#endif // QGEOAREAMONITORPOLLING_H