Vicar release 0.6-1 (a.k.a I'm still here release)
[vicar] / src / vicar-daemon / src / callrouter.cpp
1 /*
2 @version: 0.6
3 @author: Sudheer K. <scifi1947 at gmail.com>
4 @license: GNU General Public License
5 */
6
7 #include "callrouter.h"
8 #include "vicardbusadaptor.h"
9 #include <dbusutility.h>
10 #include <gconfutility.h>
11 #include <databaseutility.h>
12 #include <telepathyutility.h>
13 #include <QDebug>
14 #include <QRegExp>
15 #include <QDBusConnection>
16 #include <QDBusMessage>
17 #include <QStringListIterator>
18
19 class CallRouterPrivate
20 {
21 public:
22     CallRouterPrivate(CallRouter * p) :
23         databaseUtility(new DatabaseUtility(p)),
24         dbusAdaptor(new VicarDbusAdaptor(p)),
25         dbusUtility(new DbusUtility(p)),        
26         gconfUtility(new GConfUtility(p)),
27         tpUtility(new TelepathyUtility(p)),
28         parent(p)
29     {
30         Q_ASSERT(0 != dbusAdaptor);
31         //Do not open here - Unable to capture changes to DB if it is open too early and closed late.
32         //databaseUtility->openDatabase();
33     }
34
35     ~CallRouterPrivate()
36     {
37         qDebug() << "VICaR: Call Router Destructing";
38         //databaseUtility->closeDatabase();
39     }
40
41     DatabaseUtility *databaseUtility;
42     VicarDbusAdaptor * dbusAdaptor;
43     DbusUtility * dbusUtility;
44     GConfUtility * gconfUtility;
45     TelepathyUtility *tpUtility;
46     QString strLastDialedNumber;
47     QString strLastDTMFCode;
48     org::maemo::vicar::Profile *currentProfile;
49     CallRouter * const parent;
50 };
51
52 // ---------------------------------------------------------------------------
53
54 CallRouter::CallRouter(QObject *parent) :
55     QObject(parent),
56     d(new CallRouterPrivate(this))
57 {
58         Q_ASSERT(0 != d);
59         this->registerDBusService();
60         qDebug() << "Vicar-Daemon: Registered DBus Service " << APPLICATION_DBUS_SERVICE;
61 }
62
63 CallRouter::~CallRouter(){
64 }
65
66 void CallRouter::registerDBusService(){
67     //Connect to Session Bus
68     QDBusConnection connection = d->dbusUtility->getConnection(false);
69
70     if (!connection.interface()->isServiceRegistered(APPLICATION_DBUS_SERVICE)){
71
72         if (!connection.registerService(APPLICATION_DBUS_SERVICE)) {
73             qDebug() << "Vicar-Daemon: " << d->dbusUtility->getErrorMessage();
74             exit(1);
75         }
76     }
77
78     if (!connection.registerObject(APPLICATION_DBUS_PATH, this, QDBusConnection::ExportAdaptors)) {
79         qDebug() << "Vicar-Daemon: " << d->dbusUtility->getErrorMessage();
80         exit(2);
81     }
82
83 }
84
85
86 void CallRouter::unregisterDBusService(){
87
88     //Disconnect from Session bus
89     QDBusConnection connection = d->dbusUtility->getConnection(false);
90
91     connection.unregisterObject(APPLICATION_DBUS_PATH,QDBusConnection::UnregisterTree);
92
93     if (!connection.unregisterService(APPLICATION_DBUS_SERVICE)) {
94         qDebug() << "Vicar-Daemon: " << d->dbusUtility->getErrorMessage();
95         exit(3);
96     }
97
98 }
99
100 QString CallRouter::callViaCallingCard(QString strDestinationNumber){
101
102         d->currentProfile = new org::maemo::vicar::Profile();
103         d->currentProfile->profileID = 0;
104
105         d->databaseUtility->openDatabase();
106         bool result = d->databaseUtility->findProfileByNumber(strDestinationNumber,d->currentProfile);
107
108         QString strErrorMessage;
109         if (!result){
110             strErrorMessage = QString("Vicar-Daemon: Error finding VICaR profile. %1").arg(d->databaseUtility->lastError().text());
111         }
112         else if (d->currentProfile->profileID == 0){
113             bool routeOnDefault = d->gconfUtility->getGconfValueBoolean("route_on_default");
114             if (routeOnDefault){
115                 qDebug() << "Vicar-Daemon: Routing directly as per configuration";
116                this->placeCall(strDestinationNumber);
117             }
118             else{
119                 qDebug() << "Vicar-Daemon: No profile found. Stopping..";
120                 strErrorMessage  = "Vicar: No routing profile defined for this number.";
121                 d->dbusUtility->displayNotification(strErrorMessage );
122             }
123         }
124         else{
125             //Now call the calling card number. This is generally a local and/or tollfree number
126             QString strCallingCardNumber = d->currentProfile->gatewayNumber;
127             qDebug() << "Vicar-Daemon: Initiating call to "<< strCallingCardNumber;
128             bool status = this->placeCall(strCallingCardNumber);
129             d->strLastDialedNumber = strDestinationNumber;
130
131             QString strUserMessage;
132
133             if (status){
134                 qDebug() << "Vicar-Daemon: Call initiated successfully. Connecting DBus slot for audio connection monitor";
135                  startCallStatusMonitors();
136             }
137             else {
138                 strUserMessage = QString("Unable to initiate new call to ").append(strCallingCardNumber);
139                 strErrorMessage = d->dbusUtility->getErrorMessage();
140                 qDebug() << "Vicar-Daemon: " << strErrorMessage;
141                 d->strLastDialedNumber.clear();
142                 delete d->currentProfile;
143                 d->currentProfile = 0;
144             }
145             d->dbusUtility->displayNotification(strUserMessage);
146         }
147
148         d->databaseUtility->closeDatabase();
149         return strErrorMessage;
150 }
151
152 bool CallRouter::placeCall(QString number){
153
154     QList<QVariant> argsToSend;
155     argsToSend.append(number);
156     argsToSend.append(0);
157
158     bool status = d->dbusUtility->sendMethodCall(CSD_SERVICE,
159                                              CSD_CALL_PATH,
160                                          CSD_CALL_INTERFACE,
161                                          QString("CreateWith"),argsToSend);
162     return status;
163
164 }
165
166 void CallRouter::startCallStatusMonitors(){
167     /* Declare the slot to be executed when a call is picked up by other party (Audio connection established).
168        We need this to confirm whether a call went though successfully.
169     */
170
171     QDBusConnection connection = d->dbusUtility->getConnection();
172
173     bool success = connection.connect(QString(""),
174                            CSD_CALL_INSTANCE_PATH,
175                            CSD_CALL_INSTANCE_INTERFACE,
176                            QString("AudioConnect"),this,
177                            SLOT(sendNumberAsDTMFCode(const QDBusMessage&)));
178
179     if (success){
180         qDebug() << "Vicar-Daemon: Successfully connected to Dbus signal AudioConnect in interface "<< CSD_CALL_INSTANCE_INTERFACE;
181     }
182     else{
183         qDebug() << "Vicar-Daemon: Failed to connect to Dbus signal AudioConnect in interface "<< CSD_CALL_INSTANCE_INTERFACE;
184         qDebug() <<"Vicar-Daemon: DBus Error: "<< d->dbusUtility->getErrorMessage();
185     }
186
187
188     /* Declare the slot to be executed when the DTMF code is sent.
189     */
190
191     success = connection.connect(QString(""),
192                                CSD_CALL_INSTANCE_PATH,
193                                CSD_CALL_INSTANCE_INTERFACE,
194                                QString("StoppedDTMF"),this,
195                                SLOT(displayDTMFConfirmation()));
196
197     if (success){
198         qDebug() << "Vicar-Daemon: Successfully connected to Dbus signal StoppedDTMF in interface "<< CSD_CALL_INSTANCE_INTERFACE;
199     }
200     else{
201         qDebug() << "Vicar-Daemon: Failed to connect to Dbus signal StoppedDTMF in interface "<< CSD_CALL_INSTANCE_INTERFACE;
202         qDebug() <<"Vicar-Daemon: DBus Error: "<< d->dbusUtility->getErrorMessage();
203     }
204
205
206     /* Declare the slot to be executed when the call is terminated (due to connection errors etc).
207        We need this to avoid sending DTMF code on wrong calls.
208     */
209
210     success = connection.connect(QString(""),
211                                CSD_CALL_INSTANCE_PATH,
212                                CSD_CALL_INSTANCE_INTERFACE,
213                                QString("Terminated"),this,
214                                SLOT(stopCallStatusMonitors()));
215
216     if (success){
217         qDebug() << "Vicar-Daemon: Successfully connected to Dbus signal Terminated in interface "<< CSD_CALL_INSTANCE_INTERFACE;
218     }
219     else{
220         qDebug() << "Vicar-Daemon: Failed to connect to Dbus signal Terminated in interface "<< CSD_CALL_INSTANCE_INTERFACE;
221         qDebug() <<"Vicar-Daemon: DBus Error: "<< d->dbusUtility->getErrorMessage();
222     }
223
224     /* Declare the slot to be executed when a call is received
225       (before we can place the call to calling card number).
226        It is extremely rare that somebody should get a call within these few seconds.
227        In any case, we need this to avoid sending DTMF code on the received call.
228
229        Btw - I don't care for the incoming number here. If anyone is calling the user before we can send DTMF code,
230        then we stop sending the DTMF code even if user does not respond to the call.
231     */
232
233     success = connection.connect(QString(""),
234                                CSD_CALL_PATH,
235                                CSD_CALL_INTERFACE,
236                                QString("Coming"),this,
237                                SLOT(stopCallStatusMonitors()));
238
239     if (success){
240         qDebug() << "Vicar-Daemon: Successfully connected to Dbus signal Coming in interface" << CSD_CALL_INTERFACE;
241     }
242     else{
243         qDebug() << "Vicar-Daemon: Failed to connect to Dbus signal Coming in interface" << CSD_CALL_INTERFACE;
244         qDebug() <<"Vicar-Daemon: DBus Error: "<< d->dbusUtility->getErrorMessage();
245     }
246 }
247
248 void CallRouter::stopCallStatusMonitors(){
249
250     d->strLastDTMFCode.clear();
251     d->strLastDialedNumber.clear();
252     delete d->currentProfile;
253     d->currentProfile = 0;
254
255     QDBusConnection connection = d->dbusUtility->getConnection();
256
257     // Disconnect the slot for audio connection status
258     bool status = connection.disconnect(QString(""),
259                                    CSD_CALL_INSTANCE_PATH,
260                                    CSD_CALL_INSTANCE_INTERFACE,
261                                    QString("AudioConnect"),this,
262                                    SLOT(sendNumberAsDTMFCode(const QDBusMessage&)));
263
264     if (status){
265         qDebug() << "Vicar-Daemon: Successfully disconnected from Dbus signal AudioConnect in interface "<< CSD_CALL_INSTANCE_INTERFACE;
266     }
267     else{
268         qDebug() << "Vicar-Daemon: Failed to disconnect from Dbus signal AudioConnect in interface "<< CSD_CALL_INSTANCE_INTERFACE;
269         qDebug() <<"Vicar-Daemon: DBus Error: "<< d->dbusUtility->getErrorMessage();
270     }
271
272     // Disconnect the slot for monitoring DTMF completion
273     status = connection.disconnect(QString(""),
274                                    CSD_CALL_INSTANCE_PATH,
275                                    CSD_CALL_INSTANCE_INTERFACE,
276                                    QString("StoppedDTMF"),this,
277                                    SLOT(displayDTMFConfirmation()));
278
279     if (status){
280         qDebug() << "Vicar-Daemon: Successfully disconnected from Dbus signal StoppedDTMF in interface "<< CSD_CALL_INSTANCE_INTERFACE;
281     }
282     else{
283         qDebug() << "Vicar-Daemon: Failed to disconnect from Dbus signal StoppedDTMF in interface "<< CSD_CALL_INSTANCE_INTERFACE;
284         qDebug() <<"Vicar-Daemon: DBus Error: "<< d->dbusUtility->getErrorMessage();
285     }
286
287
288     // Disconnect the slot for monitoring terminated calls
289     status = connection.disconnect(QString(""),
290                                    CSD_CALL_INSTANCE_PATH,
291                                    CSD_CALL_INSTANCE_INTERFACE,
292                                    QString("Terminated"),this,
293                                    SLOT(stopCallStatusMonitors()));
294
295     if (status){
296         qDebug() << "Vicar-Daemon: Successfully disconnected from Dbus signal Terminated in interface "<< CSD_CALL_INSTANCE_INTERFACE;
297     }
298     else{
299         qDebug() << "Vicar-Daemon: Failed to disconnect from Dbus signal Terminated in interface "<< CSD_CALL_INSTANCE_INTERFACE;
300         qDebug() <<"Vicar-Daemon: DBus Error: "<< d->dbusUtility->getErrorMessage();
301     }
302
303     // Disconnect the slot for monitoring incoming calls
304     status = connection.disconnect(QString(""),
305                                    CSD_CALL_PATH,
306                                    CSD_CALL_INTERFACE,
307                                    QString("Coming"),this,
308                                    SLOT(stopCallStatusMonitors()));
309
310     if (status){
311         qDebug() << "Vicar-Daemon: Successfully disconnected from Dbus signal Coming in interface" << CSD_CALL_INTERFACE;
312     }
313     else{
314         qDebug() << "Vicar-Daemon: Failed to disconnect from Dbus signal Coming in interface" << CSD_CALL_INTERFACE;
315         qDebug() <<"Vicar-Daemon: DBus Error: "<< d->dbusUtility->getErrorMessage();
316     }
317 }
318
319 void CallRouter::sendNumberAsDTMFCode(const QDBusMessage& dbusMessage){
320
321     if (!d->strLastDialedNumber.isEmpty() && d->currentProfile != 0){
322         //Verify whether we have the last dialed number available
323
324         QList<QVariant> listArguments = dbusMessage.arguments();
325         bool audioConnected =  listArguments.first().toBool();
326
327         if (audioConnected){
328             // Now that the call to Calling card number is successful. We can send the original number as DTMF tones
329             QString strDTMFCode = convertToDTMFCode(d->strLastDialedNumber);
330
331             qDebug() << "Vicar-Daemon: Audio connection established. Sending DTMF code "<< strDTMFCode;
332
333
334             QList<QVariant> argsToSend;
335             argsToSend.append(strDTMFCode);
336             bool status = d->dbusUtility->sendMethodCall(CSD_SERVICE,
337                                                      CSD_CALL_PATH,
338                                                  CSD_CALL_INTERFACE,
339                                                  QString("SendDTMF"),argsToSend);
340
341             if (status){
342                 qDebug() << "Vicar-Daemon: Sending " << strDTMFCode << " as DTMF code.";
343                 d->strLastDTMFCode = strDTMFCode;
344             }
345             else{
346                 qDebug() << "Vicar-Daemon: Unable to send DTMF code.";
347             }
348         }
349         else{
350             qDebug() << "Vicar-Daemon: Audio not yet connected.";
351         }
352     }
353     else
354     {
355         qDebug() << "Vicar-Daemon: Last dialed number is empty.";
356     }
357 }
358
359 void CallRouter::displayDTMFConfirmation(){
360  //This slot is called when the all the DTMF tones are sent (i.e StoppedDTMF signal is emitted)
361  //Just display confirmation message and cleanup
362
363
364     if (!d->strLastDTMFCode.isEmpty()){
365       QString strMessage = d->strLastDTMFCode.append(" sent as DTMF code");
366       d->dbusUtility->displayNotification(strMessage);
367       qDebug() << "Vicar-Daemon: "<< d->strLastDTMFCode << " sent as DTMF code.";
368     }
369
370     /*
371       Connecting and Disconnecting from/to DBus signal for each international call
372       may not be the most efficient way of handling this. But we need to make sure
373       that the DTMF codes are sent only for the calls placed by this app (i.e calls to Calling card number).
374      */
375
376     qDebug() << "Vicar-Daemon: Now disconnecting from call status monitors..";
377     stopCallStatusMonitors();
378 }
379
380 QString CallRouter::convertToDTMFCode(QString strNumber){
381     QString strDTMFCode;
382
383     if (!strNumber.isEmpty()){
384         //int intDTMFDelay = 1;
385
386         //Add the prefix p so that there is some delay after the call is picked up by the automated system to send DTMF tones.
387         //strDTMFCode = QString("").fill('p',intDTMFDelay);
388         strDTMFCode = "";
389
390         //Now check whether we need a prefix
391         QString strDTMFPrefix = d->currentProfile->dtmfPrefix;
392
393         if (!strDTMFPrefix.isEmpty()){
394             strDTMFCode = strDTMFCode.append(strDTMFPrefix);
395         }
396
397         //Get the format required by calling card from coniguration
398         QString qstrDTMFFormat = d->currentProfile->dtmfFormat;
399         if (qstrDTMFFormat.isEmpty()) qstrDTMFFormat = "<Country Code><Area Code><Phone Number>";
400
401         /* Replace 00 (international dialing code) at the beginning
402            and also replace any character other than the numbers 0-9 and p.
403            */
404         QRegExp regexp = QRegExp("(^0{2})|[^0-9p]");        
405         strNumber = strNumber.replace(regexp,"");                
406
407         /* Now we have a clean number with only country code, area code and phone number,
408            lets convert it to the calling card friendly format
409            */
410         if (qstrDTMFFormat.startsWith("+")){
411             strDTMFCode = strDTMFCode.append("+");
412         }
413         else if (qstrDTMFFormat.startsWith("00")){
414             strDTMFCode = strDTMFCode.append("00");
415         }
416         else if (qstrDTMFFormat.startsWith("011")){
417             strDTMFCode = strDTMFCode.append("011");
418         }
419
420         strDTMFCode = strDTMFCode.append(strNumber);
421
422         //Now check whether we need a suffix
423         QString strDTMFSuffix = d->currentProfile->dtmfSuffix;
424         if (!strDTMFSuffix.isEmpty()){
425             strDTMFCode = strDTMFCode.append(strDTMFSuffix);
426         }
427     }
428
429     return strDTMFCode;
430 }
431
432 //DBus Method used by external applications to check whether VICaR is enabled and running
433 bool CallRouter::isRunning(){
434
435     return true;
436
437     //Verify Whether VICaR telepathy account is online
438     /*
439     if (d->tpUtility->getAccountStatus() == "Connected"){
440         return true;
441     }
442     else{
443         return false;
444     }
445     */
446 }
447
448 //DBus Method used by external applications to call via VICaR
449 QString CallRouter::callInternationalNumber(const QString& strDestinationNumber){
450
451     QString strErrorMessage;
452
453     qDebug() << "Vicar-Daemon: New call requested by external application. Destination number is " << strDestinationNumber;
454
455     if (isValidPhoneNumber(strDestinationNumber)){
456
457         //Remove spaces in the phone number before using
458         QString numberWithoutSpaces = QString(strDestinationNumber).remove(" ");
459
460         strErrorMessage = this->callViaCallingCard(numberWithoutSpaces);
461     }
462     else{
463         strErrorMessage = QString("Vicar-Daemon: %1 is not a valid number").arg(strDestinationNumber);
464         if (strDestinationNumber != "publish" && strDestinationNumber != "subscribe"){
465             d->dbusUtility->displayNotification(QString("Vicar: %1 is not a valid number").arg(strDestinationNumber));
466         }
467     }
468
469     qDebug() << strErrorMessage;
470
471     if (strErrorMessage.isEmpty()){
472         return QString("Success");
473     }
474     else{
475         return strErrorMessage;
476     }
477  }
478
479 //Check whether a string is valid phone number
480 bool CallRouter::isValidPhoneNumber(QString strPhoneNumber){
481
482 /* Remove all dialble characters and space. The resulting string should be a valid number */
483     QRegExp regexp = QRegExp("[p+*# ]");
484
485     strPhoneNumber = strPhoneNumber.replace(regexp,"");
486
487     qDebug() << "Vicar Daemon: Cleaned up phone number is " << strPhoneNumber;
488
489 /* Now remove all digits, the resulting string should be empty, then it is a valid number */
490     regexp = QRegExp("[0-9]");
491
492     strPhoneNumber = strPhoneNumber.replace(regexp,"");
493
494     bool isNumber = strPhoneNumber.isEmpty();
495     return isNumber;
496 }