1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtPositioning module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qgeoareamonitor_polling.h"
41#include <QtPositioning/qgeocoordinate.h>
42#include <QtPositioning/qgeorectangle.h>
43#include <QtPositioning/qgeocircle.h>
44
45#include <QtCore/qmetaobject.h>
46#include <QtCore/qtimer.h>
47#include <QtCore/qdebug.h>
48#include <QtCore/qmutex.h>
49
50#include <mutex>
51
52#define UPDATE_INTERVAL_5S 5000
53
54typedef QHash<QString, QGeoAreaMonitorInfo> MonitorTable;
55
56
57static QMetaMethod areaEnteredSignal()
58{
59 static QMetaMethod signal = QMetaMethod::fromSignal(signal: &QGeoAreaMonitorPolling::areaEntered);
60 return signal;
61}
62
63static QMetaMethod areaExitedSignal()
64{
65 static QMetaMethod signal = QMetaMethod::fromSignal(signal: &QGeoAreaMonitorPolling::areaExited);
66 return signal;
67}
68
69static QMetaMethod monitorExpiredSignal()
70{
71 static QMetaMethod signal = QMetaMethod::fromSignal(signal: &QGeoAreaMonitorPolling::monitorExpired);
72 return signal;
73}
74
75class QGeoAreaMonitorPollingPrivate : public QObject
76{
77 Q_OBJECT
78public:
79 QGeoAreaMonitorPollingPrivate()
80 {
81 nextExpiryTimer = new QTimer(this);
82 nextExpiryTimer->setSingleShot(true);
83 connect(sender: nextExpiryTimer, SIGNAL(timeout()),
84 receiver: this, SLOT(timeout()));
85 }
86
87 void startMonitoring(const QGeoAreaMonitorInfo &monitor)
88 {
89 const std::lock_guard<QRecursiveMutex> locker(mutex);
90
91 activeMonitorAreas.insert(akey: monitor.identifier(), avalue: monitor);
92 singleShotTrigger.remove(akey: monitor.identifier());
93
94 checkStartStop();
95 setupNextExpiryTimeout();
96 }
97
98 void requestUpdate(const QGeoAreaMonitorInfo &monitor, int signalId)
99 {
100 const std::lock_guard<QRecursiveMutex> locker(mutex);
101
102 activeMonitorAreas.insert(akey: monitor.identifier(), avalue: monitor);
103 singleShotTrigger.insert(akey: monitor.identifier(), avalue: signalId);
104
105 checkStartStop();
106 setupNextExpiryTimeout();
107 }
108
109 QGeoAreaMonitorInfo stopMonitoring(const QGeoAreaMonitorInfo &monitor)
110 {
111 const std::lock_guard<QRecursiveMutex> locker(mutex);
112
113 QGeoAreaMonitorInfo mon = activeMonitorAreas.take(akey: monitor.identifier());
114
115 checkStartStop();
116 setupNextExpiryTimeout();
117
118 return mon;
119 }
120
121 void registerClient(QGeoAreaMonitorPolling *client)
122 {
123 const std::lock_guard<QRecursiveMutex> locker(mutex);
124
125 connect(sender: this, SIGNAL(timeout(QGeoAreaMonitorInfo)),
126 receiver: client, SLOT(timeout(QGeoAreaMonitorInfo)));
127
128 connect(sender: this, SIGNAL(positionError(QGeoPositionInfoSource::Error)),
129 receiver: client, SLOT(positionError(QGeoPositionInfoSource::Error)));
130
131 connect(sender: this, SIGNAL(areaEventDetected(QGeoAreaMonitorInfo,QGeoPositionInfo,bool)),
132 receiver: client, SLOT(processAreaEvent(QGeoAreaMonitorInfo,QGeoPositionInfo,bool)));
133
134 registeredClients.append(t: client);
135 }
136
137 void deregisterClient(QGeoAreaMonitorPolling *client)
138 {
139 const std::lock_guard<QRecursiveMutex> locker(mutex);
140
141 registeredClients.removeAll(t: client);
142 if (registeredClients.isEmpty())
143 checkStartStop();
144 }
145
146 void setPositionSource(QGeoPositionInfoSource *newSource)
147 {
148 const std::lock_guard<QRecursiveMutex> locker(mutex);
149
150 if (newSource == source)
151 return;
152
153 if (source)
154 delete source;
155
156 source = newSource;
157
158 if (source) {
159 source->setParent(this);
160 source->moveToThread(thread: this->thread());
161 if (source->updateInterval() == 0)
162 source->setUpdateInterval(UPDATE_INTERVAL_5S);
163 disconnect(sender: source, signal: 0, receiver: 0, member: 0); //disconnect all
164 connect(sender: source, SIGNAL(positionUpdated(QGeoPositionInfo)),
165 receiver: this, SLOT(positionUpdated(QGeoPositionInfo)));
166 connect(sender: source, SIGNAL(error(QGeoPositionInfoSource::Error)),
167 receiver: this, SIGNAL(positionError(QGeoPositionInfoSource::Error)));
168 checkStartStop();
169 }
170 }
171
172 QGeoPositionInfoSource* positionSource() const
173 {
174 const std::lock_guard<QRecursiveMutex> locker(mutex);
175 return source;
176 }
177
178 MonitorTable activeMonitors() const
179 {
180 const std::lock_guard<QRecursiveMutex> locker(mutex);
181
182 return activeMonitorAreas;
183 }
184
185 void checkStartStop()
186 {
187 const std::lock_guard<QRecursiveMutex> locker(mutex);
188
189 bool signalsConnected = false;
190 foreach (const QGeoAreaMonitorPolling *client, registeredClients) {
191 if (client->signalsAreConnected) {
192 signalsConnected = true;
193 break;
194 }
195 }
196
197 if (signalsConnected && !activeMonitorAreas.isEmpty()) {
198 if (source)
199 source->startUpdates();
200 else
201 //translated to InsufficientPositionInfo
202 emit positionError(error: QGeoPositionInfoSource::ClosedError);
203 } else {
204 if (source)
205 source->stopUpdates();
206 }
207 }
208
209private:
210 void setupNextExpiryTimeout()
211 {
212 nextExpiryTimer->stop();
213 activeExpiry.first = QDateTime();
214 activeExpiry.second = QString();
215
216 foreach (const QGeoAreaMonitorInfo &info, activeMonitors()) {
217 if (info.expiration().isValid()) {
218 if (!activeExpiry.first.isValid()) {
219 activeExpiry.first = info.expiration();
220 activeExpiry.second = info.identifier();
221 continue;
222 }
223 if (info.expiration() < activeExpiry.first) {
224 activeExpiry.first = info.expiration();
225 activeExpiry.second = info.identifier();
226 }
227 }
228 }
229
230 if (activeExpiry.first.isValid())
231 nextExpiryTimer->start(msec: QDateTime::currentDateTime().msecsTo(activeExpiry.first));
232 }
233
234
235 //returns true if areaEntered should be emitted
236 bool processInsideArea(const QString &monitorIdent)
237 {
238 if (!insideArea.contains(value: monitorIdent)) {
239 if (singleShotTrigger.value(akey: monitorIdent, adefaultValue: -1) == areaEnteredSignal().methodIndex()) {
240 //this is the finishing singleshot event
241 singleShotTrigger.remove(akey: monitorIdent);
242 activeMonitorAreas.remove(akey: monitorIdent);
243 setupNextExpiryTimeout();
244 } else {
245 insideArea.insert(value: monitorIdent);
246 }
247 return true;
248 }
249
250 return false;
251 }
252
253 //returns true if areaExited should be emitted
254 bool processOutsideArea(const QString &monitorIdent)
255 {
256 if (insideArea.contains(value: monitorIdent)) {
257 if (singleShotTrigger.value(akey: monitorIdent, adefaultValue: -1) == areaExitedSignal().methodIndex()) {
258 //this is the finishing singleShot event
259 singleShotTrigger.remove(akey: monitorIdent);
260 activeMonitorAreas.remove(akey: monitorIdent);
261 setupNextExpiryTimeout();
262 } else {
263 insideArea.remove(value: monitorIdent);
264 }
265 return true;
266 }
267 return false;
268 }
269
270
271
272Q_SIGNALS:
273 void timeout(const QGeoAreaMonitorInfo &info);
274 void positionError(const QGeoPositionInfoSource::Error error);
275 void areaEventDetected(const QGeoAreaMonitorInfo &minfo,
276 const QGeoPositionInfo &pinfo, bool isEnteredEvent);
277private Q_SLOTS:
278 void timeout()
279 {
280 /*
281 * Don't block timer firing even if monitorExpiredSignal is not connected.
282 * This allows us to continue to remove the existing monitors as they expire.
283 **/
284 const QGeoAreaMonitorInfo info = activeMonitorAreas.take(akey: activeExpiry.second);
285 setupNextExpiryTimeout();
286 emit timeout(info);
287
288 }
289
290 void positionUpdated(const QGeoPositionInfo &info)
291 {
292 foreach (const QGeoAreaMonitorInfo &monInfo, activeMonitors()) {
293 const QString identifier = monInfo.identifier();
294 if (monInfo.area().contains(coordinate: info.coordinate())) {
295 if (processInsideArea(monitorIdent: identifier))
296 emit areaEventDetected(minfo: monInfo, pinfo: info, isEnteredEvent: true);
297 } else {
298 if (processOutsideArea(monitorIdent: identifier))
299 emit areaEventDetected(minfo: monInfo, pinfo: info, isEnteredEvent: false);
300 }
301 }
302 }
303
304private:
305 QPair<QDateTime, QString> activeExpiry;
306 QHash<QString, int> singleShotTrigger;
307 QTimer* nextExpiryTimer;
308 QSet<QString> insideArea;
309
310 MonitorTable activeMonitorAreas;
311
312 QGeoPositionInfoSource* source = nullptr;
313 QList<QGeoAreaMonitorPolling*> registeredClients;
314 mutable QRecursiveMutex mutex;
315};
316
317Q_GLOBAL_STATIC(QGeoAreaMonitorPollingPrivate, pollingPrivate)
318
319
320QGeoAreaMonitorPolling::QGeoAreaMonitorPolling(QObject *parent)
321 : QGeoAreaMonitorSource(parent), signalsAreConnected(false)
322{
323 d = pollingPrivate();
324 lastError = QGeoAreaMonitorSource::NoError;
325 d->registerClient(client: this);
326 //hookup to default source if existing
327 if (!positionInfoSource())
328 setPositionInfoSource(QGeoPositionInfoSource::createDefaultSource(parent: this));
329}
330
331QGeoAreaMonitorPolling::~QGeoAreaMonitorPolling()
332{
333 d->deregisterClient(client: this);
334}
335
336QGeoPositionInfoSource* QGeoAreaMonitorPolling::positionInfoSource() const
337{
338 return d->positionSource();
339}
340
341void QGeoAreaMonitorPolling::setPositionInfoSource(QGeoPositionInfoSource *source)
342{
343 d->setPositionSource(source);
344}
345
346QGeoAreaMonitorSource::Error QGeoAreaMonitorPolling::error() const
347{
348 return lastError;
349}
350
351bool QGeoAreaMonitorPolling::startMonitoring(const QGeoAreaMonitorInfo &monitor)
352{
353 if (!monitor.isValid())
354 return false;
355
356 //reject an expiry in the past
357 if (monitor.expiration().isValid() &&
358 (monitor.expiration() < QDateTime::currentDateTime()))
359 return false;
360
361 //don't accept persistent monitor since we don't support it
362 if (monitor.isPersistent())
363 return false;
364
365 //update or insert
366 d->startMonitoring(monitor);
367
368 return true;
369}
370
371int QGeoAreaMonitorPolling::idForSignal(const char *signal)
372{
373 const QByteArray sig = QMetaObject::normalizedSignature(method: signal + 1);
374 const QMetaObject * const mo = metaObject();
375
376 return mo->indexOfSignal(signal: sig.constData());
377}
378
379bool QGeoAreaMonitorPolling::requestUpdate(const QGeoAreaMonitorInfo &monitor, const char *signal)
380{
381 if (!monitor.isValid())
382 return false;
383 //reject an expiry in the past
384 if (monitor.expiration().isValid() &&
385 (monitor.expiration() < QDateTime::currentDateTime()))
386 return false;
387
388 //don't accept persistent monitor since we don't support it
389 if (monitor.isPersistent())
390 return false;
391
392 if (!signal)
393 return false;
394
395 const int signalId = idForSignal(signal);
396 if (signalId < 0)
397 return false;
398
399 //only accept area entered or exit signal
400 if (signalId != areaEnteredSignal().methodIndex() &&
401 signalId != areaExitedSignal().methodIndex())
402 {
403 return false;
404 }
405
406 d->requestUpdate(monitor, signalId);
407
408 return true;
409}
410
411bool QGeoAreaMonitorPolling::stopMonitoring(const QGeoAreaMonitorInfo &monitor)
412{
413 QGeoAreaMonitorInfo info = d->stopMonitoring(monitor);
414
415 return info.isValid();
416}
417
418QList<QGeoAreaMonitorInfo> QGeoAreaMonitorPolling::activeMonitors() const
419{
420 return d->activeMonitors().values();
421}
422
423QList<QGeoAreaMonitorInfo> QGeoAreaMonitorPolling::activeMonitors(const QGeoShape &region) const
424{
425 QList<QGeoAreaMonitorInfo> results;
426 if (region.isEmpty())
427 return results;
428
429 const MonitorTable list = d->activeMonitors();
430 foreach (const QGeoAreaMonitorInfo &monitor, list) {
431 if (region.contains(coordinate: monitor.area().center()))
432 results.append(t: monitor);
433 }
434
435 return results;
436}
437
438QGeoAreaMonitorSource::AreaMonitorFeatures QGeoAreaMonitorPolling::supportedAreaMonitorFeatures() const
439{
440 return {};
441}
442
443void QGeoAreaMonitorPolling::connectNotify(const QMetaMethod &/*signal*/)
444{
445 if (!signalsAreConnected &&
446 (isSignalConnected(signal: areaEnteredSignal()) ||
447 isSignalConnected(signal: areaExitedSignal())) )
448 {
449 signalsAreConnected = true;
450 d->checkStartStop();
451 }
452}
453
454void QGeoAreaMonitorPolling::disconnectNotify(const QMetaMethod &/*signal*/)
455{
456 if (!isSignalConnected(signal: areaEnteredSignal()) &&
457 !isSignalConnected(signal: areaExitedSignal()))
458 {
459 signalsAreConnected = false;
460 d->checkStartStop();
461 }
462}
463
464void QGeoAreaMonitorPolling::positionError(const QGeoPositionInfoSource::Error error)
465{
466 switch (error) {
467 case QGeoPositionInfoSource::AccessError:
468 lastError = QGeoAreaMonitorSource::AccessError;
469 break;
470 case QGeoPositionInfoSource::UnknownSourceError:
471 lastError = QGeoAreaMonitorSource::UnknownSourceError;
472 break;
473 case QGeoPositionInfoSource::ClosedError:
474 lastError = QGeoAreaMonitorSource::InsufficientPositionInfo;
475 break;
476 case QGeoPositionInfoSource::NoError:
477 return;
478 }
479
480 emit QGeoAreaMonitorSource::error(error: lastError);
481}
482
483void QGeoAreaMonitorPolling::timeout(const QGeoAreaMonitorInfo& monitor)
484{
485 if (isSignalConnected(signal: monitorExpiredSignal()))
486 emit monitorExpired(monitor);
487}
488
489void QGeoAreaMonitorPolling::processAreaEvent(const QGeoAreaMonitorInfo &minfo,
490 const QGeoPositionInfo &pinfo, bool isEnteredEvent)
491{
492 if (isEnteredEvent)
493 emit areaEntered(monitor: minfo, update: pinfo);
494 else
495 emit areaExited(monitor: minfo, update: pinfo);
496}
497
498#include "qgeoareamonitor_polling.moc"
499#include "moc_qgeoareamonitor_polling.cpp"
500

source code of qtlocation/src/plugins/position/positionpoll/qgeoareamonitor_polling.cpp