Initial Release
[vicar] / src / vicar-daemon / src / callrouter.cpp
diff --git a/src/vicar-daemon/src/callrouter.cpp b/src/vicar-daemon/src/callrouter.cpp
new file mode 100644 (file)
index 0000000..e6155f5
--- /dev/null
@@ -0,0 +1,500 @@
+/*
+@version: 0.2
+@author: Sudheer K. <scifi.guy@hotmail.com>
+@license: GNU General Public License
+*/
+
+#include "callrouter.h"
+#include <dbusutility.h>
+#include <gconfutility.h>
+#include <QDebug>
+#include <QRegExp>
+#include <QDBusConnection>
+#include <QTimer>
+#include <QStringListIterator>
+
+//Create a statis Dbus utility object that will be shared across all member functions
+static DbusUtility dbusUtility = DbusUtility();
+static QString strLastDialedNumber = QString();
+
+CallRouter::CallRouter(QObject *parent) :
+    QObject(parent)
+{
+        gconfUtility = new GConfUtility();
+}
+
+CallRouter::~CallRouter(){
+        delete gconfUtility;
+        gconfUtility = 0;
+}
+
+void CallRouter::registerDBusService(){
+    QDBusConnection connection = dbusUtility.getConnection();
+
+    if (!connection.registerService(APPLICATION_DBUS_SERVICE)) {
+        qDebug() << dbusUtility.getErrorMessage();
+        exit(1);
+    }
+
+    if (!connection.registerObject(APPLICATION_DBUS_PATH, this,
+            QDBusConnection::ExportScriptableSlots)) {
+        qDebug() << dbusUtility.getErrorMessage();
+        exit(2);
+    }
+
+    this->connectToDBusSignals();
+
+}
+
+
+void CallRouter::unregisterDBusService(){
+
+    this->disconnectFromDBusSignals();
+
+    QDBusConnection connection = dbusUtility.getConnection();
+
+    connection.unregisterObject(APPLICATION_DBUS_PATH,QDBusConnection::UnregisterTree);
+
+    if (!connection.unregisterService(APPLICATION_DBUS_SERVICE)) {
+        qDebug() << dbusUtility.getErrorMessage();
+        exit(3);
+    }
+
+}
+
+void CallRouter::connectToDBusSignals(){
+
+    QDBusConnection connection = dbusUtility.getConnection();
+
+    // Connect to the signal to enable call routing
+    bool success = connection.connect(QString(""),QString(""),
+                       APPLICATION_DBUS_INTERFACE,
+                       QString("startOutgoingCallMonitor"),this,
+                       SLOT(startOutgoingCallMonitor()));
+
+    if (success){
+        qDebug() << "Successfully connected to Dbus signal org.maemo.vicar.startOutgoingCallMonitor";
+    }
+    else{
+        qDebug() << "Failed to connect to Dbus signal org.maemo.vicar.startOutgoingCallMonitor";
+        qDebug() <<"DBus Error: "<< qPrintable(dbusUtility.getErrorMessage());
+    }
+
+    // Connect to the signal to disable call routing
+    success = connection.connect(QString(""),QString(""),
+                       APPLICATION_DBUS_INTERFACE,
+                       QString("stopOutgoingCallMonitor"),this,
+                       SLOT(stopOutgoingCallMonitor()));
+
+    if (success){
+        qDebug() << "Successfully connected to Dbus signal org.maemo.vicar.stopOutgoingCallMonitor";
+    }
+    else{
+        qDebug() << "Failed to connect to Dbus signal org.maemo.vicar.stopOutgoingCallMonitor";
+        qDebug() <<"DBus Error: "<< qPrintable(dbusUtility.getErrorMessage());
+    }
+
+}
+
+void CallRouter::disconnectFromDBusSignals(){
+
+    QDBusConnection connection = dbusUtility.getConnection();
+
+    // Disconnect from the signal to enable call routing
+    bool success = connection.disconnect(QString(""),QString(""),
+                       APPLICATION_DBUS_INTERFACE,
+                       QString("startOutgoingCallMonitor"),this,
+                       SLOT(startOutgoingCallMonitor()));
+
+    if (success){
+        qDebug() << "Successfully disconnected from Dbus signal org.maemo.vicar.startOutgoingCallMonitor";
+    }
+    else{
+        qDebug() << "Failed to disconnect from Dbus signal org.maemo.vicar.startOutgoingCallMonitor";
+        qDebug() <<"DBus Error: "<< qPrintable(dbusUtility.getErrorMessage());
+    }
+
+    // Disconnect from the signal to disable call routing
+    success = connection.connect(QString(""),QString(""),
+                       APPLICATION_DBUS_INTERFACE,
+                       QString("stopOutgoingCallMonitor"),this,
+                       SLOT(stopOutgoingCallMonitor()));
+
+    if (success){
+        qDebug() << "Successfully disconnected from Dbus signal org.maemo.vicar.stopOutgoingCallMonitor";
+    }
+    else{
+        qDebug() << "Failed to disconnect from Dbus signal org.maemo.vicar.stopOutgoingCallMonitor";
+        qDebug() <<"DBus Error: "<< qPrintable(dbusUtility.getErrorMessage());
+    }
+
+}
+
+void CallRouter::startOutgoingCallMonitor(){
+
+    // Connect to DBus to monitor all outgoing calls
+
+    QDBusConnection connection = dbusUtility.getConnection();
+
+
+    // Declare the slot to be executed when new calls are placed
+
+    bool success = connection.connect(QString(""),
+                       CSD_CALL_PATH,
+                       CSD_CALL_INTERFACE,
+                       QString("CreateRequested"),this,
+                       SLOT(processOutgoingCall(const QDBusMessage&)));
+
+    if (success){
+        qDebug() << "Successfully connected to Dbus signal CreateRequested in interface "<< CSD_CALL_INTERFACE;
+    }
+    else{
+        qDebug() << "Failed to connect to Dbus signal CreateRequested in interface " << CSD_CALL_INTERFACE;
+        qDebug() <<"DBus Error: "<< dbusUtility.getErrorMessage();
+    }
+
+
+}
+
+void CallRouter::stopOutgoingCallMonitor(){
+
+    this->stopCallStatusMonitors();
+
+    //Disconnect the slots from Dbus signals
+    QDBusConnection connection = dbusUtility.getConnection();
+
+    // Disconnect the slot for new calls
+    bool status = connection.disconnect(QString(""),
+                                           CSD_CALL_PATH,
+                                           CSD_CALL_INTERFACE,
+                                           QString("CreateRequested"),this,
+                                           SLOT(processOutgoingCall(const QDBusMessage&)));
+
+    if (status){
+        qDebug() << "Successfully disconnected from Dbus signal CreateRequested in interface "<< CSD_CALL_INTERFACE;
+    }
+    else{
+        qDebug() << "Failed to disconnect from Dbus signal CreateRequested in interface "<< CSD_CALL_INTERFACE;
+        qDebug() <<"DBus Error: "<< dbusUtility.getErrorMessage();
+    }
+
+}
+
+void CallRouter::processOutgoingCall(const QDBusMessage& dbusMessage){
+
+    //Verify Whether Call Routing is Enabled
+    bool isRoutingEnabled = gconfUtility->getGconfValueBoolean("routing_enabled");
+
+    if (isRoutingEnabled){
+        //User is making a phone call. Get the phone number and verify if it is an international number
+        QList<QVariant> listArguments = dbusMessage.arguments();
+        QString strInternationalNumber =  listArguments.first().toString();
+
+        qDebug() << "New Call Identified. Destination number is " << strInternationalNumber;
+
+         if (strInternationalNumber.startsWith("+") ||
+             strInternationalNumber.startsWith("00"))
+         {
+             qDebug() << "International number "<< strInternationalNumber << " recognized. Starting proceedings..";
+
+             //Check whether this is one of the excluded country codes
+             if (!isExcludedNumber(strInternationalNumber)){
+
+                //International number. Disconnect the current call (A new call will be placed)
+
+                //No arguments required to cancel the current call
+                QList<QVariant> argsToSend;
+                bool status = dbusUtility.sendMethodCall(CSD_CALL_SERVICE,
+                                                         CSD_CALL_PATH,
+                                                         CSD_CALL_INTERFACE,
+                                                         QString("Release"),argsToSend);
+
+                QString strUserMessage;
+                if (status){
+                    strUserMessage = QString("Routing international call via ").append(APPLICATION_FRIENDLY_NAME).append("..");
+                    qDebug() << strUserMessage;
+                    strLastDialedNumber = strInternationalNumber;
+                }
+                else{
+                    strUserMessage = QString("Call could not be cancelled.");
+                    qDebug() << dbusUtility.getErrorMessage();
+                }
+
+                dbusUtility.displayNotification(strUserMessage);
+
+                //Wait for a few seconds before the current call is completely disconnected
+                QTimer *timer = new QTimer(this);
+                timer->setSingleShot(true);
+                connect(timer, SIGNAL(timeout()), this, SLOT(callViaCallingCard()));
+                timer->start(3000);
+             }
+        }
+     }
+}
+
+void CallRouter::callViaCallingCard(){
+        //Now call the calling card number. This is generally a local and/or tollfree number
+
+        QString strCallingCardNumber = gconfUtility->getGconfValueString("calling_card_number");
+
+        qDebug() << "Wait time elapsed. Initiating call to "<< strCallingCardNumber;
+
+        QList<QVariant> argsToSend;
+        argsToSend.append(strCallingCardNumber);
+        argsToSend.append(0);
+
+        bool status = dbusUtility.sendMethodCall(CSD_SERVICE,
+                                                 CSD_CALL_PATH,
+                                             CSD_CALL_INTERFACE,
+                                             QString("CreateWith"),argsToSend);
+
+        QString strUserMessage;
+        if (status){
+            qDebug() << "Call initiated successfully. Connecting DBus slot for audio connection monitor";
+             startCallStatusMonitors();
+        }
+        else {
+            strUserMessage = QString("Unable to initiate new call to ").append(strCallingCardNumber);
+            qDebug() << dbusUtility.getErrorMessage();
+            strLastDialedNumber.clear();
+        }
+
+        dbusUtility.displayNotification(strUserMessage);
+
+}
+
+void CallRouter::startCallStatusMonitors(){
+    /* Declare the slot to be executed when a call is picked up by other party (Audio connection established).
+       We need this to confirm whether a call went though successfully.
+    */
+
+    QDBusConnection connection = dbusUtility.getConnection();
+
+    bool success = connection.connect(QString(""),
+                           CSD_CALL_INSTANCE_PATH,
+                           CSD_CALL_INSTANCE_INTERFACE,
+                           QString("AudioConnect"),this,
+                           SLOT(sendNumberAsDTMFCode(const QDBusMessage&)));
+
+    if (success){
+        qDebug() << "Successfully connected to Dbus signal AudioConnect in interface "<< CSD_CALL_INSTANCE_INTERFACE;
+    }
+    else{
+        qDebug() << "Failed to connect to Dbus signal AudioConnect in interface "<< CSD_CALL_INSTANCE_INTERFACE;
+        qDebug() <<"DBus Error: "<< dbusUtility.getErrorMessage();
+    }
+
+    /* Declare the slot to be executed when the call is terminated (due to connection errors etc).
+       We need this to avoid sending DTMF code on wrong calls.
+    */
+
+    success = connection.connect(QString(""),
+                               CSD_CALL_INSTANCE_PATH,
+                               CSD_CALL_INSTANCE_INTERFACE,
+                               QString("Terminated"),this,
+                               SLOT(stopCallStatusMonitors()));
+
+    if (success){
+        qDebug() << "Successfully connected to Dbus signal Terminated in interface "<< CSD_CALL_INSTANCE_INTERFACE;
+    }
+    else{
+        qDebug() << "Failed to connect to Dbus signal Terminated in interface "<< CSD_CALL_INSTANCE_INTERFACE;
+        qDebug() <<"DBus Error: "<< dbusUtility.getErrorMessage();
+    }
+
+    /* Declare the slot to be executed when a call is received
+      (before we can place the call to calling card number).
+       It is extremely rare that somebody should get a call within these few seconds.
+       In any case, we need this to avoid sending DTMF code on the received call.
+
+       Btw - I don't care for the incoming number here. If anyone is calling the user before we can send DTMF code,
+       then we stop sending the DTMF code even if user does not respond to the call.
+    */
+
+    success = connection.connect(QString(""),
+                               CSD_CALL_PATH,
+                               CSD_CALL_INTERFACE,
+                               QString("Coming"),this,
+                               SLOT(stopCallStatusMonitors()));
+
+    if (success){
+        qDebug() << "Successfully connected to Dbus signal Coming in interface" << CSD_CALL_INTERFACE;
+    }
+    else{
+        qDebug() << "Failed to connect to Dbus signal Coming in interface" << CSD_CALL_INTERFACE;
+        qDebug() <<"DBus Error: "<< dbusUtility.getErrorMessage();
+    }
+}
+
+void CallRouter::stopCallStatusMonitors(){
+
+    strLastDialedNumber.clear();
+
+    QDBusConnection connection = dbusUtility.getConnection();
+
+    // Disconnect the slot for audio connection status
+    bool status = connection.disconnect(QString(""),
+                                   CSD_CALL_INSTANCE_PATH,
+                                   CSD_CALL_INSTANCE_INTERFACE,
+                                   QString("AudioConnect"),this,
+                                   SLOT(sendNumberAsDTMFCode(const QDBusMessage&)));
+
+    if (status){
+        qDebug() << "Successfully disconnected from Dbus signal AudioConnect in interface "<< CSD_CALL_INSTANCE_INTERFACE;
+    }
+    else{
+        qDebug() << "Failed to disconnect from Dbus signal AudioConnect in interface "<< CSD_CALL_INSTANCE_INTERFACE;
+        qDebug() <<"DBus Error: "<< dbusUtility.getErrorMessage();
+    }
+
+    // Disconnect the slot for monitoring terminated calls
+    status = connection.disconnect(QString(""),
+                                   CSD_CALL_INSTANCE_PATH,
+                                   CSD_CALL_INSTANCE_INTERFACE,
+                                   QString("Terminated"),this,
+                                   SLOT(stopCallStatusMonitors()));
+
+    if (status){
+        qDebug() << "Successfully disconnected from Dbus signal Terminated in interface "<< CSD_CALL_INSTANCE_INTERFACE;
+    }
+    else{
+        qDebug() << "Failed to disconnect from Dbus signal Terminated in interface "<< CSD_CALL_INSTANCE_INTERFACE;
+        qDebug() <<"DBus Error: "<< dbusUtility.getErrorMessage();
+    }
+
+    // Disconnect the slot for monitoring incoming calls
+    status = connection.disconnect(QString(""),
+                                   CSD_CALL_PATH,
+                                   CSD_CALL_INTERFACE,
+                                   QString("Coming"),this,
+                                   SLOT(stopCallStatusMonitors()));
+
+    if (status){
+        qDebug() << "Successfully disconnected from Dbus signal Coming in interface" << CSD_CALL_INTERFACE;
+    }
+    else{
+        qDebug() << "Failed to disconnect from Dbus signal Coming in interface" << CSD_CALL_INTERFACE;
+        qDebug() <<"DBus Error: "<< dbusUtility.getErrorMessage();
+    }
+}
+
+void CallRouter::sendNumberAsDTMFCode(const QDBusMessage& dbusMessage){
+
+    if (!strLastDialedNumber.isEmpty()){
+        //Verify whether we have the last dialed number available
+
+        QList<QVariant> listArguments = dbusMessage.arguments();
+        bool audioConnected =  listArguments.first().toBool();
+
+        if (audioConnected){
+            // Now that the call to Calling card number is successful. We can send the original number as DTMF tones
+            QString strDTMFCode = convertToDTMFCode(strLastDialedNumber);
+
+            qDebug() << "Audio connection established. Sending DTMF code "<< strDTMFCode;
+
+            QList<QVariant> argsToSend;
+            argsToSend.append(strDTMFCode);
+
+            bool status = dbusUtility.sendMethodCall(CSD_SERVICE,
+                                                     CSD_CALL_PATH,
+                                                 CSD_CALL_INTERFACE,
+                                                 QString("SendDTMF"),argsToSend);
+
+            if (status){
+                QString strMessage = strDTMFCode.append(" sent as DTMF code");
+                qDebug() << strMessage;
+                dbusUtility.displayNotification(strMessage);
+            }
+            else{
+                qDebug() << "Unable to send DTMF code.";
+            }
+
+
+            /*
+              Connecting and Disconnecting from/to DBus signal for each international call
+              may not be the most efficient way of handling this. But we need to make sure
+              that the DTMF codes are sent only for the calls placed by this app (i.e calls to Calling card number).
+             */
+
+            qDebug() << "Now disconnecting from call status monitors..";
+            stopCallStatusMonitors();
+
+        }
+        else{
+            qDebug() << "Audio not yet connected.";
+        }
+    }
+    else
+    {
+        qDebug() << "Last dialed number is empty.";
+    }
+}
+
+QString CallRouter::convertToDTMFCode(QString strNumber){
+    QString strDTMFCode;
+
+    if (!strNumber.isEmpty()){
+
+        //Get the format required by calling card from coniguration
+        QString qstrDTMFFormat = gconfUtility->getGconfValueString("dtmf_format");
+        int intDTMFDelay = gconfUtility->getGconfValueInteger("dtmf_delay");
+
+        if (intDTMFDelay <1 ) intDTMFDelay = 1;
+        if (qstrDTMFFormat.isEmpty()) qstrDTMFFormat = "<Country Code><Area Code><Phone Number>";
+
+        //Add the prefix p so that there is some delay after the call is picked up by the automated system to send DTMF tones.
+        strDTMFCode = QString("").fill('p',intDTMFDelay);
+
+        /* Replace 00 (international dialing code) at the beginning
+           and also replace any character other than the numbers 0-9 and p.
+           */
+        QRegExp regexp = QRegExp("(^0{2})|[^0-9p]");        
+        strNumber = strNumber.replace(regexp,"");                
+
+        /* Now we have a clean number with only country code, area code and phone number,
+           lets convert it to the calling card friendly format
+           */
+        if (qstrDTMFFormat.startsWith("+")){
+            strDTMFCode = strDTMFCode.append("+");
+        }
+        else if (qstrDTMFFormat.startsWith("00")){
+            strDTMFCode = strDTMFCode.append("00");
+        }
+        else if (qstrDTMFFormat.startsWith("011")){
+            strDTMFCode = strDTMFCode.append("011");
+        }
+        //Default case - we don't need any prefix
+
+        strDTMFCode = strDTMFCode.append(strNumber);
+
+        //Now check whether we need a suffix
+        QString strDTMFSuffix = gconfUtility->getGconfValueString("dtmf_suffix");
+        if (!strDTMFSuffix.isEmpty() && !strDTMFSuffix.contains("--None--")){
+            strDTMFCode = strDTMFCode.append(strDTMFSuffix);
+        }
+    }
+
+    return strDTMFCode;
+}
+
+bool CallRouter::isExcludedNumber(QString strInternationalNumber){
+
+    bool isExcluded = false;
+
+    //Get the list of excluded codes
+    QString qstrExcludedNumbers = gconfUtility->getGconfValueString("numbers_to_exclude");
+    QStringList strExcludedCodeList = qstrExcludedNumbers.split(",");
+    QStringListIterator iterator(strExcludedCodeList);
+
+    QRegExp regexp = QRegExp("(^0{2})|[^0-9p]");
+
+    while (iterator.hasNext()){
+        QString strCode = iterator.next();
+        strCode = strCode.replace(regexp,"");
+        strInternationalNumber = strInternationalNumber.replace(regexp,"");
+        if (!strCode.isEmpty() && strInternationalNumber.startsWith(strCode)){
+            isExcluded = true;
+        }
+    }
+    return isExcluded;
+}