blob: dfa2b9eddfc0559cce3865947fbd7adae4af4517 [file] [log] [blame]
/****************************************************************************
**
** This file is part of the Qt Extended Opensource Package.
**
** Copyright (C) 2009 Trolltech ASA.
**
** Contact: Qt Extended Information ([email protected])
**
** This file may be used under the terms of the GNU General Public License
** version 2.0 as published by the Free Software Foundation and appearing
** in the file LICENSE.GPL included in the packaging of this file.
**
** Please review the following information to ensure GNU General Public
** Licensing requirements will be met:
** http://www.fsf.org/licensing/licenses/info/GPLv2.html.
**
**
****************************************************************************/
#include "callmanager.h"
#include <qatutils.h>
#include <qsimcontrolevent.h>
CallManager::CallManager( QObject *parent )
: QObject( parent )
{
_holdWillFail = false;
_activateWillFail = false;
_joinWillFail = false;
_deflectWillFail = false;
_multipartyLimit = -1;
numRings = 0;
hangupTimer = new QTimer(this);
hangupTimer->setSingleShot(true);
connect( hangupTimer, SIGNAL(timeout()), this, SLOT(hangupTimeout()) );
ringTimer = new QTimer(this);
ringTimer->setSingleShot(true);
connect( ringTimer, SIGNAL(timeout()), this, SLOT(sendNextRing()) );
}
CallManager::~CallManager()
{
}
bool CallManager::command( const QString& cmd )
{
if ( cmd.startsWith( "ATD*" ) || cmd.startsWith( "ATD#" ) ) {
// Supplementary service request - just say OK for now.
emit send( "OK" );
} else if ( cmd.startsWith( "ATD" ) && cmd.endsWith( ";" ) ) {
// Voice call setup.
QString number = cmd.mid(3, cmd.length() - 4);
if ( number.endsWith( "g" ) || number.endsWith( "G" ) )
number = number.left(number.length() - 1); // Closed user group flag - skip.
if ( number.endsWith( "i" ) || number.endsWith( "I" ) )
number = number.left(number.length() - 1); // Caller id suppress flag - skip.
// Determine if the number is blocked by the fixed-dialing phone book.
bool dialCheckOk = true;
emit dialCheck( number, dialCheckOk );
if ( !dialCheckOk ) {
emit send( "ERROR" );
return true;
}
// Stop if a dialing call is already in progress, or there are both
// connected and held calls at the same time.
if ( hasCall( CallState_Dialing ) || hasCall( CallState_Alerting ) ) {
emit send( "ERROR" );
return true;
}
if ( hasCall( CallState_Active ) && hasCall( CallState_Held ) ) {
emit send( "ERROR" );
return true;
}
// If there is a connected call, place it on hold.
changeGroup( CallState_Active, CallState_Held );
// Check for special dial-back numbers.
if ( number == "199" ) {
send( "NO CARRIER" );
QTimer::singleShot( 5000, this, SLOT(dialBack()) );
return true;
} else if ( number == "1993" ) {
send( "NO CARRIER" );
QTimer::singleShot( 30000, this, SLOT(dialBack()) );
return true;
} else if ( number == "177" ) {
send( "NO CARRIER" );
QTimer::singleShot( 2000, this, SLOT(dialBackWithHangup5()) );
return true;
} else if ( number == "166" ) {
send( "NO CARRIER" );
QTimer::singleShot( 1000, this, SLOT(dialBackWithHangup4()) );
return true;
} else if ( number == "155" ) {
send( "BUSY" );
return true;
}
// Other special numbers
if ( number == "144" )
send( "+CCWV" );
// Perform call control on certain numbers.
if ( number == "12399" ) {
QSimControlEvent event;
event.setType( QSimControlEvent::Call );
event.setResult( QSimControlEvent::Allowed );
event.setText( "12399 allowed by call control" );
emit controlEvent( event );
} else if ( number == "12388" ) {
QSimControlEvent event;
event.setType( QSimControlEvent::Call );
event.setResult( QSimControlEvent::AllowedWithModifications );
event.setText( "12388 allowed, but modified to 12389" );
number = "12389";
emit controlEvent( event );
} else if ( number == "12377" ) {
QSimControlEvent event;
event.setType( QSimControlEvent::Call );
event.setResult( QSimControlEvent::NotAllowed );
event.setText( "12377 disallowed by call control" );
send( "NO CARRIER" );
emit controlEvent( event );
return true;
}
// Create a new call and add it to the list.
CallInfo info;
info.id = newId();
info.state = CallState_Dialing;
info.number = number;
info.incoming = false;
info.dialBack = false;
callList += info;
emit callStatesChanged( &callList );
// Advertise the call state change and then return to command mode.
sendState( info );
send( "OK" );
// Data call - phone number 696969
} else if ( cmd.startsWith( "ATD" ) ) {
// Data call setup.
QString number = cmd.mid(3, cmd.length() - 4);
if ( number.endsWith( "g" ) || number.endsWith( "G" ) )
number = number.left(number.length() - 1); // Closed user group flag - skip.
if ( number.endsWith( "i" ) || number.endsWith( "I" ) )
number = number.left(number.length() - 1); // Caller id suppress flag - skip.
if ( number == "696969" ) {
// Create a new call and add it to the list.
CallInfo info;
info.id = newId();
info.state = CallState_Dialing;
info.number = number;
info.incoming = false;
info.dialBack = false;
callList += info;
emit callStatesChanged( &callList );
// Advertise the call state change and then return to command mode.
sendState( info );
send( "CONNECT 19200" );
} else {
// If not a data line
emit send( "NO CARRIER" );
}
} else if ( cmd == "AT+CLCC" ) {
// List all calls that are presently active.
foreach ( CallInfo info, callList ) {
int multiparty;
if ( countForState(info.state) >= 2 )
multiparty = 1;
else
multiparty = 0;
QString line =
"+CLCC: " + QString::number(info.id) + "," +
QString::number((int)(info.incoming)) + "," +
QString::number((int)(info.state)) + ",0," +
QString::number(multiparty);
if ( !info.number.isEmpty() ) {
line += ",";
line += QAtUtils::encodeNumber(info.number);
}
send( line );
}
send( "OK" );
} else if ( cmd.startsWith( "ATH" ) || cmd == "AT+CHUP" ) {
// Hang up all active and held calls in the system.
hangupAll();
send( "OK" );
} else if ( cmd == "AT+CHLD=0" ) {
// Reject incoming call, or release held calls.
if ( chld0() )
send( "OK" );
else
send( "ERROR" );
} else if ( cmd == "AT+CHLD=1" ) {
// Release all active calls and accept the held or waiting ones.
if ( chld1() )
send( "OK" );
else
send( "ERROR" );
} else if ( cmd.startsWith( "AT+CHLD=1" ) ) {
// Release a specific call.
if ( chld1x( cmd.mid(9).toInt() ) )
send( "OK" );
else
send( "ERROR" );
} else if ( cmd == "AT+CHLD=2" ) {
// Place active calls on hold and accept the held or waiting call.
if ( chld2() )
send( "OK" );
else
send( "ERROR" );
} else if ( cmd.startsWith( "AT+CHLD=2" ) ) {
// Place all active calls on hold except for the specified call.
if ( chld2x( cmd.mid(9).toInt() ) )
send( "OK" );
else
send( "ERROR" );
} else if ( cmd == "AT+CHLD=3" ) {
// Add a held call to the conversation.
if ( chld3() )
send( "OK" );
else
send( "ERROR" );
} else if ( cmd == "AT+CHLD=4" ) {
// Add a held call to the conversation, and then disconnect.
if ( chld4() )
send( "OK" );
else
send( "ERROR" );
} else if ( cmd == "ATA" ) {
// Accept the incoming call.
if ( acceptCall() )
send( "OK" );
else
send( "ERROR" );
} else if ( cmd.startsWith( "AT+CTFR=" ) ) {
// Deflect the incoming call to another number.
int id = idForIncoming();
if ( id >= 0 && !deflectWillFail() ) {
hangupCall(id);
hangupTimer->stop();
ringTimer->stop();
send( "OK" );
} else {
send( "ERROR" );
}
} else {
// Command is not understood by the call manager.
return false;
}
// If we get here, then the command was understood.
return true;
}
void CallManager::emitRing(const CallInfo &info)
{
QString str;
// Annouce the incoming call using GSM 27.007 notifications.
if ( info.state == CallState_Waiting ) {
str = "+CCWA: " + QAtUtils::encodeNumber( info.number ) + ",1,";
if (info.number.isNull())
str += "2";
else if (info.number.isEmpty())
str += "1";
else
str += "0";
} else {
str = "+CRING: VOICE";
if (info.number.isNull())
;
else if (info.number.isEmpty())
str += "\\n\\n+CLIP: " + QAtUtils::encodeNumber( info.number ) + ",,,,1";
else
str += "\\n\\n+CLIP: " + QAtUtils::encodeNumber( info.number ) + ",,,,0";
if (info.calledNumber.isNull())
;
else
str += "\\n\\n+CDIP: " + QAtUtils::encodeNumber( info.calledNumber ) + ",,,";
if (info.name.isNull())
;
else if (info.name.isEmpty())
str += "\\n\\n+CNAP: \"" + info.name + "\",1";
else
str += "\\n\\n+CNAP: \"" + info.name + "\",0";
}
emit unsolicited(str);
}
void CallManager::startIncomingCall( const QString& number,
const QString& calledNumber,
const QString& name, bool dialBack )
{
// Bail out if there is already an incoming call.
if ( idForIncoming() >= 0 ) {
qWarning( "Incoming call already exists - connect create another" );
return;
}
// Create a new call and add it to the list.
CallInfo info;
info.id = newId();
if ( hasCall( CallState_Active ) || hasCall( CallState_Held ) ) {
// If there are active calls, the call state is "waiting", not "incoming".
info.state = CallState_Waiting;
} else {
info.state = CallState_Incoming;
}
info.number = number;
info.calledNumber = calledNumber;
info.incoming = true;
info.dialBack = dialBack;
info.name = name;
callList += info;
emitRing(info);
emit callStatesChanged( &callList );
// Announce the incoming call using Ericsson-style state notifications.
sendState( info );
// Start the ring timer to periodically send RING notifications until call is accepted.
numRings = 0;
ringTimer->start(2000);
}
void CallManager::startIncomingCall( const QString& number,
const QString& calledNumber,
const QString& name )
{
startIncomingCall( number, calledNumber, name, false );
}
void CallManager::hangupAll()
{
for ( int index = 0; index < callList.size(); ++index ) {
callList[index].state = CallState_Hangup;
sendState( callList[index] );
}
callList.clear();
emit callStatesChanged( &callList );
}
void CallManager::hangupConnected()
{
QList<CallInfo> newCallList;
for ( int index = 0; index < callList.size(); ++index ) {
if ( callList[index].state == CallState_Active ) {
callList[index].state = CallState_Hangup;
sendState( callList[index] );
} else {
newCallList += callList[index];
}
}
callList = newCallList;
if ( !hasCall( CallState_Held ) )
waitingToIncoming();
emit callStatesChanged( &callList );
}
void CallManager::hangupHeld()
{
QList<CallInfo> newCallList;
for ( int index = 0; index < callList.size(); ++index ) {
if ( callList[index].state == CallState_Held ) {
callList[index].state = CallState_Hangup;
sendState( callList[index] );
} else {
newCallList += callList[index];
}
}
callList = newCallList;
if ( !hasCall( CallState_Active ) )
waitingToIncoming();
emit callStatesChanged( &callList );
}
void CallManager::hangupConnectedAndHeld()
{
QList<CallInfo> newCallList;
for ( int index = 0; index < callList.size(); ++index ) {
if ( callList[index].state == CallState_Active ||
callList[index].state == CallState_Held ) {
callList[index].state = CallState_Hangup;
sendState( callList[index] );
} else {
newCallList += callList[index];
}
}
callList = newCallList;
waitingToIncoming();
emit callStatesChanged( &callList );
}
void CallManager::hangupCall( int id )
{
chld1x( id );
}
void CallManager::hangupRemote( int id )
{
int index = indexForId( id );
if ( index >= 0 )
{
callList[index].state = CallState_Hangup;
sendState( callList[index] );
callList.removeAt( index );
if ( !hasCall( CallState_Active ) && !hasCall( CallState_Held ) )
waitingToIncoming();
send ( "NO CARRIER" );
emit callStatesChanged( &callList );
}
}
bool CallManager::acceptCall()
{
int id = idForIncoming();
int index = indexForId(id);
if ( id < 0 ) {
return false;
} else if ( hasCall( CallState_Active ) && hasCall( CallState_Held ) ) {
// No open slots to accept the call - fail it. AT+CHLD=1 must be used instead.
return false;
} else if ( hasCall( CallState_Active ) ) {
// Put the active calls on hold and accept the incoming call.
changeGroup( CallState_Active, CallState_Held );
callList[index].state = CallState_Active;
sendState( callList[index] );
emit callStatesChanged( &callList );
return true;
} else {
// Only held calls, or no other calls, so just make the incoming call active.
callList[index].state = CallState_Active;
sendState( callList[index] );
emit callStatesChanged( &callList );
return true;
}
}
bool CallManager::chld0()
{
// If there is an incoming call, then that is the one to hang up.
int id = idForIncoming();
if ( id >= 0 )
return chld1x( id );
// Bail out if no held calls.
if ( !hasCall( CallState_Held ) )
return false;
// Hang up the held calls.
hangupHeld();
return true;
}
bool CallManager::chld1()
{
int id = idForIncoming();
if ( id >= 0 ) {
// Hangup the active calls and then make the incoming call active.
hangupConnected();
int index = indexForId(id);
callList[index].state = CallState_Active;
sendState( callList[index] );
emit callStatesChanged( &callList );
return true;
} else if ( hasCall( CallState_Held ) ) {
// Hangup the active calls and activate the held ones.
hangupConnected();
for ( int index = 0; index < callList.size(); ++index ) {
if ( callList[index].state == CallState_Held ) {
callList[index].state = CallState_Active;
sendState( callList[index] );
emit callStatesChanged( &callList );
}
}
return true;
} else if ( hasCall( CallState_Active ) ) {
// We only have active calls - hang them up.
hangupConnected();
return true;
} else if ( ( id = idForDialing() ) >= 0 ) {
// We have a dialing call.
hangupCall(id);
return true;
} else {
return false;
}
}
bool CallManager::chld1x( int x )
{
QList<CallInfo> newCallList;
bool found = false;
for ( int index = 0; index < callList.size(); ++index ) {
if ( callList[index].id == x ) {
callList[index].state = CallState_Hangup;
sendState( callList[index] );
found = true;
} else {
newCallList += callList[index];
}
}
callList = newCallList;
if ( !hasCall( CallState_Active ) && !hasCall( CallState_Held ) )
waitingToIncoming();
emit callStatesChanged( &callList );
return found;
}
bool CallManager::chld2()
{
int id = idForIncoming();
if ( id >= 0 ) {
if ( hasCall( CallState_Active ) && hasCall( CallState_Held ) ) {
// Three-way calling situation: cannot do anything.
return false;
}
if ( holdWillFail() && hasCall( CallState_Active ) ) {
// Cannot put calls on hold at this time.
return false;
}
changeGroup( CallState_Active, CallState_Held );
int index = indexForId( id );
callList[index].state = CallState_Active;
sendState( callList[index] );
emit callStatesChanged( &callList );
return true;
} else if ( hasCall( CallState_Active ) && hasCall( CallState_Held ) ) {
// Swap the active and held calls.
if ( activateWillFail() || holdWillFail() )
return false;
changeGroup( CallState_Active, CallState_SwapDummy );
changeGroup( CallState_Held, CallState_Active );
changeGroup( CallState_SwapDummy, CallState_Held );
return true;
} else if ( hasCall( CallState_Active ) ) {
// No held calls - put active calls on hold.
if ( holdWillFail() )
return false;
changeGroup( CallState_Active, CallState_Held );
return true;
} else if ( hasCall( CallState_Held ) ) {
// Re-activate the held calls.
if ( activateWillFail() )
return false;
changeGroup( CallState_Held, CallState_Active );
return true;
} else {
// No held or active calls - cannot do anything.
return false;
}
}
bool CallManager::chld2x( int x )
{
int index = indexForId(x);
if ( index < 0 )
return false;
if ( callList[index].state == CallState_Held ) {
// Call is currently on hold - activate it.
if ( activateWillFail() )
return false;
if ( hasCall( CallState_Active ) && countForState( CallState_Held ) > 1 ) {
// Cannot swap because there are active calls, but multiple held calls.
return false;
} else if ( hasCall( CallState_Active ) ) {
// Swap active and held calls.
if ( holdWillFail() )
return false;
changeGroup( CallState_Active, CallState_SwapDummy );
changeGroup( CallState_Held, CallState_Active );
changeGroup( CallState_SwapDummy, CallState_Held );
} else {
// No active calls, so make just this call active.
callList[index].state = CallState_Active;
sendState( callList[index] );
emit callStatesChanged( &callList );
}
return true;
} else if ( callList[index].state == CallState_Active ) {
// Call is currently active - put all others in the active group on hold.
if ( activateWillFail() )
return false;
if ( hasCall( CallState_Held ) )
return false; // Can't do this if there is already a hold group.
for ( int index2 = 0; index2 < callList.size(); ++index2 ) {
if ( callList[index2].state == CallState_Active && index2 != index ) {
if ( holdWillFail() )
return false;
callList[index2].state = CallState_Held;
sendState( callList[index2] );
emit callStatesChanged( &callList );
}
}
return true;
} else {
// Not held or active.
return false;
}
}
bool CallManager::chld3()
{
if ( joinWillFail() ) {
return false;
} else if ( hasCall( CallState_Active ) && hasCall( CallState_Held ) ) {
if ( multipartyLimit() >= 0 &&
( countForState( CallState_Active ) +
countForState( CallState_Held ) ) > multipartyLimit() ) {
// We have exceeded the multiparty limit.
return false;
}
changeGroup( CallState_Held, CallState_Active );
return true;
} else {
return false;
}
}
bool CallManager::chld4()
{
if ( joinWillFail() ) {
return false;
} else if ( hasCall( CallState_Active ) && hasCall( CallState_Held ) ) {
if ( multipartyLimit() >= 0 &&
( countForState( CallState_Active ) +
countForState( CallState_Held ) ) > multipartyLimit() ) {
// We have exceeded the multiparty limit.
return false;
}
hangupConnectedAndHeld();
return true;
} else {
return false;
}
}
void CallManager::dialingToConnected()
{
// Find the currently dialing or alerting call.
int index = indexForId( idForState( CallState_Dialing ) );
if ( index < 0 )
index = indexForId( idForState( CallState_Alerting ) );
if ( index < 0 )
return;
// Transition the call to its new state.
callList[index].state = CallState_Active;
sendState( callList[index] );
emit callStatesChanged( &callList );
// If the dialed number starts with 05123, disconnect the
// call after xx seconds, where xx is part of the dial string
// as 05123xx
if( callList[index].number.startsWith( "05123" ) ) {
bool ok;
QString temp = callList[index].number;
temp = temp.replace( "05123" , "" );
int timeout = temp.toInt( &ok, 10 );
timeout = ok ? timeout * 1000 : 10000;
QTimer::singleShot( timeout, this, SLOT(hangup()) );
}
}
void CallManager::dialingToAlerting()
{
// Find the currently dialing or alerting call.
int index = indexForId( idForState( CallState_Dialing ) );
if ( index < 0 )
return;
// Transition the call to its new state.
callList[index].state = CallState_Alerting;
sendState( callList[index] );
emit callStatesChanged( &callList );
}
void CallManager::waitingToIncoming()
{
int index = indexForId( idForState( CallState_Waiting ) );
if ( index < 0 )
return;
callList[index].state = CallState_Incoming;
sendState( callList[index] );
}
void CallManager::dialBack()
{
startIncomingCall( "1234567", "7654321", "Alice", true );
}
void CallManager::dialBackWithHangup5()
{
startIncomingCall( "1234567", "7654321", "Bob", true );
hangupTimer->start( 5000 );
}
void CallManager::dialBackWithHangup4()
{
startIncomingCall( "1234567", "7654321", "Mallory", true );
hangupTimer->start( 4000 );
}
void CallManager::hangupTimeout()
{
// Find the call that started off as incoming, even if it isn't any longer.
// Note: this won't work too well if there are multiple dial-back calls.
for ( int index = 0; index < callList.size(); ++index ) {
if ( callList[index].dialBack ) {
hangupCall( callList[index].id );
break;
}
}
}
void CallManager::hangup()
{
send ( "NO CARRIER" );
hangupConnected();
}
void CallManager::sendNextRing()
{
if ( idForIncoming() >= 0 ) {
if ( numRings++ >= 10 ) {
// Ringing for too long, so hang up the call.
hangupCall( idForIncoming() );
} else {
for ( int index = 0; index < callList.size(); ++index )
if ( callList[index].id == idForIncoming() && callList[index].state == CallState_Incoming )
emitRing(callList[index]);
ringTimer->start(2000);
}
}
}
int CallManager::newId()
{
int id;
bool seen;
for ( id = 1; id <= 32; ++id ) {
seen = false;
foreach ( CallInfo info, callList ) {
if ( info.id == id ) {
seen = true;
break;
}
}
if ( !seen )
return id;
}
return -1;
}
int CallManager::idForDialing()
{
int id = idForState( CallState_Dialing );
if ( id < 0 )
id = idForState( CallState_Alerting );
return id;
}
int CallManager::idForIncoming()
{
int id = idForState( CallState_Incoming );
if ( id < 0 )
id = idForState( CallState_Waiting );
return id;
}
int CallManager::idForState( CallState state )
{
for ( int index = 0; index < callList.size(); ++index ) {
if ( callList[index].state == state )
return callList[index].id;
}
return -1;
}
int CallManager::countForState( CallState state )
{
int count = 0;
for ( int index = 0; index < callList.size(); ++index ) {
if ( callList[index].state == state )
++count;
}
return count;
}
int CallManager::indexForId( int id )
{
for ( int index = 0; index < callList.size(); ++index ) {
if ( callList[index].id == id )
return index;
}
return -1;
}
bool CallManager::hasCall( CallState state )
{
foreach ( CallInfo info, callList ) {
if ( info.state == state )
return true;
}
return false;
}
void CallManager::changeGroup( CallState oldState, CallState newState )
{
for ( int index = 0; index < callList.size(); ++index ) {
if ( callList[index].state == oldState ) {
callList[index].state = newState;
sendState( callList[index] );
}
}
emit callStatesChanged( &callList );
}
void CallManager::sendState( const CallInfo& info )
{
static int const stateMap[] = {3, 4, 1, 6, 5, 5, 0, 0};
if ( info.state == CallState_SwapDummy )
return; // In the middle of a state swap: don't send this.
QString line =
"*ECAV: " + QString::number(info.id) + "," +
QString::number(stateMap[(int)(info.state)]) + ",0";
if ( !info.number.isEmpty() ) {
line += ",,," + QAtUtils::encodeNumber(info.number);
}
if ( info.state == CallState_Incoming || info.state == CallState_Waiting ) {
// Stop sending RING notifications.
ringTimer->stop();
}
if ( info.state == CallState_Hangup && info.dialBack ) {
// Hanging up an incoming call that started as a dialback. Stop hangup timer.
hangupTimer->stop();
}
emit unsolicited( line );
}
OSZAR »