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
54 typedef QHash<QString, QGeoAreaMonitorInfo> MonitorTable;
55
56
areaEnteredSignal()57 static QMetaMethod areaEnteredSignal()
58 {
59 static QMetaMethod signal = QMetaMethod::fromSignal(&QGeoAreaMonitorPolling::areaEntered);
60 return signal;
61 }
62
areaExitedSignal()63 static QMetaMethod areaExitedSignal()
64 {
65 static QMetaMethod signal = QMetaMethod::fromSignal(&QGeoAreaMonitorPolling::areaExited);
66 return signal;
67 }
68
monitorExpiredSignal()69 static QMetaMethod monitorExpiredSignal()
70 {
71 static QMetaMethod signal = QMetaMethod::fromSignal(&QGeoAreaMonitorPolling::monitorExpired);
72 return signal;
73 }
74
75 class QGeoAreaMonitorPollingPrivate : public QObject
76 {
77 Q_OBJECT
78 public:
QGeoAreaMonitorPollingPrivate()79 QGeoAreaMonitorPollingPrivate()
80 {
81 nextExpiryTimer = new QTimer(this);
82 nextExpiryTimer->setSingleShot(true);
83 connect(nextExpiryTimer, SIGNAL(timeout()),
84 this, SLOT(timeout()));
85 }
86
startMonitoring(const QGeoAreaMonitorInfo & monitor)87 void startMonitoring(const QGeoAreaMonitorInfo &monitor)
88 {
89 const std::lock_guard<QRecursiveMutex> locker(mutex);
90
91 activeMonitorAreas.insert(monitor.identifier(), monitor);
92 singleShotTrigger.remove(monitor.identifier());
93
94 checkStartStop();
95 setupNextExpiryTimeout();
96 }
97
requestUpdate(const QGeoAreaMonitorInfo & monitor,int signalId)98 void requestUpdate(const QGeoAreaMonitorInfo &monitor, int signalId)
99 {
100 const std::lock_guard<QRecursiveMutex> locker(mutex);
101
102 activeMonitorAreas.insert(monitor.identifier(), monitor);
103 singleShotTrigger.insert(monitor.identifier(), signalId);
104
105 checkStartStop();
106 setupNextExpiryTimeout();
107 }
108
stopMonitoring(const QGeoAreaMonitorInfo & monitor)109 QGeoAreaMonitorInfo stopMonitoring(const QGeoAreaMonitorInfo &monitor)
110 {
111 const std::lock_guard<QRecursiveMutex> locker(mutex);
112
113 QGeoAreaMonitorInfo mon = activeMonitorAreas.take(monitor.identifier());
114
115 checkStartStop();
116 setupNextExpiryTimeout();
117
118 return mon;
119 }
120
registerClient(QGeoAreaMonitorPolling * client)121 void registerClient(QGeoAreaMonitorPolling *client)
122 {
123 const std::lock_guard<QRecursiveMutex> locker(mutex);
124
125 connect(this, SIGNAL(timeout(QGeoAreaMonitorInfo)),
126 client, SLOT(timeout(QGeoAreaMonitorInfo)));
127
128 connect(this, SIGNAL(positionError(QGeoPositionInfoSource::Error)),
129 client, SLOT(positionError(QGeoPositionInfoSource::Error)));
130
131 connect(this, SIGNAL(areaEventDetected(QGeoAreaMonitorInfo,QGeoPositionInfo,bool)),
132 client, SLOT(processAreaEvent(QGeoAreaMonitorInfo,QGeoPositionInfo,bool)));
133
134 registeredClients.append(client);
135 }
136
deregisterClient(QGeoAreaMonitorPolling * client)137 void deregisterClient(QGeoAreaMonitorPolling *client)
138 {
139 const std::lock_guard<QRecursiveMutex> locker(mutex);
140
141 registeredClients.removeAll(client);
142 if (registeredClients.isEmpty())
143 checkStartStop();
144 }
145
setPositionSource(QGeoPositionInfoSource * newSource)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(this->thread());
161 if (source->updateInterval() == 0)
162 source->setUpdateInterval(UPDATE_INTERVAL_5S);
163 disconnect(source, 0, 0, 0); //disconnect all
164 connect(source, SIGNAL(positionUpdated(QGeoPositionInfo)),
165 this, SLOT(positionUpdated(QGeoPositionInfo)));
166 connect(source, SIGNAL(error(QGeoPositionInfoSource::Error)),
167 this, SIGNAL(positionError(QGeoPositionInfoSource::Error)));
168 checkStartStop();
169 }
170 }
171
positionSource() const172 QGeoPositionInfoSource* positionSource() const
173 {
174 const std::lock_guard<QRecursiveMutex> locker(mutex);
175 return source;
176 }
177
activeMonitors() const178 MonitorTable activeMonitors() const
179 {
180 const std::lock_guard<QRecursiveMutex> locker(mutex);
181
182 return activeMonitorAreas;
183 }
184
checkStartStop()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(QGeoPositionInfoSource::ClosedError);
203 } else {
204 if (source)
205 source->stopUpdates();
206 }
207 }
208
209 private:
setupNextExpiryTimeout()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(QDateTime::currentDateTime().msecsTo(activeExpiry.first));
232 }
233
234
235 //returns true if areaEntered should be emitted
processInsideArea(const QString & monitorIdent)236 bool processInsideArea(const QString &monitorIdent)
237 {
238 if (!insideArea.contains(monitorIdent)) {
239 if (singleShotTrigger.value(monitorIdent, -1) == areaEnteredSignal().methodIndex()) {
240 //this is the finishing singleshot event
241 singleShotTrigger.remove(monitorIdent);
242 activeMonitorAreas.remove(monitorIdent);
243 setupNextExpiryTimeout();
244 } else {
245 insideArea.insert(monitorIdent);
246 }
247 return true;
248 }
249
250 return false;
251 }
252
253 //returns true if areaExited should be emitted
processOutsideArea(const QString & monitorIdent)254 bool processOutsideArea(const QString &monitorIdent)
255 {
256 if (insideArea.contains(monitorIdent)) {
257 if (singleShotTrigger.value(monitorIdent, -1) == areaExitedSignal().methodIndex()) {
258 //this is the finishing singleShot event
259 singleShotTrigger.remove(monitorIdent);
260 activeMonitorAreas.remove(monitorIdent);
261 setupNextExpiryTimeout();
262 } else {
263 insideArea.remove(monitorIdent);
264 }
265 return true;
266 }
267 return false;
268 }
269
270
271
272 Q_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);
277 private Q_SLOTS:
timeout()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(activeExpiry.second);
285 setupNextExpiryTimeout();
286 emit timeout(info);
287
288 }
289
positionUpdated(const QGeoPositionInfo & info)290 void positionUpdated(const QGeoPositionInfo &info)
291 {
292 foreach (const QGeoAreaMonitorInfo &monInfo, activeMonitors()) {
293 const QString identifier = monInfo.identifier();
294 if (monInfo.area().contains(info.coordinate())) {
295 if (processInsideArea(identifier))
296 emit areaEventDetected(monInfo, info, true);
297 } else {
298 if (processOutsideArea(identifier))
299 emit areaEventDetected(monInfo, info, false);
300 }
301 }
302 }
303
304 private:
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
Q_GLOBAL_STATIC(QGeoAreaMonitorPollingPrivate,pollingPrivate)317 Q_GLOBAL_STATIC(QGeoAreaMonitorPollingPrivate, pollingPrivate)
318
319
320 QGeoAreaMonitorPolling::QGeoAreaMonitorPolling(QObject *parent)
321 : QGeoAreaMonitorSource(parent), signalsAreConnected(false)
322 {
323 d = pollingPrivate();
324 lastError = QGeoAreaMonitorSource::NoError;
325 d->registerClient(this);
326 //hookup to default source if existing
327 if (!positionInfoSource())
328 setPositionInfoSource(QGeoPositionInfoSource::createDefaultSource(this));
329 }
330
~QGeoAreaMonitorPolling()331 QGeoAreaMonitorPolling::~QGeoAreaMonitorPolling()
332 {
333 d->deregisterClient(this);
334 }
335
positionInfoSource() const336 QGeoPositionInfoSource* QGeoAreaMonitorPolling::positionInfoSource() const
337 {
338 return d->positionSource();
339 }
340
setPositionInfoSource(QGeoPositionInfoSource * source)341 void QGeoAreaMonitorPolling::setPositionInfoSource(QGeoPositionInfoSource *source)
342 {
343 d->setPositionSource(source);
344 }
345
error() const346 QGeoAreaMonitorSource::Error QGeoAreaMonitorPolling::error() const
347 {
348 return lastError;
349 }
350
startMonitoring(const QGeoAreaMonitorInfo & monitor)351 bool 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
idForSignal(const char * signal)371 int QGeoAreaMonitorPolling::idForSignal(const char *signal)
372 {
373 const QByteArray sig = QMetaObject::normalizedSignature(signal + 1);
374 const QMetaObject * const mo = metaObject();
375
376 return mo->indexOfSignal(sig.constData());
377 }
378
requestUpdate(const QGeoAreaMonitorInfo & monitor,const char * signal)379 bool 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
stopMonitoring(const QGeoAreaMonitorInfo & monitor)411 bool QGeoAreaMonitorPolling::stopMonitoring(const QGeoAreaMonitorInfo &monitor)
412 {
413 QGeoAreaMonitorInfo info = d->stopMonitoring(monitor);
414
415 return info.isValid();
416 }
417
activeMonitors() const418 QList<QGeoAreaMonitorInfo> QGeoAreaMonitorPolling::activeMonitors() const
419 {
420 return d->activeMonitors().values();
421 }
422
activeMonitors(const QGeoShape & region) const423 QList<QGeoAreaMonitorInfo> QGeoAreaMonitorPolling::activeMonitors(const QGeoShape ®ion) 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(monitor.area().center()))
432 results.append(monitor);
433 }
434
435 return results;
436 }
437
supportedAreaMonitorFeatures() const438 QGeoAreaMonitorSource::AreaMonitorFeatures QGeoAreaMonitorPolling::supportedAreaMonitorFeatures() const
439 {
440 return {};
441 }
442
connectNotify(const QMetaMethod &)443 void QGeoAreaMonitorPolling::connectNotify(const QMetaMethod &/*signal*/)
444 {
445 if (!signalsAreConnected &&
446 (isSignalConnected(areaEnteredSignal()) ||
447 isSignalConnected(areaExitedSignal())) )
448 {
449 signalsAreConnected = true;
450 d->checkStartStop();
451 }
452 }
453
disconnectNotify(const QMetaMethod &)454 void QGeoAreaMonitorPolling::disconnectNotify(const QMetaMethod &/*signal*/)
455 {
456 if (!isSignalConnected(areaEnteredSignal()) &&
457 !isSignalConnected(areaExitedSignal()))
458 {
459 signalsAreConnected = false;
460 d->checkStartStop();
461 }
462 }
463
positionError(const QGeoPositionInfoSource::Error error)464 void 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(lastError);
481 }
482
timeout(const QGeoAreaMonitorInfo & monitor)483 void QGeoAreaMonitorPolling::timeout(const QGeoAreaMonitorInfo& monitor)
484 {
485 if (isSignalConnected(monitorExpiredSignal()))
486 emit monitorExpired(monitor);
487 }
488
processAreaEvent(const QGeoAreaMonitorInfo & minfo,const QGeoPositionInfo & pinfo,bool isEnteredEvent)489 void QGeoAreaMonitorPolling::processAreaEvent(const QGeoAreaMonitorInfo &minfo,
490 const QGeoPositionInfo &pinfo, bool isEnteredEvent)
491 {
492 if (isEnteredEvent)
493 emit areaEntered(minfo, pinfo);
494 else
495 emit areaExited(minfo, pinfo);
496 }
497
498 #include "qgeoareamonitor_polling.moc"
499 #include "moc_qgeoareamonitor_polling.cpp"
500