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