038d1b63384e542131a3811400dc375f26f56385
[platform/core/context/context-provider.git] / src / my-place / visit-detector / visit_detector.cpp
1 /*
2  * Copyright (c) 2016 Samsung Electronics Co., Ltd.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include <set>
18 #include <iostream>
19 #include <iomanip>
20 #include <sstream>
21 #include <Types.h>
22 #include <Json.h>
23 #include "../facade/user_places_types.h"
24 #include "visit_detector.h"
25 #include "../facade/user_places_params.h"
26 #include "../visit-categer/visit_categer.h"
27 #include "../utils/similarity.h"
28 #include "../utils/median.h"
29 #include "../utils/debug_utils.h"
30
31 #ifdef TIZEN_ENGINEER_MODE
32 #define __VISIT_TABLE_COLUMNS \
33         VISIT_COLUMN_WIFI_APS " TEXT, "\
34         VISIT_COLUMN_START_TIME " timestamp, "\
35         VISIT_COLUMN_END_TIME " timestamp, "\
36         VISIT_COLUMN_START_TIME_HUMAN " TEXT, "\
37         VISIT_COLUMN_END_TIME_HUMAN " TEXT, "\
38         VISIT_COLUMN_LOCATION_VALID " INTEGER, "\
39         VISIT_COLUMN_LOCATION_LATITUDE " REAL, "\
40         VISIT_COLUMN_LOCATION_LONGITUDE " REAL, "\
41         VISIT_COLUMN_LOCATION_ACCURACY " REAL, "\
42         VISIT_COLUMN_CATEG_HOME " REAL, "\
43         VISIT_COLUMN_CATEG_WORK " REAL, "\
44         VISIT_COLUMN_CATEG_OTHER " REAL"
45 #else /* TIZEN_ENGINEER_MODE */
46 #define __VISIT_TABLE_COLUMNS \
47         VISIT_COLUMN_WIFI_APS " TEXT, "\
48         VISIT_COLUMN_START_TIME " timestamp, "\
49         VISIT_COLUMN_END_TIME " timestamp, "\
50         VISIT_COLUMN_LOCATION_VALID " INTEGER, "\
51         VISIT_COLUMN_LOCATION_LATITUDE " REAL, "\
52         VISIT_COLUMN_LOCATION_LONGITUDE " REAL, "\
53         VISIT_COLUMN_LOCATION_ACCURACY " REAL, "\
54         VISIT_COLUMN_CATEG_HOME " REAL, "\
55         VISIT_COLUMN_CATEG_WORK " REAL, "\
56         VISIT_COLUMN_CATEG_OTHER " REAL"
57 #endif /* TIZEN_ENGINEER_MODE */
58
59 #define __WIFI_APS_MAP_TABLE_COLUMNS \
60         WIFI_APS_MAP_COLUMN_MAC " TEXT NOT NULL UNIQUE, "\
61         WIFI_APS_MAP_COLUMN_NETWORK_NAME " TEXT NOT NULL, "\
62         WIFI_APS_MAP_COLUMN_INSERT_TIME " timestamp"
63
64 ctx::VisitDetector::VisitDetector(time_t startScan, PlaceRecogMode energyMode, bool testMode) :
65         __testMode(testMode),
66         __locationLogger(testMode ? nullptr : new LocationLogger(this)),
67         __wifiLogger(testMode ? nullptr : new WifiLogger(this, energyMode)),
68         __currentInterval(startScan, startScan + VISIT_DETECTOR_PERIOD_SECONDS_HIGH_ACCURACY),
69         __stableCounter(0),
70         __tolerance(VISIT_DETECTOR_TOLERANCE_DEPTH),
71         __entranceToPlace(false),
72         __periodSeconds(VISIT_DETECTOR_PERIOD_SECONDS_HIGH_ACCURACY),
73         __dbManager(testMode ? nullptr : new DatabaseManager()),
74         __entranceTime(0),
75         __departureTime(0)
76 {
77         __setPeriod(energyMode);
78         __currentInterval = Interval(startScan, startScan + __periodSeconds);
79         __currentMacEvents = std::make_shared<MacEvents>();
80         __stayMacs = std::make_shared<MacSet>();
81
82         if (__testMode) {
83                 __detectedVisits = std::make_shared<ctx::Visits>();
84                 return;
85         }
86
87         __listeners.push_back(__locationLogger);
88         __listeners.push_back(__wifiLogger);
89
90         __dbCreateTables();
91         __wifiLogger->startLogging();
92 }
93
94 ctx::VisitDetector::~VisitDetector()
95 {
96 }
97
98 bool ctx::VisitDetector::__isValid(const ctx::Mac &mac)
99 {
100         return mac != "00:00:00:00:00:00";
101 }
102
103 void ctx::VisitDetector::onWifiScan(ctx::MacEvent e)
104 {
105         _D("timestamp=%d, current_interval.end=%d, mac=%s, network=%s",
106                         e.timestamp,
107                         __currentInterval.end,
108                         std::string(e.mac).c_str(),
109                         e.networkName.c_str());
110         if (__isValid(e.mac)) {
111                 while (e.timestamp > __currentInterval.end) {
112                         __processCurrentLogger();
113                         __shiftCurrentInterval();
114                 }
115                 __currentMacEvents->push_back(e);
116         }
117 }
118
119 void ctx::VisitDetector::__processCurrentLogger()
120 {
121         _D("");
122         std::shared_ptr<ctx::Frame> frame = __makeFrame(__currentMacEvents, __currentInterval);
123         __detectEntranceOrDeparture(frame);
124         __currentMacEvents->clear();
125 }
126
127 std::shared_ptr<ctx::Frame> ctx::VisitDetector::__makeFrame(std::shared_ptr<ctx::MacEvents> logger, ctx::Interval interval)
128 {
129         std::set<time_t> timestamps;
130         std::shared_ptr<Frame> frame = std::make_shared<Frame>(interval);
131         for (auto log : *logger) {
132                 timestamps.insert(log.timestamp);
133                 if (frame->macs2Counts.find(log.mac) == frame->macs2Counts.end()) {
134                         frame->macs2Counts[log.mac] = 1;
135                 } else {
136                         frame->macs2Counts[log.mac] += 1;
137                 }
138         }
139         frame->numberOfTimestamps = timestamps.size();
140         return frame;
141 }
142
143 void ctx::VisitDetector::__shiftCurrentInterval()
144 {
145         __currentInterval.end += __periodSeconds;
146         __currentInterval.start += __periodSeconds;
147 }
148
149 void ctx::VisitDetector::__detectEntranceOrDeparture(std::shared_ptr<ctx::Frame> frame)
150 {
151         __entranceToPlace ? __detectDeparture(frame) : __detectEntrance(frame);
152         if (__entranceToPlace) {
153                 for (MacEvent e : *__currentMacEvents) {
154                         __wifiAPsMap.insert(std::pair<std::string, std::string>(e.mac, e.networkName));
155                 }
156         }
157 }
158
159 bool ctx::VisitDetector::__isDisjoint(const ctx::Macs2Counts &macs2Counts, const ctx::MacSet &macSet)
160 {
161         for (auto &mac : macSet) {
162                 if (macs2Counts.find(mac) != macs2Counts.end())
163                         return false;
164         }
165         return true;
166 }
167
168 bool ctx::VisitDetector::__protrudesFrom(const ctx::Macs2Counts &macs2Counts, const ctx::MacSet &macSet)
169 {
170         for (auto &macCount : macs2Counts) {
171                 if (macSet.find(macCount.first) == macSet.end())
172                         return true;
173         }
174         return false;
175 }
176
177 void ctx::VisitDetector::__detectDeparture(std::shared_ptr<ctx::Frame> frame)
178 {
179         if (__tolerance == VISIT_DETECTOR_TOLERANCE_DEPTH) {
180                 __departureTime = frame->interval.start;
181                 __bufferedFrames.clear();
182         } else { // __tolerance < VISIT_DETECTOR_TOLERANCE_DEPTH
183                 __bufferedFrames.push_back(frame);
184         }
185         if (__isDisjoint(frame->macs2Counts, *__representativesMacs)) {
186                 if (frame->macs2Counts.empty() || __protrudesFrom(frame->macs2Counts, *__stayMacs)) {
187                         __tolerance--;
188                 } else { // no new macs
189                         __bufferedFrames.clear();
190                 }
191                 if (__tolerance == 0) { // departure detected
192                         __visitEndDetected();
193                         __processBuffer(frame);
194                 }
195         } else if (__tolerance < VISIT_DETECTOR_TOLERANCE_DEPTH) {
196                 __tolerance++;
197         }
198 }
199
200 void ctx::VisitDetector::__visitStartDetected()
201 {
202         __entranceToPlace = true;
203
204         __locationEvents.clear();
205         if (!__testMode) {
206                 for (IVisitListener* listener : __listeners) {
207                         listener->onVisitStart();
208                 }
209         }
210         __representativesMacs = __selectRepresentatives(__historyFrames);
211         __entranceTime = __historyFrames[0]->interval.start;
212         _I("Entrance detected, timestamp: %d", __entranceTime);
213         __resetHistory();
214 }
215
216 void ctx::VisitDetector::__visitEndDetected()
217 {
218         if (!__testMode) {
219                 for (IVisitListener* listener : __listeners) {
220                         listener->onVisitEnd();
221                 }
222         }
223         _I("Departure detected, timestamp: %d", __departureTime);
224
225         Interval interval(__entranceTime, __departureTime);
226         Visit visit(interval, __representativesMacs);
227         VisitCateger::categorize(visit);
228
229         __putLocationToVisit(visit);
230
231         if (__testMode) {
232                 __detectedVisits->push_back(visit);
233         } else {
234                 __dbInsertVisit(visit);
235                 __dbInsertWifiAPsMap(visit);
236         }
237
238         // cleaning
239         __entranceToPlace = false;
240         __representativesMacs.reset();
241         __tolerance = VISIT_DETECTOR_TOLERANCE_DEPTH;
242 }
243
244 void ctx::VisitDetector::__putLocationToVisit(ctx::Visit &visit)
245 {
246         // TODO: filter out small accuracy locations?
247         std::vector<double> latitudes;
248         std::vector<double> longitudes;
249         std::vector<double> accuracy;
250         visit.locationValid = false;
251         for (LocationEvent &location : __locationEvents) {
252                 if (location.timestamp >= __entranceTime && location.timestamp <= __departureTime) {
253                         latitudes.push_back(location.coordinates.latitude);
254                         longitudes.push_back(location.coordinates.longitude);
255                         accuracy.push_back(location.coordinates.accuracy);
256                         visit.locationValid = true;
257                 }
258         }
259         if (visit.locationValid) {
260                 visit.location = medianLocation(latitudes, longitudes, accuracy);
261                 _D("visit location set: lat=%.8f, lon=%.8f, acc=%.8f",
262                                 visit.location.latitude,
263                                 visit.location.longitude,
264                                 visit.location.accuracy);
265         } else {
266                 _D("visit location not set");
267         }
268 }
269
270 void ctx::VisitDetector::__processBuffer(std::shared_ptr<ctx::Frame> frame)
271 {
272         if (__bufferedFrames.empty()) {
273                 __historyFrames.push_back(frame);
274         } else {
275                 __historyFrames.push_back(__bufferedFrames[0]);
276                 for (size_t i = 1; i < __bufferedFrames.size(); i++) {
277                         __detectEntrance(__bufferedFrames[i]);
278                         if (__entranceToPlace)
279                                 break;
280                 }
281         }
282 }
283
284 void ctx::VisitDetector::__detectEntrance(std::shared_ptr<ctx::Frame> currentFrame)
285 {
286         if (currentFrame->macs2Counts.empty() || __historyFrames.empty()) {
287                 __resetHistory(currentFrame);
288                 return;
289         }
290
291         if (__stableCounter == 0) {
292                 std::shared_ptr<Frame> oldestHistoryFrame = __historyFrames[0];
293                 __stayMacs = macSetFromMacs2Counts(oldestHistoryFrame->macs2Counts);
294         }
295
296         std::shared_ptr<MacSet> currentBeacons = macSetFromMacs2Counts(currentFrame->macs2Counts);
297
298         if (similarity::overlapBiggerOverSmaller(*currentBeacons, *__stayMacs) > VISIT_DETECTOR_OVERLAP) {
299                 __stableCounter++;
300                 __historyFrames.push_back(currentFrame);
301                 if (__stableCounter == VISIT_DETECTOR_STABLE_DEPTH) // entrance detected
302                         __visitStartDetected();
303         } else {
304                 __resetHistory(currentFrame);
305         }
306         return;
307 }
308
309 void ctx::VisitDetector::__resetHistory()
310 {
311         __stableCounter = 0;
312         __historyFrames.clear();
313 }
314
315 void ctx::VisitDetector::__resetHistory(std::shared_ptr<Frame> frame)
316 {
317         __resetHistory();
318         __historyFrames.push_back(frame);
319 }
320
321 std::shared_ptr<ctx::MacSet> ctx::VisitDetector::__selectRepresentatives(const std::vector<std::shared_ptr<Frame>> &frames)
322 {
323         Macs2Counts reprs2Counts;
324         count_t allCount = 0;
325
326         for (auto frame : frames) {
327                 allCount += frame->numberOfTimestamps;
328                 for (auto &c : frame->macs2Counts) {
329                         reprs2Counts[c.first] += c.second;
330                 }
331         }
332
333         std::shared_ptr<Macs2Shares> reprs2Shares = __macSharesFromCounts(reprs2Counts, allCount);
334
335         share_t maxShare = __calcMaxShare(*reprs2Shares);
336         share_t threshold = maxShare < VISIT_DETECTOR_REP_THRESHOLD ? maxShare : VISIT_DETECTOR_REP_THRESHOLD;
337
338         std::shared_ptr<MacSet> reprsMacSet = __macSetOfGreaterOrEqualShare(*reprs2Shares, threshold);
339
340         return reprsMacSet;
341 }
342
343 ctx::share_t ctx::VisitDetector::__calcMaxShare(const ctx::Macs2Shares &macs2Shares)
344 {
345         ctx::share_t maxShare = 0.0;
346         for (auto &macShare : macs2Shares) {
347                 if (macShare.second > maxShare)
348                         maxShare = macShare.second;
349         }
350         return maxShare;
351 }
352
353 std::shared_ptr<ctx::MacSet> ctx::VisitDetector::__macSetOfGreaterOrEqualShare(const ctx::Macs2Shares &macs2Shares, ctx::share_t threshold)
354 {
355         std::shared_ptr<MacSet> macSet = std::make_shared<MacSet>();
356         for (auto &macShare : macs2Shares) {
357                 if (macShare.second >= threshold)
358                         macSet->insert(macShare.first);
359         }
360         return macSet;
361 }
362
363 std::shared_ptr<ctx::Macs2Shares> ctx::VisitDetector::__macSharesFromCounts(ctx::Macs2Counts const &macs2Counts, ctx::count_t denominator)
364 {
365         std::shared_ptr<Macs2Shares> macs2Shares(std::make_shared<Macs2Shares>());
366         for (auto macCount : macs2Counts) {
367                 (*macs2Shares)[macCount.first] = (share_t) macCount.second / denominator;
368         }
369         return macs2Shares;
370 }
371
372 std::shared_ptr<ctx::Visits> ctx::VisitDetector::__getVisits()
373 {
374         return __detectedVisits;
375 }
376
377 void ctx::VisitDetector::__dbCreateTables()
378 {
379         bool ret = __dbManager->createTable(0, VISIT_TABLE, __VISIT_TABLE_COLUMNS);
380         _D("db: Visit Table Creation Result: %s", ret ? "SUCCESS" : "FAIL");
381
382         ret = __dbManager->createTable(0, WIFI_APS_MAP_TABLE, __WIFI_APS_MAP_TABLE_COLUMNS);
383         _D("db: Wifi AP Map Table Creation Result: %s", ret ? "SUCCESS" : "FAIL");
384 }
385
386 void ctx::VisitDetector::__putVisitCategToJson(const char* key, const Categs &categs, int categType, Json &data)
387 {
388         auto categ = categs.find(categType);
389         if (categ == categs.end()) {
390                 _E("json_put_visit no type %d in categs", categType);
391         } else {
392                 data.set(NULL, key, categ->second);
393         }
394 }
395
396 void ctx::VisitDetector::__putVisitCategsToJson(const Categs &categs, Json &data)
397 {
398         __putVisitCategToJson(VISIT_COLUMN_CATEG_HOME, categs, PLACE_CATEG_ID_HOME, data);
399         __putVisitCategToJson(VISIT_COLUMN_CATEG_WORK, categs, PLACE_CATEG_ID_WORK, data);
400         __putVisitCategToJson(VISIT_COLUMN_CATEG_OTHER, categs, PLACE_CATEG_ID_OTHER, data);
401 }
402
403 int ctx::VisitDetector::__dbInsertVisit(Visit visit)
404 {
405         std::stringstream ss;
406         ss << *visit.macSet;
407
408         Json data;
409         data.set(NULL, VISIT_COLUMN_WIFI_APS, ss.str().c_str());
410
411         data.set(NULL, VISIT_COLUMN_LOCATION_VALID, visit.locationValid);
412         data.set(NULL, VISIT_COLUMN_LOCATION_LATITUDE, visit.location.latitude);
413         data.set(NULL, VISIT_COLUMN_LOCATION_LONGITUDE, visit.location.longitude);
414         data.set(NULL, VISIT_COLUMN_LOCATION_ACCURACY, visit.location.accuracy);
415
416         data.set(NULL, VISIT_COLUMN_START_TIME, static_cast<int>(visit.interval.start));
417         data.set(NULL, VISIT_COLUMN_END_TIME, static_cast<int>(visit.interval.end));
418
419 #ifdef TIZEN_ENGINEER_MODE
420         std::string startTimeHuman = DebugUtils::humanReadableDateTime(visit.interval.start, "%F %T", 80);
421         std::string endTimeHuman = DebugUtils::humanReadableDateTime(visit.interval.end, "%F %T", 80);
422         data.set(NULL, VISIT_COLUMN_START_TIME_HUMAN, startTimeHuman.c_str());
423         data.set(NULL, VISIT_COLUMN_END_TIME_HUMAN, endTimeHuman.c_str());
424         _D("db: visit table insert interval: (%d, %d): (%s, %s)",
425                         visit.interval.start, visit.interval.end, startTimeHuman.c_str(), endTimeHuman.c_str());
426 #else
427         _D("db: visit table insert interval: (%d, %d)", visit.interval.start, visit.interval.end);
428 #endif /* TIZEN_ENGINEER_MODE */
429
430         __putVisitCategsToJson(visit.categs, data);
431
432         int64_t rowId;
433         bool ret = __dbManager->insertSync(VISIT_TABLE, data, &rowId);
434         _D("db: visit table insert result: %s", ret ? "SUCCESS" : "FAIL");
435         return ret;
436 }
437
438 int ctx::VisitDetector::__dbInsertWifiAPsMap(Visit visit)
439 {
440         std::stringstream query;
441         time_t now = time(nullptr);
442         const char* separator = " ";
443         query << "BEGIN TRANSACTION; \
444                         REPLACE INTO " WIFI_APS_MAP_TABLE " \
445                         ( " WIFI_APS_MAP_COLUMN_MAC ", " WIFI_APS_MAP_COLUMN_NETWORK_NAME ", " WIFI_APS_MAP_COLUMN_INSERT_TIME " ) \
446                         VALUES";
447         for (Mac mac : *visit.macSet) {
448                 // TODO: Add protection from SQL injection in network name!!
449                 query << separator << "( '" << mac << "', '" << __wifiAPsMap.find(mac)->second << "', '" << now << "' )";
450                 separator = ", ";
451         }
452         __wifiAPsMap.clear();
453         query << "; \
454                         END TRANSACTION;";
455         bool ret = __dbManager->execute(0, query.str().c_str(), NULL);
456         _D("DB Wifi APs map insert request: %s", ret ? "SUCCESS" : "FAIL");
457         return ret;
458 }
459
460 void ctx::VisitDetector::onNewLocation(LocationEvent locationEvent)
461 {
462         _D("");
463         locationEvent.log();
464         __locationEvents.push_back(locationEvent);
465 };
466
467 void ctx::VisitDetector::__setPeriod(PlaceRecogMode energyMode)
468 {
469         switch (energyMode) {
470         case PLACE_RECOG_LOW_POWER_MODE:
471                 __periodSeconds = VISIT_DETECTOR_PERIOD_SECONDS_LOW_POWER;
472                 break;
473         case PLACE_RECOG_HIGH_ACCURACY_MODE:
474                 __periodSeconds = VISIT_DETECTOR_PERIOD_SECONDS_HIGH_ACCURACY;
475                 break;
476         default:
477                 _E("Incorrect energy mode");
478         }
479 }
480
481 void ctx::VisitDetector::setMode(PlaceRecogMode energyMode)
482 {
483         _D("");
484         __setPeriod(energyMode);
485         if (__wifiLogger)
486                 __wifiLogger->setMode(energyMode);
487 }
488
489
490