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