Some optimization to settings and searching.
[jenirok] / src / daemon / calllistener.cpp
1 /*
2  * This file is part of Jenirok.
3  *
4  * Jenirok is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * Jenirok is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License
15  * along with Jenirok.  If not, see <http://www.gnu.org/licenses/>.
16  *
17  */
18
19 #include <QtCore/QDebug>
20 #include <QtCore/QTimer>
21 #include <QtCore/QDateTime>
22 #include "calllistener.h"
23 #include "settings.h"
24 #include "cache.h"
25 #include "contactmanager.h"
26 #include "connectionmanager.h"
27 #include "sourcecoreconfig.h"
28 #include "db.h"
29
30 namespace
31 {
32     const QString CALL_SERVICE_NAME = "com.nokia.csd";
33     const QString CALL_SERVICE_PATH = "/com/nokia/csd/call";
34     const QString CALL_SERVICE_INTERFACE = "com.nokia.csd.Call";
35     const QString CALL_SERVICE_INSTANCE_NAME = "com.nokia.csd.Call.Instance";
36     const QString CALL_SIGNAL_INCOMING = "Coming";
37     const QString CALL_SIGNAL_RELEASE = "Release";
38     const QString CALL_SIGNAL_TERMINATED = "Terminated";
39     const QString CALL_SIGNAL_ANSWERED = "AudioConnect";
40 }
41
42 QDBusConnection CallListener::systemBus_ = QDBusConnection::systemBus();
43
44 CallListener::CallListener(): source_(0),
45 closeConnection_(false), initialized_(false), box_(0), label_(0),
46 retries_(-1), timer_(0), currentCall_(0)
47 {
48 }
49
50 CallListener::~CallListener()
51 {
52     end();
53     DB::removeDatabase();
54 }
55
56 bool CallListener::begin()
57 {
58     if(Settings::instance()->getConnectionType() == Settings::ALWAYS_ASK)
59     {
60         qDebug() << "Bad connection settings, unable to start";
61         return false;
62     }
63
64     sourceId_ = Source::stringToId(Settings::instance()->get("source"));
65     QMap<QString, QString> tmpConfig;
66     SourceCoreConfig* config = SourceCoreConfig::getCoreConfig(sourceId_);
67     config->getConfig(tmpConfig);
68     sourceConfig_ = tmpConfig;
69     delete config;
70
71     systemBus_.connect(CALL_SERVICE_NAME,
72                        CALL_SERVICE_PATH,
73                        CALL_SERVICE_INTERFACE,
74                        CALL_SIGNAL_INCOMING,
75                        this,
76                        SLOT(incomingCall(QDBusObjectPath, QString)));
77
78     systemBus_.connect(CALL_SERVICE_NAME,
79                        CALL_SERVICE_PATH,
80                        CALL_SERVICE_INTERFACE,
81                        CALL_SIGNAL_RELEASE,
82                        this,
83                        SLOT(callTerminate()));
84
85     qDebug() << "Starting...";
86
87     return true;
88
89 }
90
91 void CallListener::end()
92 {
93     systemBus_.disconnect(CALL_SERVICE_NAME,
94                           CALL_SERVICE_PATH,
95                           CALL_SERVICE_INTERFACE,
96                           CALL_SIGNAL_INCOMING,
97                           this,
98                           SLOT(incomingCall(QDBusObjectPath, QString)));
99
100     systemBus_.disconnect(CALL_SERVICE_NAME,
101                           CALL_SERVICE_PATH,
102                           CALL_SERVICE_INTERFACE,
103                           CALL_SIGNAL_RELEASE,
104                           this,
105                           SLOT(callTerminate()));
106
107     searchClose();
108     sourceConfig_.clear();
109
110 }
111
112 void CallListener::search(Source::SearchDetails const& details)
113 {
114     if(currentCall_)
115     {
116         delete currentCall_;
117     }
118
119     currentCall_ = new CallDetails;
120     currentCall_->number = details.query;
121     currentCall_->time = QDateTime::currentDateTime().toTime_t();
122     currentCall_->answered = false;
123
124     searchInit();
125
126     Source::Result result;
127
128     if(Cache::instance().findItem(details.query, result))
129     {
130
131         qDebug() << "Found from cache";
132
133         showDelayedResult(createResult(result.name,
134                                        result.street,
135                                        result.city), BANNER_DELAY);
136
137         currentCall_->result = result;
138     }
139     else
140     {
141         retries_ = 0;
142
143         if(!handleConnection())
144         {
145             qDebug() << "Unable to connect";
146             return;
147         }
148
149         showDelayedResult(tr("Searching..."), BANNER_DELAY);
150
151         source_->search(details);
152     }
153
154 }
155
156 void CallListener::requestFinished(QVector <Source::Result> const& results,
157                                    Source::SearchDetails const& details,
158                                    bool error)
159 {
160     if(closeConnection_)
161     {
162         closeConnection_ = false;
163         ConnectionManager cm;
164         cm.disconnect(true);
165     }
166
167     // If box is not visible, the call must have been terminated already
168     if(!initialized_ || !box_->isVisible() || !currentCall_)
169     {
170         return;
171     }
172
173     QString message;
174
175     if(error)
176     {
177         qDebug() << "Error: " << source_->errorString();
178
179         if(retries_ < SEARCH_RETRIES && retries_ >= 0)
180         {
181             retries_++;
182             source_->search(Source::SearchDetails(currentCall_->number, "", Source::BOTH));
183             return;
184         }
185         else
186         {
187             timedMessage_ = "";
188             QString errorString;
189             Source::Error error = source_->error();
190
191             switch(error)
192             {
193             case Source::TIMEOUT:
194                 errorString = tr("Request timed out");
195                 break;
196             default:
197                 errorString = source_->errorString();
198                 break;
199             }
200
201             showError(tr("Searching failed:") + " " + errorString + ".");
202         }
203     }
204     else
205     {
206         timedMessage_ = "";
207
208         if(results.size() == 0)
209         {
210             message = tr("Phone number was not found");
211             showResult(message);
212         }
213         else
214         {
215             message = createResult(results.at(0).name, results.at(0).street,
216                                    results.at(0).city);
217             showResult(message);
218             Source::Result result = results.at(0);
219             result.number = details.query;
220             Cache::instance().addItem(result);
221             currentCall_->result = results.at(0);
222         }
223     }
224
225     retries_ = -1;
226 }
227
228 QString CallListener::createResult(QString const& name, QString const& street, QString const& city)
229 {
230     QString result = "<b>" + name + "</b>";
231
232     if(!street.isEmpty() || !city.isEmpty())
233     {
234         result += "<br>";
235
236         if(!street.isEmpty())
237         {
238             result += street + ", ";
239         }
240
241         result += city;
242     }
243
244     return result;
245 }
246
247 void CallListener::showResult(QString const& text)
248 {
249     if(!initialized_)
250     {
251         return;
252     }
253
254     label_->setText("<font color='black'>" + text + "</font>");
255
256     if(box_->isVisible())
257     {
258         box_->hide();
259     }
260
261     box_->show();
262 }
263
264 void CallListener::incomingCall(QDBusObjectPath path, QString number)
265 {
266     if(number.isEmpty())
267     {
268         qDebug() << "Unknown caller without number";
269         return;
270     }
271
272     ContactManager cm;
273
274     if(!cm.numberExists(number))
275     {
276         qDebug() << "Number doesn't exist: " << number;
277
278         systemBus_.connect(CALL_SERVICE_NAME,
279                            path.path(),
280                            CALL_SERVICE_INSTANCE_NAME,
281                            CALL_SIGNAL_TERMINATED,
282                            this,
283                            SLOT(callTerminate()));
284
285         systemBus_.connect(CALL_SERVICE_NAME,
286                            path.path(),
287                            CALL_SERVICE_INSTANCE_NAME,
288                            CALL_SIGNAL_ANSWERED,
289                            this,
290                            SLOT(handleAnswer()));
291
292         search(Source::SearchDetails(number, "", Source::BOTH));
293     }
294     else
295     {
296         qDebug() << "Number exists: " << number;
297     }
298 }
299
300 void CallListener::callTerminate()
301 {
302     if(box_ && box_->isVisible())
303     {
304         box_->hide();
305     }
306
307     if(currentCall_)
308     {
309         currentCall_->result.number = currentCall_->number;
310         Cache::instance().logItem(currentCall_->result, !currentCall_->answered, currentCall_->time);
311         delete currentCall_;
312         currentCall_ = 0;
313     }
314
315     searchClose();
316 }
317
318 void CallListener::handleAnswer()
319 {
320     qDebug() << "Answered";
321
322     if(currentCall_)
323     {
324         currentCall_->answered = true;
325     }
326 }
327
328 void CallListener::showDelayedResult(QString const& text, int delay)
329 {
330     timedMessage_ = text;
331     QTimer::singleShot(delay, this, SLOT(showTimedMessage()));
332 }
333
334 void CallListener::showTimedMessage()
335 {
336     if(timedMessage_.size() == 0 || !initialized_)
337     {
338         return;
339     }
340
341     showResult(timedMessage_);
342
343     timedMessage_ = "";
344 }
345
346 void CallListener::searchInit()
347 {
348     qDebug() << "Initializing search...";
349
350     if(initialized_)
351     {
352         qDebug() << "Already initialized";
353         return;
354     }
355
356     source_ = Source::getSource(sourceId_);
357     SourceCoreConfig* config = SourceCoreConfig::getCoreConfig(sourceId_);
358     config->loadFromConfig(sourceConfig_);
359     config->apply(source_);
360     delete config;
361     source_->setMaxResults(1);
362     source_->setFindNumber(false);
363     source_->setTimeout(REQUEST_TIMEOUT);
364
365     connect(source_, SIGNAL(requestFinished(QVector <Source::Result> const&,
366                                            Source::SearchDetails const&, bool)),
367                                            this, SLOT(requestFinished(QVector <Source::Result> const&,
368                                                                       Source::SearchDetails const&, bool)));
369     box_ = new InformationBox;
370     label_ = new QLabel("", box_);
371     label_->setMargin(8);
372     label_->setWordWrap(true);
373     box_->setWidget(label_);
374     initialized_ = true;
375 }
376
377 void CallListener::searchClose()
378 {
379     if(!initialized_)
380     {
381         return;
382     }
383
384     initialized_ = false;
385
386     qDebug() << "Closing search...";
387
388     if(source_)
389     {
390         disconnect(source_, SIGNAL(requestFinished(QVector <Source::Result> const&,
391                                                   Source::SearchDetails const&, bool)),
392                                                   this, SLOT(requestFinished(QVector <Source::Result> const&,
393                                                                              Source::SearchDetails const&, bool)));
394     }
395
396     delete source_;
397     source_ = 0;
398     delete box_;
399     box_ = 0;
400     label_ = 0;
401
402     if(closeConnection_)
403     {
404         closeConnection_ = false;
405         ConnectionManager cm;
406         cm.disconnect(true);
407     }
408 }
409
410 bool CallListener::handleConnection()
411 {
412     if(!initialized_)
413     {
414         return false;
415     }
416
417     ConnectionManager cm;
418
419     if(cm.isConnected())
420     {
421         closeConnection_ = false;
422         return true;
423     }
424
425     closeConnection_ = true;
426
427     Settings::ConnectionType configType = Settings::instance()->getConnectionType();
428
429     if(configType == Settings::ALWAYS_ASK)
430     {
431         showError(tr("Automatic connecting is not allowed by settings."), BANNER_DELAY);
432         return false;
433     }
434
435     showDelayedResult(tr("Connecting..."), BANNER_DELAY);
436
437     ConnectionManager::Connection best;
438
439     ConnectionManager::ConnectionType lookupType = ConnectionManager::NO_TYPE;
440
441     switch(configType)
442     {
443     case Settings::WLAN:
444         lookupType = ConnectionManager::WLAN;
445         break;
446     case Settings::GPRS:
447         lookupType = ConnectionManager::GPRS;
448         break;
449     default:
450         lookupType = ConnectionManager::NO_TYPE;
451         break;
452     }
453
454     int cretries = 0;
455     int scans = 0;
456     bool found = false;
457     int maxScans = GPRS_SCANS;
458
459     if(lookupType != ConnectionManager::GPRS)
460     {
461         maxScans = WLAN_SCANS;
462     }
463
464     while(cretries < CONNECTION_LOOKUP_RETRIES)
465     {
466         if(!initialized_)
467         {
468             return false;
469         }
470
471         if(scans < maxScans)
472         {
473             if(cm.getBestConnection(best, lookupType))
474             {
475                 found = true;
476             }
477
478             scans++;
479         }
480
481         // If there is only gprs connection available,
482         // make sure that we are on 3g network
483         if(found && (best.type != ConnectionManager::GPRS || is3g()))
484         {
485             break;
486         }
487
488         if(found)
489         {
490             sleep(WAIT_BETWEEN_RETRIES);
491         }
492
493         qDebug() << "No connections found, retrying...";
494
495         cretries++;
496
497     }
498
499     if(cretries >= CONNECTION_LOOKUP_RETRIES)
500     {
501         showError(tr("No available 3G or WLAN networks found."));
502         return false;
503     }
504
505     int retries = 0;
506
507     while(retries < CONNECT_RETRIES)
508     {
509         if(!initialized_)
510         {
511             return false;
512         }
513
514         qDebug() << "Connecting to " << best.name;
515
516         if(cm.connect(best.id))
517         {
518             break;
519         }
520         else if(cm.error() == ConnectionManager::INVALID_IAP)
521         {
522             showError(tr("Selected access point doesn't exist."));
523             return false;
524         }
525
526         retries++;
527
528         qDebug() << "Unable to connect, retrying...";
529
530         if(retries < CONNECT_RETRIES)
531         {
532             sendRetrySignal(best.id, initialized_);
533         }
534
535     }
536
537     if(retries >= CONNECT_RETRIES)
538     {
539         sendRetrySignal(best.id, false);
540
541         if(initialized_)
542         {
543             showError(tr("Unable to connect to network."));
544         }
545
546         return false;
547     }
548
549     return initialized_;
550 }
551
552 void CallListener::showError(QString const& msg, int timeout)
553 {
554     qDebug() << "Error: " << msg;
555
556     if(!initialized_ || !box_)
557     {
558         return;
559     }
560
561     box_->setTimeout(ERROR_BANNER_TIMEOUT);
562
563     if(timeout)
564     {
565         showDelayedResult(msg, timeout);
566     }
567     else
568     {
569         showResult(msg);
570     }
571 }
572
573 bool CallListener::is3g()
574 {
575     QDBusMessage msg = QDBusMessage::createMethodCall("com.nokia.phone.net",
576                                                       "/com/nokia/phone/net",
577                                                       "Phone.Net",
578                                                       "get_registration_status");
579
580     QDBusMessage rep = systemBus_.call(msg);
581
582     if(rep.type() == QDBusMessage::ErrorMessage)
583     {
584         qDebug() << "Unable to get network status";
585         return false;
586     }
587
588     uint status = rep.arguments().value(6).toUInt();
589
590     if(status & 0x10 || status & 0x08)
591     {
592         return true;
593     }
594
595     return false;
596 }
597
598 void CallListener::sendRetrySignal(QString const& iap, bool retry)
599 {
600     QDBusMessage msg = QDBusMessage::createSignal("/com/nokia/icd_ui",
601                                                   "com.nokia.icd_ui",
602                                                   "retry");
603
604     QList<QVariant> arguments;
605     arguments.append(QVariant(iap));
606     arguments.append(QVariant(retry));
607     msg.setArguments(arguments);
608
609     QDBusConnection::systemBus().send(msg);
610
611     qDebug() << "Retry signal sent";
612 }
613
614 void CallListener::timerEvent(QTimerEvent* event)
615 {
616     Q_UNUSED(event);
617     killTimer(timer_);
618     timer_ = 0;
619 }
620
621 void CallListener::sleep(int ms)
622 {
623     if(timer_)
624     {
625         killTimer(timer_);
626     }
627
628     timer_ = startTimer(ms);
629
630     while(timer_)
631     {
632         QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents);
633     }
634 }