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