Initial Release
[vicar] / src / vicar-daemon / src / callrouter.cpp
1 /*
2 @version: 0.2
3 @author: Sudheer K. <scifi.guy@hotmail.com>
4 @license: GNU General Public License
5 */
6
7 #include "callrouter.h"
8 #include <dbusutility.h>
9 #include <gconfutility.h>
10 #include <QDebug>
11 #include <QRegExp>
12 #include <QDBusConnection>
13 #include <QTimer>
14 #include <QStringListIterator>
15
16 //Create a statis Dbus utility object that will be shared across all member functions
17 static DbusUtility dbusUtility = DbusUtility();
18 static QString strLastDialedNumber = QString();
19
20 CallRouter::CallRouter(QObject *parent) :
21     QObject(parent)
22 {
23         gconfUtility = new GConfUtility();
24 }
25
26 CallRouter::~CallRouter(){
27         delete gconfUtility;
28         gconfUtility = 0;
29 }
30
31 void CallRouter::registerDBusService(){
32     QDBusConnection connection = dbusUtility.getConnection();
33
34     if (!connection.registerService(APPLICATION_DBUS_SERVICE)) {
35         qDebug() << dbusUtility.getErrorMessage();
36         exit(1);
37     }
38
39     if (!connection.registerObject(APPLICATION_DBUS_PATH, this,
40             QDBusConnection::ExportScriptableSlots)) {
41         qDebug() << dbusUtility.getErrorMessage();
42         exit(2);
43     }
44
45     this->connectToDBusSignals();
46
47 }
48
49
50 void CallRouter::unregisterDBusService(){
51
52     this->disconnectFromDBusSignals();
53
54     QDBusConnection connection = dbusUtility.getConnection();
55
56     connection.unregisterObject(APPLICATION_DBUS_PATH,QDBusConnection::UnregisterTree);
57
58     if (!connection.unregisterService(APPLICATION_DBUS_SERVICE)) {
59         qDebug() << dbusUtility.getErrorMessage();
60         exit(3);
61     }
62
63 }
64
65 void CallRouter::connectToDBusSignals(){
66
67     QDBusConnection connection = dbusUtility.getConnection();
68
69     // Connect to the signal to enable call routing
70     bool success = connection.connect(QString(""),QString(""),
71                        APPLICATION_DBUS_INTERFACE,
72                        QString("startOutgoingCallMonitor"),this,
73                        SLOT(startOutgoingCallMonitor()));
74
75     if (success){
76         qDebug() << "Successfully connected to Dbus signal org.maemo.vicar.startOutgoingCallMonitor";
77     }
78     else{
79         qDebug() << "Failed to connect to Dbus signal org.maemo.vicar.startOutgoingCallMonitor";
80         qDebug() <<"DBus Error: "<< qPrintable(dbusUtility.getErrorMessage());
81     }
82
83     // Connect to the signal to disable call routing
84     success = connection.connect(QString(""),QString(""),
85                        APPLICATION_DBUS_INTERFACE,
86                        QString("stopOutgoingCallMonitor"),this,
87                        SLOT(stopOutgoingCallMonitor()));
88
89     if (success){
90         qDebug() << "Successfully connected to Dbus signal org.maemo.vicar.stopOutgoingCallMonitor";
91     }
92     else{
93         qDebug() << "Failed to connect to Dbus signal org.maemo.vicar.stopOutgoingCallMonitor";
94         qDebug() <<"DBus Error: "<< qPrintable(dbusUtility.getErrorMessage());
95     }
96
97 }
98
99 void CallRouter::disconnectFromDBusSignals(){
100
101     QDBusConnection connection = dbusUtility.getConnection();
102
103     // Disconnect from the signal to enable call routing
104     bool success = connection.disconnect(QString(""),QString(""),
105                        APPLICATION_DBUS_INTERFACE,
106                        QString("startOutgoingCallMonitor"),this,
107                        SLOT(startOutgoingCallMonitor()));
108
109     if (success){
110         qDebug() << "Successfully disconnected from Dbus signal org.maemo.vicar.startOutgoingCallMonitor";
111     }
112     else{
113         qDebug() << "Failed to disconnect from Dbus signal org.maemo.vicar.startOutgoingCallMonitor";
114         qDebug() <<"DBus Error: "<< qPrintable(dbusUtility.getErrorMessage());
115     }
116
117     // Disconnect from the signal to disable call routing
118     success = connection.connect(QString(""),QString(""),
119                        APPLICATION_DBUS_INTERFACE,
120                        QString("stopOutgoingCallMonitor"),this,
121                        SLOT(stopOutgoingCallMonitor()));
122
123     if (success){
124         qDebug() << "Successfully disconnected from Dbus signal org.maemo.vicar.stopOutgoingCallMonitor";
125     }
126     else{
127         qDebug() << "Failed to disconnect from Dbus signal org.maemo.vicar.stopOutgoingCallMonitor";
128         qDebug() <<"DBus Error: "<< qPrintable(dbusUtility.getErrorMessage());
129     }
130
131 }
132
133 void CallRouter::startOutgoingCallMonitor(){
134
135     // Connect to DBus to monitor all outgoing calls
136
137     QDBusConnection connection = dbusUtility.getConnection();
138
139
140     // Declare the slot to be executed when new calls are placed
141
142     bool success = connection.connect(QString(""),
143                        CSD_CALL_PATH,
144                        CSD_CALL_INTERFACE,
145                        QString("CreateRequested"),this,
146                        SLOT(processOutgoingCall(const QDBusMessage&)));
147
148     if (success){
149         qDebug() << "Successfully connected to Dbus signal CreateRequested in interface "<< CSD_CALL_INTERFACE;
150     }
151     else{
152         qDebug() << "Failed to connect to Dbus signal CreateRequested in interface " << CSD_CALL_INTERFACE;
153         qDebug() <<"DBus Error: "<< dbusUtility.getErrorMessage();
154     }
155
156
157 }
158
159 void CallRouter::stopOutgoingCallMonitor(){
160
161     this->stopCallStatusMonitors();
162
163     //Disconnect the slots from Dbus signals
164     QDBusConnection connection = dbusUtility.getConnection();
165
166     // Disconnect the slot for new calls
167     bool status = connection.disconnect(QString(""),
168                                            CSD_CALL_PATH,
169                                            CSD_CALL_INTERFACE,
170                                            QString("CreateRequested"),this,
171                                            SLOT(processOutgoingCall(const QDBusMessage&)));
172
173     if (status){
174         qDebug() << "Successfully disconnected from Dbus signal CreateRequested in interface "<< CSD_CALL_INTERFACE;
175     }
176     else{
177         qDebug() << "Failed to disconnect from Dbus signal CreateRequested in interface "<< CSD_CALL_INTERFACE;
178         qDebug() <<"DBus Error: "<< dbusUtility.getErrorMessage();
179     }
180
181 }
182
183 void CallRouter::processOutgoingCall(const QDBusMessage& dbusMessage){
184
185     //Verify Whether Call Routing is Enabled
186     bool isRoutingEnabled = gconfUtility->getGconfValueBoolean("routing_enabled");
187
188     if (isRoutingEnabled){
189         //User is making a phone call. Get the phone number and verify if it is an international number
190         QList<QVariant> listArguments = dbusMessage.arguments();
191         QString strInternationalNumber =  listArguments.first().toString();
192
193         qDebug() << "New Call Identified. Destination number is " << strInternationalNumber;
194
195          if (strInternationalNumber.startsWith("+") ||
196              strInternationalNumber.startsWith("00"))
197          {
198              qDebug() << "International number "<< strInternationalNumber << " recognized. Starting proceedings..";
199
200              //Check whether this is one of the excluded country codes
201              if (!isExcludedNumber(strInternationalNumber)){
202
203                 //International number. Disconnect the current call (A new call will be placed)
204
205                 //No arguments required to cancel the current call
206                 QList<QVariant> argsToSend;
207                 bool status = dbusUtility.sendMethodCall(CSD_CALL_SERVICE,
208                                                          CSD_CALL_PATH,
209                                                          CSD_CALL_INTERFACE,
210                                                          QString("Release"),argsToSend);
211
212                 QString strUserMessage;
213                 if (status){
214                     strUserMessage = QString("Routing international call via ").append(APPLICATION_FRIENDLY_NAME).append("..");
215                     qDebug() << strUserMessage;
216                     strLastDialedNumber = strInternationalNumber;
217                 }
218                 else{
219                     strUserMessage = QString("Call could not be cancelled.");
220                     qDebug() << dbusUtility.getErrorMessage();
221                 }
222
223                 dbusUtility.displayNotification(strUserMessage);
224
225                 //Wait for a few seconds before the current call is completely disconnected
226                 QTimer *timer = new QTimer(this);
227                 timer->setSingleShot(true);
228                 connect(timer, SIGNAL(timeout()), this, SLOT(callViaCallingCard()));
229                 timer->start(3000);
230              }
231         }
232      }
233 }
234
235 void CallRouter::callViaCallingCard(){
236         //Now call the calling card number. This is generally a local and/or tollfree number
237
238         QString strCallingCardNumber = gconfUtility->getGconfValueString("calling_card_number");
239
240         qDebug() << "Wait time elapsed. Initiating call to "<< strCallingCardNumber;
241
242         QList<QVariant> argsToSend;
243         argsToSend.append(strCallingCardNumber);
244         argsToSend.append(0);
245
246         bool status = dbusUtility.sendMethodCall(CSD_SERVICE,
247                                                  CSD_CALL_PATH,
248                                              CSD_CALL_INTERFACE,
249                                              QString("CreateWith"),argsToSend);
250
251         QString strUserMessage;
252         if (status){
253             qDebug() << "Call initiated successfully. Connecting DBus slot for audio connection monitor";
254              startCallStatusMonitors();
255         }
256         else {
257             strUserMessage = QString("Unable to initiate new call to ").append(strCallingCardNumber);
258             qDebug() << dbusUtility.getErrorMessage();
259             strLastDialedNumber.clear();
260         }
261
262         dbusUtility.displayNotification(strUserMessage);
263
264 }
265
266 void CallRouter::startCallStatusMonitors(){
267     /* Declare the slot to be executed when a call is picked up by other party (Audio connection established).
268        We need this to confirm whether a call went though successfully.
269     */
270
271     QDBusConnection connection = dbusUtility.getConnection();
272
273     bool success = connection.connect(QString(""),
274                            CSD_CALL_INSTANCE_PATH,
275                            CSD_CALL_INSTANCE_INTERFACE,
276                            QString("AudioConnect"),this,
277                            SLOT(sendNumberAsDTMFCode(const QDBusMessage&)));
278
279     if (success){
280         qDebug() << "Successfully connected to Dbus signal AudioConnect in interface "<< CSD_CALL_INSTANCE_INTERFACE;
281     }
282     else{
283         qDebug() << "Failed to connect to Dbus signal AudioConnect in interface "<< CSD_CALL_INSTANCE_INTERFACE;
284         qDebug() <<"DBus Error: "<< dbusUtility.getErrorMessage();
285     }
286
287     /* Declare the slot to be executed when the call is terminated (due to connection errors etc).
288        We need this to avoid sending DTMF code on wrong calls.
289     */
290
291     success = connection.connect(QString(""),
292                                CSD_CALL_INSTANCE_PATH,
293                                CSD_CALL_INSTANCE_INTERFACE,
294                                QString("Terminated"),this,
295                                SLOT(stopCallStatusMonitors()));
296
297     if (success){
298         qDebug() << "Successfully connected to Dbus signal Terminated in interface "<< CSD_CALL_INSTANCE_INTERFACE;
299     }
300     else{
301         qDebug() << "Failed to connect to Dbus signal Terminated in interface "<< CSD_CALL_INSTANCE_INTERFACE;
302         qDebug() <<"DBus Error: "<< dbusUtility.getErrorMessage();
303     }
304
305     /* Declare the slot to be executed when a call is received
306       (before we can place the call to calling card number).
307        It is extremely rare that somebody should get a call within these few seconds.
308        In any case, we need this to avoid sending DTMF code on the received call.
309
310        Btw - I don't care for the incoming number here. If anyone is calling the user before we can send DTMF code,
311        then we stop sending the DTMF code even if user does not respond to the call.
312     */
313
314     success = connection.connect(QString(""),
315                                CSD_CALL_PATH,
316                                CSD_CALL_INTERFACE,
317                                QString("Coming"),this,
318                                SLOT(stopCallStatusMonitors()));
319
320     if (success){
321         qDebug() << "Successfully connected to Dbus signal Coming in interface" << CSD_CALL_INTERFACE;
322     }
323     else{
324         qDebug() << "Failed to connect to Dbus signal Coming in interface" << CSD_CALL_INTERFACE;
325         qDebug() <<"DBus Error: "<< dbusUtility.getErrorMessage();
326     }
327 }
328
329 void CallRouter::stopCallStatusMonitors(){
330
331     strLastDialedNumber.clear();
332
333     QDBusConnection connection = dbusUtility.getConnection();
334
335     // Disconnect the slot for audio connection status
336     bool status = connection.disconnect(QString(""),
337                                    CSD_CALL_INSTANCE_PATH,
338                                    CSD_CALL_INSTANCE_INTERFACE,
339                                    QString("AudioConnect"),this,
340                                    SLOT(sendNumberAsDTMFCode(const QDBusMessage&)));
341
342     if (status){
343         qDebug() << "Successfully disconnected from Dbus signal AudioConnect in interface "<< CSD_CALL_INSTANCE_INTERFACE;
344     }
345     else{
346         qDebug() << "Failed to disconnect from Dbus signal AudioConnect in interface "<< CSD_CALL_INSTANCE_INTERFACE;
347         qDebug() <<"DBus Error: "<< dbusUtility.getErrorMessage();
348     }
349
350     // Disconnect the slot for monitoring terminated calls
351     status = connection.disconnect(QString(""),
352                                    CSD_CALL_INSTANCE_PATH,
353                                    CSD_CALL_INSTANCE_INTERFACE,
354                                    QString("Terminated"),this,
355                                    SLOT(stopCallStatusMonitors()));
356
357     if (status){
358         qDebug() << "Successfully disconnected from Dbus signal Terminated in interface "<< CSD_CALL_INSTANCE_INTERFACE;
359     }
360     else{
361         qDebug() << "Failed to disconnect from Dbus signal Terminated in interface "<< CSD_CALL_INSTANCE_INTERFACE;
362         qDebug() <<"DBus Error: "<< dbusUtility.getErrorMessage();
363     }
364
365     // Disconnect the slot for monitoring incoming calls
366     status = connection.disconnect(QString(""),
367                                    CSD_CALL_PATH,
368                                    CSD_CALL_INTERFACE,
369                                    QString("Coming"),this,
370                                    SLOT(stopCallStatusMonitors()));
371
372     if (status){
373         qDebug() << "Successfully disconnected from Dbus signal Coming in interface" << CSD_CALL_INTERFACE;
374     }
375     else{
376         qDebug() << "Failed to disconnect from Dbus signal Coming in interface" << CSD_CALL_INTERFACE;
377         qDebug() <<"DBus Error: "<< dbusUtility.getErrorMessage();
378     }
379 }
380
381 void CallRouter::sendNumberAsDTMFCode(const QDBusMessage& dbusMessage){
382
383     if (!strLastDialedNumber.isEmpty()){
384         //Verify whether we have the last dialed number available
385
386         QList<QVariant> listArguments = dbusMessage.arguments();
387         bool audioConnected =  listArguments.first().toBool();
388
389         if (audioConnected){
390             // Now that the call to Calling card number is successful. We can send the original number as DTMF tones
391             QString strDTMFCode = convertToDTMFCode(strLastDialedNumber);
392
393             qDebug() << "Audio connection established. Sending DTMF code "<< strDTMFCode;
394
395             QList<QVariant> argsToSend;
396             argsToSend.append(strDTMFCode);
397
398             bool status = dbusUtility.sendMethodCall(CSD_SERVICE,
399                                                      CSD_CALL_PATH,
400                                                  CSD_CALL_INTERFACE,
401                                                  QString("SendDTMF"),argsToSend);
402
403             if (status){
404                 QString strMessage = strDTMFCode.append(" sent as DTMF code");
405                 qDebug() << strMessage;
406                 dbusUtility.displayNotification(strMessage);
407             }
408             else{
409                 qDebug() << "Unable to send DTMF code.";
410             }
411
412
413             /*
414               Connecting and Disconnecting from/to DBus signal for each international call
415               may not be the most efficient way of handling this. But we need to make sure
416               that the DTMF codes are sent only for the calls placed by this app (i.e calls to Calling card number).
417              */
418
419             qDebug() << "Now disconnecting from call status monitors..";
420             stopCallStatusMonitors();
421
422         }
423         else{
424             qDebug() << "Audio not yet connected.";
425         }
426     }
427     else
428     {
429         qDebug() << "Last dialed number is empty.";
430     }
431 }
432
433 QString CallRouter::convertToDTMFCode(QString strNumber){
434     QString strDTMFCode;
435
436     if (!strNumber.isEmpty()){
437
438         //Get the format required by calling card from coniguration
439         QString qstrDTMFFormat = gconfUtility->getGconfValueString("dtmf_format");
440         int intDTMFDelay = gconfUtility->getGconfValueInteger("dtmf_delay");
441
442         if (intDTMFDelay <1 ) intDTMFDelay = 1;
443         if (qstrDTMFFormat.isEmpty()) qstrDTMFFormat = "<Country Code><Area Code><Phone Number>";
444
445         //Add the prefix p so that there is some delay after the call is picked up by the automated system to send DTMF tones.
446         strDTMFCode = QString("").fill('p',intDTMFDelay);
447
448         /* Replace 00 (international dialing code) at the beginning
449            and also replace any character other than the numbers 0-9 and p.
450            */
451         QRegExp regexp = QRegExp("(^0{2})|[^0-9p]");        
452         strNumber = strNumber.replace(regexp,"");                
453
454         /* Now we have a clean number with only country code, area code and phone number,
455            lets convert it to the calling card friendly format
456            */
457         if (qstrDTMFFormat.startsWith("+")){
458             strDTMFCode = strDTMFCode.append("+");
459         }
460         else if (qstrDTMFFormat.startsWith("00")){
461             strDTMFCode = strDTMFCode.append("00");
462         }
463         else if (qstrDTMFFormat.startsWith("011")){
464             strDTMFCode = strDTMFCode.append("011");
465         }
466         //Default case - we don't need any prefix
467
468         strDTMFCode = strDTMFCode.append(strNumber);
469
470         //Now check whether we need a suffix
471         QString strDTMFSuffix = gconfUtility->getGconfValueString("dtmf_suffix");
472         if (!strDTMFSuffix.isEmpty() && !strDTMFSuffix.contains("--None--")){
473             strDTMFCode = strDTMFCode.append(strDTMFSuffix);
474         }
475     }
476
477     return strDTMFCode;
478 }
479
480 bool CallRouter::isExcludedNumber(QString strInternationalNumber){
481
482     bool isExcluded = false;
483
484     //Get the list of excluded codes
485     QString qstrExcludedNumbers = gconfUtility->getGconfValueString("numbers_to_exclude");
486     QStringList strExcludedCodeList = qstrExcludedNumbers.split(",");
487     QStringListIterator iterator(strExcludedCodeList);
488
489     QRegExp regexp = QRegExp("(^0{2})|[^0-9p]");
490
491     while (iterator.hasNext()){
492         QString strCode = iterator.next();
493         strCode = strCode.replace(regexp,"");
494         strInternationalNumber = strInternationalNumber.replace(regexp,"");
495         if (!strCode.isEmpty() && strInternationalNumber.startsWith(strCode)){
496             isExcluded = true;
497         }
498     }
499     return isExcluded;
500 }