Fixes to connection handling in daemon.
[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         QTimer::singleShot(500, this, SLOT(closeConnection()));
405     }
406 }
407
408 void CallListener::closeConnection()
409 {
410     if(closeConnection_)
411     {
412         closeConnection_ = false;
413         ConnectionManager cm;
414         cm.disconnect(true);
415     }
416 }
417
418 bool CallListener::handleConnection()
419 {
420     if(!initialized_)
421     {
422         return false;
423     }
424
425     ConnectionManager cm;
426
427     if(cm.isConnected())
428     {
429         closeConnection_ = false;
430         return true;
431     }
432
433     closeConnection_ = true;
434
435     Settings::ConnectionType configType = Settings::instance()->getConnectionType();
436
437     if(configType == Settings::ALWAYS_ASK)
438     {
439         showError(tr("Automatic connecting is not allowed by settings."), BANNER_DELAY);
440         return false;
441     }
442
443     showDelayedResult(tr("Connecting..."), BANNER_DELAY);
444
445     ConnectionManager::Connection best;
446
447     ConnectionManager::ConnectionType lookupType = ConnectionManager::NO_TYPE;
448
449     switch(configType)
450     {
451     case Settings::WLAN:
452         lookupType = ConnectionManager::WLAN;
453         break;
454     case Settings::GPRS:
455         lookupType = ConnectionManager::GPRS;
456         break;
457     default:
458         lookupType = ConnectionManager::NO_TYPE;
459         break;
460     }
461
462     int cretries = 0;
463     int scans = 0;
464     bool found = false;
465     int maxScans = GPRS_SCANS;
466
467     if(lookupType != ConnectionManager::GPRS)
468     {
469         maxScans = WLAN_SCANS;
470     }
471
472     while(cretries < CONNECTION_LOOKUP_RETRIES)
473     {
474         if(!initialized_)
475         {
476             return false;
477         }
478
479         if(scans < maxScans)
480         {
481             if(cm.getBestConnection(best, lookupType))
482             {
483                 found = true;
484             }
485
486             scans++;
487         }
488
489         // If there is only gprs connection available,
490         // make sure that we are on 3g network
491         if(found && (best.type != ConnectionManager::GPRS || is3g()))
492         {
493             break;
494         }
495
496         if(found)
497         {
498             sleep(WAIT_BETWEEN_RETRIES);
499         }
500
501         qDebug() << "No connections found, retrying...";
502
503         cretries++;
504
505     }
506
507     if(cretries >= CONNECTION_LOOKUP_RETRIES)
508     {
509         showError(tr("No available 3G or WLAN networks found."));
510         return false;
511     }
512
513     int retries = 0;
514
515     while(retries < CONNECT_RETRIES)
516     {
517         if(!initialized_)
518         {
519             return false;
520         }
521
522         qDebug() << "Connecting to " << best.name << " (" << best.id << ")";
523
524         if(cm.connect(best.id))
525         {
526             sleep(500);
527             break;
528         }
529         else if(cm.error() == ConnectionManager::INVALID_IAP)
530         {
531             showError(tr("Selected access point doesn't exist."));
532             return false;
533         }
534
535         retries++;
536
537         qDebug() << "Unable to connect, retrying...";
538
539         if(retries < CONNECT_RETRIES)
540         {
541             sendRetrySignal(best.id, false);
542             sleep(WAIT_BETWEEN_RETRIES);
543         }
544
545     }
546
547     if(retries >= CONNECT_RETRIES)
548     {
549         sendRetrySignal(best.id, false);
550
551         if(initialized_)
552         {
553             showError(tr("Unable to connect to network."));
554         }
555
556         return false;
557     }
558
559     return initialized_;
560 }
561
562 void CallListener::showError(QString const& msg, int timeout)
563 {
564     qDebug() << "Error: " << msg;
565
566     if(!initialized_ || !box_)
567     {
568         return;
569     }
570
571     box_->setTimeout(ERROR_BANNER_TIMEOUT);
572
573     if(timeout)
574     {
575         showDelayedResult(msg, timeout);
576     }
577     else
578     {
579         showResult(msg);
580     }
581 }
582
583 bool CallListener::is3g()
584 {
585     QDBusMessage msg = QDBusMessage::createMethodCall("com.nokia.phone.net",
586                                                       "/com/nokia/phone/net",
587                                                       "Phone.Net",
588                                                       "get_registration_status");
589
590     QDBusMessage rep = systemBus_.call(msg);
591
592     if(rep.type() == QDBusMessage::ErrorMessage)
593     {
594         qDebug() << "Unable to get network status";
595         return false;
596     }
597
598     uint status = rep.arguments().value(6).toUInt();
599
600     if(status & 0x10 || status & 0x08)
601     {
602         return true;
603     }
604
605     return false;
606 }
607
608 void CallListener::sendRetrySignal(QString const& iap, bool retry)
609 {
610    QDBusMessage msg = QDBusMessage::createSignal("/com/nokia/icd_ui",
611                                                   "com.nokia.icd_ui",
612                                                   "retry");
613
614     QList<QVariant> arguments;
615     arguments.append(QVariant(iap));
616     arguments.append(QVariant(retry));
617     msg.setArguments(arguments);
618
619     QDBusConnection::systemBus().send(msg);
620
621     qDebug() << "Retry signal sent";
622 }
623
624 void CallListener::timerEvent(QTimerEvent* event)
625 {
626     Q_UNUSED(event);
627     killTimer(timer_);
628     timer_ = 0;
629 }
630
631 void CallListener::sleep(int ms)
632 {
633     if(timer_)
634     {
635         killTimer(timer_);
636     }
637
638     timer_ = startTimer(ms);
639
640     while(timer_)
641     {
642         QCoreApplication::processEvents(QEventLoop::WaitForMoreEvents);
643     }
644 }